Найти - Пользователи
Полная версия: Асинхронный запуск нового процесса
Начало » Python для экспертов » Асинхронный запуск нового процесса
1 2 3 4
shiza
Если от пользователя надо всетки только одну строку, то придется распарсить ее, как это делает shell, на части и передавать в subprocess.Popen таки список. Там не очень сложный синтаксис IMHO =).
The gray Cardinal
shiza
Если от пользователя надо всетки только одну строку, то придется распарсить ее…
Перебирать посимвольно? Встретил двойную кавычку, “запомнил” её, встретил пробел, тоже “запомнил” его, и так понемногу “набирать” элементы нужного мне списка, в таком роде?
Мне почему-то казалось, что Python должен уметь превратить в список строку типа
gedit “/home/alex/фа йл.txt”
“автоматически”, ведь задача-то вроде беспредельно тривиальная - разобрать типичную командную строку на список. Это не так, надо ручками разбирать?
gmorgunov
Привет.
Протестировал в SUSE.Первое впечатление - хорошая программа(нормальный баланс между функционалом
и простотой) :)
Значит тестовый пример:
- элемент1 ls
- элемент2 gedit “/home/mike/test.tcl”
- элемент3 cat QuickStarter | grep Quick
- элемент4 /home/mike/counter/counter (Gui Qt)
- элемент5 элемент5

Все запускается, работает. (gedit “/home/mike/test.tcl” - тоже кстати).

Единственное замечание: вкладка запуск/запустить ,после запустить /home/mike/couter/couter - виснет.
Только Ctrl+c.
По-моему ее надо вообще убрать(Функционала и так хватает).
Ну и реакция на экзотику(русская команда) вполне предсказуема. :)

Тестировал и в kate и в console, и в /home/mike, и в /home/mike/pusk_gray.
В каждой папке сохраняет свои настройки, что и задумывалось наверное.

В общем - здоровая пропаганда PyQt4 :)
The gray Cardinal
Немного исправил скрипт, разобрал строку (хотя, как-то неуклюже получилось). Зависаний вроде нет.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, glob, os, locale, subprocess, cPickle
from PyQt4 import QtGui, QtCore

class Dialog(QtGui.QDialog):
def __init__(self, parent=None, elem=None):
QtGui.QDialog.__init__(self, parent)
self.setMinimumWidth(400) # минимальная ширина окна диалога
self.setFixedHeight(100) # запрет изменения высоты

lay = QtGui.QGridLayout(self)
label1 = QtGui.QLabel(parent.tr.translate('QuickStarter', 'Name:'), self) # метка
label1.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
lay.addWidget(label1, 0, 0)
self.ln_edit1 = QtGui.QLineEdit('', self) # строковое поле ввода
self.ln_edit1.setMaxLength(100)
lay.addWidget(self.ln_edit1, 0, 1, 1, 2)
label2 = QtGui.QLabel(parent.tr.translate('QuickStarter', 'Command:'), self) # метка
label2.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
lay.addWidget(label2, 1, 0)
self.ln_edit2 = QtGui.QLineEdit('', self) # строковое поле ввода
self.ln_edit2.setMaxLength(255)
lay.addWidget(self.ln_edit2, 1, 1, 1, 2)
boxlay = QtGui.QHBoxLayout()
lay.addLayout(boxlay, 2, 2)
button1 = QtGui.QPushButton(parent.tr.translate('QuickStarter', 'Ok'), self) # кнопка
button1.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
self.connect(button1, QtCore.SIGNAL('clicked()'), lambda: self.done(1))
boxlay.addWidget(button1)
button2 = QtGui.QPushButton(parent.tr.translate('QuickStarter', 'Cancel'), self) # кнопка
button2.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
self.connect(button2, QtCore.SIGNAL('clicked()'), lambda: self.done(0))
boxlay.addWidget(button2)

if elem == None:
self.setWindowTitle(parent.tr.translate('QuickStarter', 'New element')) # заголовок окна
else:
self.setWindowTitle(parent.tr.translate('QuickStarter', 'Element edit')) # заголовок окна
self.ln_edit1.setText(elem.data(0, QtCore.Qt.DisplayRole).toString())
self.ln_edit2.setText(elem.data(1, QtCore.Qt.DisplayRole).toString())

class Translator(QtCore.QTranslator):
def __init__(self, parent=None, lang='QuickStarter_en_EN.qm'):
QtCore.QTranslator.__init__(self, parent)
self.load(lang)
def translate(self, context, sourceText):
res = QtCore.QTranslator.translate(self, context, sourceText)
if len(res) == 0:
res = QtCore.QString(sourceText)
return res

class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QWidget.__init__(self)
home = os.path.realpath(os.path.dirname(sys.argv[0])) # каталог скрипта
self.setWindowIcon(QtGui.QIcon(home + '/qs64.bmp')) # иконка окна
menubar = self.menuBar() # строка меню
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # поверх всех окон

# объект для сохранения настроек приложения:
self.settings = QtCore.QSettings('script-coding.info', 'QuickStarter')

# восстановление языковой настройки:
self.lang = self.settings.value('lang', QtCore.QVariant(u'QuickStarter_en_EN.qm')).toString()
self.tr = Translator(None, self.lang)

# восстановление настройки геометрии окна:
self.setGeometry(self.settings.value('geometry', QtCore.QVariant(QtCore.QRect(300, 300, 350, 200))).toRect())

# заголовок окна:
self.setWindowTitle(self.tr.translate('QuickStarter', 'Quick starter'))

# меню "Language choice":
menuLang = menubar.addMenu(self.tr.translate('QuickStarter', 'Language choice'))

pointName = self.tr.translate('QuickStarter', 'By default')
point = QtGui.QAction(pointName, self)
self.connect(point, QtCore.SIGNAL('triggered()'), lambda: self.lang_menu('QuickStarter_en_EN.qm'))
menuLang.addAction(point)

for filename in glob.glob(home + '/QuickStarter_*_*.qm'):
pointName = os.path.basename(filename)
point = QtGui.QAction(pointName, self)
self.connect(point, QtCore.SIGNAL('triggered()'), lambda: self.lang_menu(pointName))
menuLang.addAction(point)

# выход по Escape:
menuLang.addSeparator()
exit = QtGui.QAction(self.tr.translate('QuickStarter', 'Exit'), self)
exit.setShortcut('Escape')
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
menuLang.addAction(exit)

# дерево
self.tree = QtGui.QTreeWidget(self)
self.setCentralWidget(self.tree)
self.tree.headerItem().setHidden(True)
# обработчик двойного щелчка по дереву
self.connect(self.tree, QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'), self.doubleClick)

# восстановление содержимого дерева:
def restoreTreeItem(lst, parent = None):
item = QtGui.QTreeWidgetItem()
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(lst[0]))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(lst[1]))
if parent:
parent.addChild(item)
else:
self.tree.addTopLevelItem(item)
if lst[3]:
self.tree.expandItem(item)
if lst[4]:
self.tree.setCurrentItem(item)
for elem in lst[2]:
restoreTreeItem(elem, item)

dump = str(self.settings.value('tree', QtCore.QVariant('(lp1\n.')).toString())
self.treeList = cPickle.loads(dump)
for elem in self.treeList:
restoreTreeItem(elem)

# меню "Edit"
menuEdit = menubar.addMenu(self.tr.translate('QuickStarter', 'Edit'))

nm = self.tr.translate('QuickStarter', 'Add an element of the root')
self.addRoot = QtGui.QAction(nm, self)
self.connect(self.addRoot, QtCore.SIGNAL('triggered()'), self.addRootEvent)
menuEdit.addAction(self.addRoot)

nm = self.tr.translate('QuickStarter', 'Add an element')
self.add = QtGui.QAction(nm, self)
self.connect(self.add, QtCore.SIGNAL('triggered()'), self.addEvent)
menuEdit.addAction(self.add)

nm = self.tr.translate('QuickStarter', 'Delete the element')
self.delete = QtGui.QAction(nm, self)
self.delete.setShortcut('Delete')
self.connect(self.delete, QtCore.SIGNAL('triggered()'), self.deleteEvent)
menuEdit.addAction(self.delete)

nm = self.tr.translate('QuickStarter', 'Edit the element')
self.edit = QtGui.QAction(nm, self)
self.edit.setShortcut('Return')
self.connect(self.edit, QtCore.SIGNAL('triggered()'), self.editEvent)
menuEdit.addAction(self.edit)

# меню "Running"
menuEdit = menubar.addMenu(self.tr.translate('QuickStarter', 'Running'))

nm = self.tr.translate('QuickStarter', 'Run')
self.run = QtGui.QAction(nm, self)
self.run.setShortcut('Space')
self.connect(self.run, QtCore.SIGNAL('triggered()'), self.runEvent)
menuEdit.addAction(self.run)

nm = self.tr.translate('QuickStarter', 'Run and do not exit')
self.runNotExit = QtGui.QAction(nm, self)
self.runNotExit.setShortcut('Ctrl+Space')
self.connect(self.runNotExit, QtCore.SIGNAL('triggered()'), lambda: self.runEvent(False))
menuEdit.addAction(self.runNotExit)

def runEvent(self, exit = True):
# Обработчик пункта меню "Run".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
commandLine = unicode(currItem.data(1, QtCore.Qt.DisplayRole).toString())
commandLine = commandLine.encode(locale.getdefaultlocale()[1])
commandLine = os.path.expanduser(commandLine) # обработка "~"

lst = commandLine.split()
i = 0
part = ''
resList = []
while i < len(lst):
piece = lst[i]
begin = piece[0]
end = piece[-1]
if begin <> '"' and end <> '"':
if len(part) == 0:
resList.append(piece)
else:
part = part + ' ' + piece
elif begin == '"' and end == '"':
resList.append(piece[1:-1])
elif begin == '"' and end <> '"':
part += piece[1:]
else: # begin <> '"' and end == '"':
part = part + ' ' + piece[:-1]
resList.append(part)
part = ''
i += 1
if len(part) > 0: # незакрытая кавычка
resList.append(part)

try:
subprocess.Popen(resList) # запуск
# subprocess.Popen(commandLine, shell = True) # запуск
if exit:
self.close()
except:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Failed to run the application.') + '\n' + \
self.tr.translate('QuickStarter', 'Please check the command line and / or your permission to the files.')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)

def doubleClick(self, item, column):
# Обработчик двойного щелчка по дереву.
self.editEvent()

def contextMenuEvent(self, event):
# Обработчик контекстного меню.
menu = QtGui.QMenu(self)
menu.addAction(self.addRoot)
menu.addAction(self.add)
menu.addAction(self.delete)
menu.addAction(self.edit)
menu.addSeparator()
menu.addAction(self.run)
menu.addAction(self.runNotExit)
menu.exec_(event.globalPos())

def addRootEvent(self):
# Обработчик пункта меню "Add an element of the root".
dlg = Dialog(self)
if dlg.exec_(): # нажата кнопка "Ок"
item = QtGui.QTreeWidgetItem()
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
self.tree.addTopLevelItem(item)
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def addEvent(self):
# Обработчик пункта меню "Add an element".
parentItem = self.tree.currentItem()
if parentItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen parent element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
dlg = Dialog(self)
if dlg.exec_(): # нажата кнопка "Ок"
item = QtGui.QTreeWidgetItem()
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
item.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
item.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
parentItem.addChild(item)
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def deleteEvent(self):
# Обработчик пункта меню "Delete the element".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
caption = self.tr.translate('QuickStarter', 'Quick starter')
text1 = self.tr.translate('QuickStarter', 'Deleting element')
text2 = self.tr.translate('QuickStarter', 'Continue?')
nm = ' "' + currItem.data(0, QtCore.Qt.DisplayRole).toString() + '".\n '
text = text1 + nm + text2
reply = QtGui.QMessageBox.question(self, caption, text, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.No:
return
parent = currItem.parent()
if parent != None:
parent.takeChild(parent.indexOfChild(currItem))
else:
self.tree.takeTopLevelItem(self.tree.indexOfTopLevelItem(currItem))

def editEvent(self):
# Обработчик пункта меню "Edit the element".
currItem = self.tree.currentItem()
if currItem == None:
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'Do not chosen element!')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)
return
dlg = Dialog(self, currItem)
if dlg.exec_(): # нажата кнопка "Ок"
nm = dlg.ln_edit1.text()
if len(nm) == 0: nm = self.tr.translate('QuickStarter', 'Untitled')
currItem.setData(0, QtCore.Qt.DisplayRole, QtCore.QVariant(nm))
currItem.setData(1, QtCore.Qt.DisplayRole, QtCore.QVariant(dlg.ln_edit2.text()))
self.tree.sortItems(0, QtCore.Qt.AscendingOrder)
dlg.destroy()

def lang_menu(self, pointName):
# Обработчик любого пункта колонки меню "Language choice".
self.lang = pointName
caption = self.tr.translate('QuickStarter', 'Quick starter')
text = self.tr.translate('QuickStarter', 'In order for language setting to come into force, restart the application.')
QtGui.QMessageBox.information(self, caption, text, QtGui.QMessageBox.Ok)

def closeEvent(self, event):
# сохранение языковой настройки:
self.settings.setValue('lang', QtCore.QVariant(self.lang))
# сохранение настройки геометрии окна:
self.settings.setValue('geometry', QtCore.QVariant(self.geometry()))
# сохранение содержимого дерева:
self.treeList = []
for i in xrange(self.tree.topLevelItemCount()):
topLevelItem = self.tree.topLevelItem(i)
lst = []
self.treeList.append(lst)
self.serializeTreeItem(topLevelItem, lst)
dump = cPickle.dumps(self.treeList)
self.settings.setValue('tree', QtCore.QVariant(dump))

def serializeTreeItem(self, item, lst):
nm = item.data(0, QtCore.Qt.DisplayRole).toString()
cm = item.data(1, QtCore.Qt.DisplayRole).toString()
lst.append(nm) # первый элемент - наименование
lst.append(cm) # второй элемент - командная строка
lstChild = []
lst.append(lstChild) # третий элемент - список дочерних
lst.append(item.isExpanded()) # четвёртый элемент - признак "раскрытости"
if self.tree.currentItem() == item:
lst.append(True) # пятый элемент - признак текущего элемента
else:
lst.append(False) # пятый элемент - признак текущего элемента
for i in xrange(item.childCount()):
elem = []
lstChild.append(elem)
self.serializeTreeItem(item.child(i), elem)

if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Plastique")
main = MainWindow()
main.show()
sys.exit(app.exec_())
gmorgunov
Спасибо за тест :).
gmorgunov
cat QuickStarter | grep Quick
Это и всё подобное должно просто не вызывать зависаний процессов, и не более того - прога предназначена исключительно для запуска GUI-приложений.
gmorgunov
Единственное замечание: вкладка запуск/запустить ,после запустить /home/mike/couter/couter - виснет.
Может, не “вкладка”, а меню? Что есть “couter”? Это исполняемый файл или папка? Если второе, то надо запускать что-то вроде “nautilus /home/mike/couter/couter”. Что именно и как “виснет”? Я ведь тебя не понял вначале именно из-за этого неясного “виснет” (я думал, что виснет сам python-скрипт, а не процесс оболочки).
gmorgunov
По-моему ее надо вообще убрать(Функционала и так хватает).
Не понял :). Если убрать команду “Запустить”, скрипт будет бесполезен полностью.
gmorgunov
Попробую объяснить.
Значит вкладка запуск:
1) Запустить.
2) Запустить и не выходить.

Я имел в виду, что пункт 1) запустить - лишний( имхо).

По поводу зависания, последовательность действий:

- выделяю элемент4 - исполняемый файл /home/mike/counter/counter ( в папке /home/mike/counter - qmake -project; qmake;
make на выходе - рабочий /home/mike/counter/counter).
- запустить ( именно запустить, не запустить и не выходить).
- появляется окно моего гуя.
- пару раз нажимаю на счетчик. в консоли выводится кол-во нажатий.
- закрываю окно гуя.
Вот тут, я и повисаю в консоли. /home/mike/counter/counter - рабочий, только что проверил.

P.S. Правда я это делал со старым кодом, новый еще не смотрел. :)
The gray Cardinal
gmorgunov
Я имел в виду, что вкладка 1) запустить - лишняя.
Всё же это не “вкладка”, а самый обыкновенный пункт меню.
“Запустить” от “Запустить и не выходить” отличается одним единственным оператором: “self.close()”. У обоих этих пунктов меню есть хоткеи, ими и предполагается пользоваться всегда. А меню сделано “для комплектности”. Не совсем понимаю, чем станет лучше, если убрать один из этих двух пунктов меню :).
gmorgunov
выделяю элемент4 - исполняемый файл /home/mike/counter/counter ( в папке /home/mike/counter - qmake -project; qmake;
make на выходе - рабочий /home/mike/counter/counter)
Эту строку мой мозг распарсить не в состоянии ;).
Похоже, что /home/mike/counter/counter - исполняемый файл GUI-приложения, пригодный для непосредственного запуска. В таком случае вопрос: а не повисаешь ли ты в консоли, если запускаешь /home/mike/counter/counter непосредственно из консоли, без моего скрипта?
===
Вообще, мой скрипт задумывался как альтернатива Gnome-скому “Alt+F2”, и им надо пользоваться не из консоли, а вешать его на хоткей, работая на Рабочем столе. Т.е. нажал на хоткей - вылезло окно скрипта, далее клавишами управления курсора подполз в дереве к нужному ярлыку, нажал пробел - произошёл запуск нужного GUI-приложения, окно скрипта захлопнулось. При закрытии окна скрипта, кстати, запоминаются его размеры и положение, а также состояние дерева (текущий элемент и развёрнутость узлов). Таким образом, например, следующий запуск того же GUI-приложения будет всего в два нажатия: хоткей для вызова скрипта и сразу пробел. В винде всё то же самое. Имхо, это чуть удобнее, чем “Alt+F2” в Gnome или “Win+R” в винде: не надо набирать командную строку.
===
Буду премного благодарен за тест последнего варианта :).
И ещё есть подозрения в неуклюжести распарсивания строки, сделанном по совету shiza (неуклюж не совет, а его реализация :)). Но, похоже, распарсивание всё же работает.
gmorgunov
Итак тестируем новый вариант(QuickStarter2 - в папке /home/mike, где и QuickStarter)
Все настройки сохранились, удаляю их, создаю новые:

- счетчик /home/mike/counter/counter.
- таблица /home/mike/ExamplesShlee/chapter11/TableWidget/TableWidget.

Тест:
Запустить не выходить:
- счетчик - нормально.
- таблица - нормально.
запустить:
- таблица - нормально.
- счетчик - опять виснет, но это уже мой баг(хотя из консоли запускается нормально) :/

Проверяем “ ” - “/home/mike/ExamplesShlee/chapter11/TableWidget/TableWidget”
Оба нормально.
Проверяем ~ - “~/ExamplesShlee/chapter11/TableWidget/TableWidget”
Вот тут - “проверьте …” Т.е. ~ парсится неправильно.

P.S. Парсить ~ по-моему лишнее. Указывать полный путь - надежнее будет. А так все нормально :)
The gray Cardinal
Спасибо :).
А вообще интересная петрушка получается. Выходит, к каким-то “зависаниям” приводит поспешный вызов “self.close()”, что ли?
shiza
The gray Cardinal
“автоматически”, ведь задача-то вроде беспредельно тривиальная - разобрать типичную командную строку на список. Это не так, надо ручками разбирать?
Мне тоже казалось что такое должно быть. Однако я пробежался по стандартным модулям…
и прям такого не нашел. Нашел то, что могло-бы облегчить задачу, но оно что-то сложновато. В общем получается, что проще ручками %)
The gray Cardinal
shiza
Спасибо :).
===
По поводу ~, в винде без проблем запускается:

~\notepad “C:\Temp\фа йл.txt”

(Предварительно скопировать туда notepad.exe, конечно.)
Попозже проверю ~ ещё раз в openSUSE.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB