Mit subprocess.call xterm starten und parent schließen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

ich möchte einen kleinen Launcher schreiben, scheitere aber an einer vermeintlich einfachen Sache: Wenn der Childprozess gestartet ist, soll der Parent (das Launcherskript) geschlossen werden. Bisher konnte ich das nur über eine eingebaute Verzögerung machen:

Code: Alles auswählen

import subprocess
import sys
import time

items = [
    ('ranger', ('xterm', '-e', 'ranger')),
    ('iceweasel', ('iceweasel',))
]

def start():
    for idx, item in enumerate(items):
        print '{:2d} {}'.format(idx, item[0])
    choice = int(raw_input('> '))
    app = items[choice][1]
    subprocess.Popen(app)
    time.sleep(0.5)
    sys.exit()

if __name__ == '__main__':
    start()
Den Launcher starte ich so:

Code: Alles auswählen

$ xterm -e launcher.py
Gibt es nicht eine Möglichkeit, mit dem Start der Anwendung den Launcher zu schließen, ohne diesen ``time.sleep(0.5)`` Hack?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Also wenn das *so* funktioniert, dann könntest Du auch einfach auf das Ende von dem externen Prozess warten. Dann wartest Du nicht zu lange, aber auch nicht zu kurz.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@mutetella: üblicherweise macht man sowas mit 'os.execvp'. Das ersetzt den aktuellen Prozess, man braucht in also gar nicht erst mühsam beenden.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:Also wenn das *so* funktioniert, ...
Weshalb sollte das *so* nicht funktionieren?
BlackJack hat geschrieben:... dann könntest Du auch einfach auf das Ende von dem externen Prozess warten.
Genau das möchte ich ja nicht. Sonst hab' ich ja bei jedem Programm, das ich über den Launcher starte, das Terminal des Launchers offen.
BlackJack hat geschrieben:Dann wartest Du nicht zu lange, aber auch nicht zu kurz.
Wie, nicht zu lang, nicht zu kurz? :?

@Sirius3
Klingt genau nach dem, was ich suche, allerdings funktioniert das nur mit X-Anwendungen. Wenn ich

Code: Alles auswählen

os.execvp('xterm', ('xterm', '-e', 'ranger'))
mache, startet `ranger` in einem neuen xterm und das Terminal, von dem aus `execvp` aufgerufen wird, bleibt solange offen, bis `ranger` wieder geschlossen wird. Aber eigentlich sollte doch `ranger` den Pythonprozess, der `execvp` aufruft, übernehmen?

Es kommt mir so vor, als würde weder `subprocess.Popen` noch `os.execvp` Kenntnis darüber bekommen, dass ein neuer Prozess gestartet wurde. Kann das sein?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Vergiss es, ich hätte da ein anderes Verhalten erwartet.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

@mutetella: Nach der Dokumentation wuerde ich es eher mit

Code: Alles auswählen

os.execvp('xterm', ('-e', 'ranger'))
versuchen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@cofi
Ok, ich muss gestehen, dass ich die Dokumentation nicht vollends begriffen hab' und deshalb auf effobt dieses Beispiel

Code: Alles auswählen

program = "python"
arguments = ["hello.py"]

print os.execvp(program, (program,) +  tuple(arguments))
übernommen hab'. Dass dabei das aufzurufende Programm sowohl für den `file` Parameter wie auch als erstes Element für `args` übergeben wird, kam mir auch seltsam vor. Wie auch immer: Sowohl die eine wie die andere Variante ändert letztlich nichts daran, dass das aufzurufende Programm (in meinem Fall ('xterm', '-e', 'ranger')) den aufrufenden Prozess (``xterm -e launcher.py``) nicht übernimmt.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Na moment mal: Du willst, dass "ranger" im 1. xterm ausgefuehrt wird? Dann solltest du "launcher.py" durch "ranger" ersetzen und nicht durch ein "xterm", das "ranger" ausfuehrt.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@cofi
Nein, ich möchte, dass `ranger` in einem neuen Terminal startet und das Terminal, von dem aus ``xterm -e ranger`` aufgerufen wird, dann wieder geschlossen wird. Wie gesagt: Mit einer X-Anwendung geschieht eben dieses, wenn ich allerdings ``xterm`` aufrufe, muss ich nach ~ 0.5 Sekunden das 1. Terminal "händisch" mit ``sys.exit()`` schließen. Und das empfinde ich eben als einen Hack und nicht als saubere Lösung.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@cofi: Das erste Argument ist das Programm und das zweite Argument ist das was dem aufgerufenen Programm als `argv` übergeben wird. Und da ist das erste Element ”noch mal” das Programm. Das mag redundant erscheinen weil es in den meisten Fällen das gleiche wie das erste Argument ist, aber es muss nicht das gleiche sein. Man kann unter umständen auch wollen dass das Programm selber ”denkt” es wäre unter einem anderen Namen oder Pfad gestartet worden wenn es sein `argv` untersucht. Das ist zum Beispiel immer dann der Fall wenn ein Programm über einen symbolischen Link gestartet wurde. Dann ist das erste Argument der tatsächliche Pfad zum Programm und das erste Element von `argv` der symbolische Link. Es gibt dann zum Beispiel auch Programme die sich unterschiedlich vehalten, je nach dem über welchen Link/Namen sie gestartet wurden.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ich kann das jetzt mangels Hintergrundwissen nicht wirklich artikulieren, aber aus einem Gefühl heraus versuchte ich erstmal, das `launcher.py` Skript ohne shebang via ``python launcher.py`` aufzurufen, weil ich dachte, dass eventuell über die shebang ein zusätzlicher Prozess gestart wird. War aber eine Sackgasse. Hat mir aber keine Ruhe gelassen, und so habe ich versucht, ``xterm -e ranger`` folgendermaßen zu starten:

Code: Alles auswählen

os.execvp('bash', ('xterm', '-e', 'ranger'))
Somit wird tatsächlich der bestehende Prozess durch ``xterm`` ersetzt:

Code: Alles auswählen

$ xterm -e launcher.py
$ ps -A | grep launcher.py
 8285 pts/1     00:00:00 launcher.py
# Nachdem ich `ranger` im launcher gewählt habe:
$ ps -A | grep 8285
  8285 pts/1    00:00:00 bash
$ ps -A | grep ranger
  8301 pts/1    00:00:00 ranger
# Nachdem ich `ranger` beendet habe:
$ ps -A | grep 8301
$ ps -A | grep 8285
So will ich das! Wenn mir das jetzt noch jemand erklären könnte, wär' das klasse!

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Nein wird er nicht, Du startest da bloss eine `bash`-Shell und tust so als wäre deren Name `xterm`. Du führst dort *nicht* ``xterm -e ranger`` aus sondern eigentlich ``bash -e ranger``!

Edit: Was mir übrigens ein wenig unsinnig erscheint, denn Du könntest doch eigentlich auch ``ranger`` *direkt*, ohne eine zusätzliche Shell starten, oder!?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:... Du könntest doch eigentlich auch ``ranger`` *direkt*, ohne eine zusätzliche Shell starten, oder!?
Dann würde ``ranger` allerdings nicht in einem eigenen Terminal starten, sondern im selben wie der Launcher. Und ich möchte eben ``ranger`` in einem eigenen Terminal starten und dann den Launcher schließen.

Mehr und mehr glaube ich, dass ``xterm`` im Gegensatz zu anderen X-Anwendungen aus irgendeinem Grund nicht oder anderst signalisiert, dass es ordnungsgemäß gestartet ist. Und `os.execvp` wie auch `subprocess.Popen` fahren erst fort, wenn dieses Signal (0) kommt. Ich habe mal folgendes probiert, was das auch bestätigt:

Code: Alles auswählen

process = subprocess.Popen(('xterm', '-e', 'ranger'))
while True:
    print process.poll()
    time.sleep(0.5)
# Ein neues Terminal mit `ranger` startet und es erscheint...
None
None
None
...
# Nachdem `ranger` beendet wird erscheint...
0
0
0
...
Das gleiche mit

Code: Alles auswählen

process = subprocess.Popen(('iceweasel', ))
while True:
    print process.poll()
    time.sleep(0.5)
# `iceweasel` startet und es erscheint...
0
0
0
...
# Und nach dem beenden...
0
0
0
...
Es scheint also eher ein `xterm` Problem bzw. Eigenart zu sein, oder?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Bei ``os.execvp('bash', ('xterm', '-e', 'ranger'))`` startet ``ranger`` auch nicht in einem eigenen ``xterm``. Das in dem Aufruf 'xterm' steht hat *nichts* zu bedeuten. Du hättest da auch ``os.execvp('bash', ('halleluja und drei ave maria', '-e', 'ranger'))`` schreiben können.


Nicht ``xterm`` verhält sich komisch sondern ``iceweasel`` verhält sich hier anders als viele andere Programme! Die 0 bedeutet nicht dass der Prozess gestartet ist, sondern das er *beendet* ist. ``iceweasel`` startet und beendet sich gleich wieder. Das Programm was Du da siehst und was weiter läuft ist entweder ``iceweasel``-Prozess der bereits vorher lief, oder einer der von dem ``iceweasel``-Prozess als weiterer, „detach”ter Prozess gestartet wurde.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack
Und wie kann ich dann ermitteln, dass das Programm gestartet ist, damit ich dann den Launcher schließen kann? Es kann doch nicht sein, dass ich nach einer ``pi * Daumen`` Verzögerung hoffen muss, dass das Programm steht um den Launcher schließen zu können!?

mutetella

P.S. Übrigens habe ich noch `pterm`, `aterm`, `rxvt`, `lxterminal` und das `gnome-terminal` getestet. Alle senden nach ihrem Start ein `None`, außer das `gnome-terminal`, auch hier kommt die 0.
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Was ist denn für Dich ”der Launcher”? Worauf willst Du überhaupt warten? Nach einem `Popen()` ist das Programm gestartet, da musst Du nicht drauf warten.

Die Prozesse ”senden” auch kein `None`. Was Du mit `poll()` abfragst ist der Rückgabecode des Prozesses. Und solange der noch läuft bekommst Du halt `None` von diesem Aufruf zurück weil es noch keinen Rückgabecode gibt, denn der steht ja erst am Ende des Prozesses fest. Wenn Du von `poll()` etwas anderes als `None` zurück bekommst dann gibt es den Prozess nicht mehr. Es macht also auch keinen Sinn danach noch weiter `poll()` aufzurufen, weil das natürlich immer den selben Wert liefert, eben den Rückgabecode den der Prozess am Ende per `exit()` an das Betriebssystem zurückgegeben hat.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@mutetella:
Die exec*-Calls geben Dir überhaupt nie was zurück, wie auch - exec ersetzt den aktuellen Prozess mit dem neuen, d.h. alles was nach exec* in Deinem Skript kommt, wird nie erreicht.

Warum muss die Auswahl eigentlich in einem neuem Terminal laufen und das alte geschlossen werden? Warum nicht einfach im selben Terminal bleiben? Da kannst Du prima mit exec Prozesshopping betreiben.

Wie stellst Du überhaupt sicher, dass das Terminal mit Skriptende auch mit geschlossen wird? Zwar verhalten sich die meisten Emulatoren unter X so, das OSX Terminal z.B. aber nicht - dort bleibt das Terminalfenster mit einer letzten Statusmeldung offen. Um hier sicher zu gehen, müsstest Du das Terminal selbst abschiessen.

@BlackJack:
In mutetellas Ansinnen müsste man tatsächlich mindestens solange warten, bis der Subprozess sich detached hat, um nicht als noch Kindprozess mit dem Terminal mit beendet zu werden.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@mutetella: so langsam verstehe ich, was Du zu erreichen versuchst.
Du startest Deinen Launcher über `xterm -e launcher`. Dann willst Du eine Auswahl treffen, ein neues Programm soll gestartet werden und das alte xterm-Fenster geschlossen werden. Dazu mußt Du wissen, dass alle Kinder und Kindeskinder eines Prozesses das Signal SIGHUP gesendet bekommen, wenn ein xterm-Fenster geschlossen wird. Das beendet normalerweise den Prozess, außer man ignoriert das Signal.

Code: Alles auswählen

import os
import signal

items = [
    ('ranger', ('xterm', '-e', 'ranger')),
    ('iceweasel', ('iceweasel',))
]
 
def start():
    for idx, item in enumerate(items):
        print '{:2d} {}'.format(idx, item[0])
    choice = int(raw_input('> '))
    app = items[choice][1]
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    pid = os.fork()
    if pid == 0:
        os.execvp(app[0], app)
 
if __name__ == '__main__':
    start()
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:Die Prozesse ”senden” auch kein `None`. Was Du mit `poll()` abfragst ist der Rückgabecode des Prozesses.
Das ist mir schon klar. Nur ist es halt so, dass der Kindprozess erst gar nicht zum Starten kommt, wenn der Elternprozess zu früh beendet wird. Wenn ich also ohne zeitliche Verzögerung ``p = subprocess.Popen(wasauchimmer); sys.exit()`` mache, dann startet `wasauchimmer` erst gar nicht, weil der Elternprozess zuvor schon wieder geschlossen wird. Ich dachte eben, dass es evtl. eine Möglichkeit gibt, `p` abzufragen, ob das zugrundeliegende Programm bereits gestartet ist.
jerch hat geschrieben:Warum muss die Auswahl eigentlich in einem neuem Terminal laufen und das alte geschlossen werden?
Ich verwende i3 als Fenstermanager. Das Launcherskript möchte ich in einem floating window starten, die daraus gestarteten Programme aber nicht.
jerch hat geschrieben:Wie stellst Du überhaupt sicher, dass das Terminal mit Skriptende auch mit geschlossen wird?
Den Job übernimmt X für mich. Und OSX interessiert mich nicht... :wink:

@Sirius3
Sind meine Kommentare richtig?

Code: Alles auswählen

    # if SIGHUP occurs, ignore it
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    # create new child process
    pid = os.fork()
    # start app within child if creation not fails
    if pid == 0:
        os.execvp(app[0], app)
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Der ' if creation not fails'-Zusatz ist falsch. Ob die Erzeugung geklappt hat wird da nicht geprüft, sondern in welchem der beiden Prozesse man sich befindet.
Antworten