Unterprozess, der das Verzeichnis ändert

92

Ich möchte ein Skript in einem Unterverzeichnis / Superverzeichnis ausführen (ich muss mich zuerst in diesem Unterverzeichnis / Superverzeichnis befinden). Ich kann subprocessmein Unterverzeichnis nicht betreten:

tducin@localhost:~/Projekty/tests/ve$ python
Python 2.7.4 (default, Sep 26 2013, 03:20:26) 
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> import os
>>> os.getcwd()
'/home/tducin/Projekty/tests/ve'
>>> subprocess.call(['cd ..'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 524, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/usr/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1308, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

Python wirft OSError und ich weiß nicht warum. Es spielt keine Rolle, ob ich versuche, in ein vorhandenes Unterverzeichnis oder ein Verzeichnis nach oben zu wechseln (wie oben) - ich habe immer den gleichen Fehler.

Ducin
quelle
1
Was passiert, wenn os.chdir()stattdessen verwendet wird ?
Greole

Antworten:

143

Ihr Code versucht, ein Programm mit dem Namen aufzurufen cd ... Sie möchten einen Befehl mit dem Namen aufrufen cd.

Ist cdaber eine Shell intern. Sie können es also nur als nennen

subprocess.call('cd ..', shell=True) # pointless code! See text below.

Aber es ist sinnlos, dies zu tun. Da kein Prozess das Arbeitsverzeichnis eines anderen Prozesses ändern kann (zumindest unter einem UNIX-ähnlichen Betriebssystem, aber auch unter Windows), ändert die Subshell bei diesem Aufruf ihr Verzeichnis und wird sofort beendet.

Was Sie wollen, können Sie mit os.chdir()oder mit dem subprocessgenannten Parameter erreichen, cwdder das Arbeitsverzeichnis unmittelbar vor der Ausführung eines Unterprozesses ändert.

Sie können dies beispielsweise lsim Stammverzeichnis ausführen

wd = os.getcwd()
os.chdir("/")
subprocess.Popen("ls")
os.chdir(wd)

oder einfach

subprocess.Popen("ls", cwd="/")
glglgl
quelle
1
cdexistiert normalerweise auch als Binärdatei, nicht nur als eingebaute Shell. Das eigentliche Problem des OP war, dass er eine Binärdatei aufrief cd .., ja. (Und Ihr dritter Absatz wäre sein nächstes Problem gewesen, also gute Antwort.)
Leon Weber
@LeonWeber Wie sollte cdes möglich sein, als Binärdatei zu arbeiten? Es kann das Arbeitsverzeichnis seiner Eltern nicht chanten.
glglgl
2
Ich habe über Linux gesprochen. Guter Punkt. Ich habe mich gefragt, und hier ist die Antwort: /usr/bin/cdbesteht aus builtin cd "$@"- also nennt es auch nur die eingebaute Shell cd.
Leon Weber
1
@The_Diver Deshalb cdmuss als interner Shell-Befehl implementiert werden. Es gibt keinen anderen Weg, es zu tun. Interne Shell-Befehle werden im selben Prozess wie die Shell ausgeführt. Was ich mit Subshell gemeint habe, ist die Shell, für die ausgeführt wurde shell=True. Es erhält den auszuführenden Befehl, führt ihn aus und beendet ihn.
glglgl
1
Ich denke, ein oder zwei Beispiele Ihres vorgeschlagenen Ansatzes wären nützlich.
sscirrus
55

Um your_commandals Unterprozess in einem anderen Verzeichnis ausgeführt zu werden, übergeben Sie den cwdParameter, wie in der Antwort von @ wim vorgeschlagen :

import subprocess

subprocess.check_call(['your_command', 'arg 1', 'arg 2'], cwd=working_dir)

Ein untergeordneter Prozess kann das Arbeitsverzeichnis seines übergeordneten Elements ( normalerweise ) nicht ändern . Das Ausführen cd ..in einem untergeordneten Shell-Prozess unter Verwendung eines Unterprozesses ändert das Arbeitsverzeichnis Ihres übergeordneten Python-Skripts nicht, dh das Codebeispiel in der Antwort von @ glglgl ist falsch . cdIst eine Shell eingebaut (keine separate ausführbare Datei), kann sie das Verzeichnis nur im selben Prozess ändern .

jfs
quelle
24

Sie möchten einen absoluten Pfad zur ausführbaren Datei verwenden und das cwdArbeitsverzeichnis mit kwarg von Popenfestlegen. Siehe die Dokumente .

Wenn cwd nicht None ist, wird das aktuelle Verzeichnis des Kindes vor der Ausführung in cwd geändert. Beachten Sie, dass dieses Verzeichnis bei der Suche in der ausführbaren Datei nicht berücksichtigt wird. Sie können daher den Pfad des Programms nicht relativ zu cwd angeben.

wim
quelle
Dies hängt davon ab, ob ein anderer Unterprozess ausgeführt werden soll. Wenn ja, ist Ihr Weg der richtige. Aber wenn nur das eigene Programm in einem anderen Verzeichnis agiert, hilft das nichts.
glglgl
Was meinst du damit, dass es nicht hilft? Dies ist der naheliegendste Weg, dies zu tun.
wim
1
Nein, da es nur die cwd des Prozesses ändert, den ich starten werde, wie z subprocess.call(['ls', '-l'], cwd='/'). Dadurch ändert sich die cwd auf /und läuft dann lsmit -lals Argument. Aber wenn ich tun möchte , os.chdir('/')und dann open('etc/fstab', 'r')kann ich nicht ersetzen os.chdir()mit etwas über , subprocess.XXX(cwd='/')da es nicht helfen, sagte wie. Dies sind zwei völlig unterschiedliche Szenarien.
glglgl
Deshalb heißt es in meiner Antwort, einen absoluten Pfad zur ausführbaren Datei zu verwenden. Haben Sie diesen Teil verpasst?
wim
2
Nein, habe ich nicht. Ich glaube ich gebe auf. Wenn ich das aktuelle Arbeitsverzeichnis ändern und eine Datei öffnen möchte, habe ich keine ausführbare Datei. Es ist eine ganz andere Situation. Übrigens: Es ist nicht erforderlich, einen absoluten Pfad zu verwenden, wenn ich ihn cwd=bestimmungsgemäß verwende. Das kann ich auch subprocess.call(['bin/ls', '-l'], cwd='/').
glglgl
17

subprocess.callund andere Methoden im subprocessModul haben einen cwdParameter.

Dieser Parameter bestimmt das Arbeitsverzeichnis, in dem Sie Ihren Prozess ausführen möchten.

Sie können also so etwas tun:

subprocess.call('ls', shell=True, cwd='path/to/wanted/dir/')

Überprüfen Sie den docs subprocess.popen-Konstruktor

l__flex__l
quelle
7

Eine weitere Option, die auf dieser Antwort basiert: https://stackoverflow.com/a/29269316/451710

Auf diese Weise können Sie mehrere Befehle (z. B. cd) im selben Prozess ausführen .

import subprocess

commands = '''
pwd
cd some-directory
pwd
cd another-directory
pwd
'''

process = subprocess.Popen('/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = process.communicate(commands.encode('utf-8'))
print(out.decode('utf-8'))
Eyal Levin
quelle
1
Dies ist nur ein Kreisverkehr und ineffiziente Möglichkeitshell=True, executable='/bin/bash'
Tripleee
2

Ich denke in diesen Tagen würden Sie tun:

import subprocess

subprocess.run(["pwd"], cwd="sub-dir")
Francois
quelle
0

Wenn Sie über CD-Funktionalität verfügen möchten (vorausgesetzt, Shell = True) und dennoch das Verzeichnis in Bezug auf das Python-Skript ändern möchten, können mit diesem Code 'cd'-Befehle ausgeführt werden.

import subprocess
import os

def cd(cmd):
    #cmd is expected to be something like "cd [place]"
    cmd = cmd + " && pwd" # add the pwd command to run after, this will get our directory after running cd
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) # run our new command
    out = p.stdout.read()
    err = p.stderr.read()
    # read our output
    if out != "":
        print(out)
        os.chdir(out[0:len(out) - 1]) # if we did get a directory, go to there while ignoring the newline 
    if err != "":
        print(err) # if that directory doesn't exist, bash/sh/whatever env will complain for us, so we can just use that
    return
Taktischer Smoking
quelle
0

Wenn Sie das Verzeichnis wechseln müssen, führen Sie einen Befehl aus und rufen Sie auch die Standardausgabe ab:

import os
import logging as log
from subprocess import check_output, CalledProcessError, STDOUT
log.basicConfig(level=log.DEBUG)

def cmd_std_output(cd_dir_path, cmd):
    cmd_to_list = cmd.split(" ")
    try:
        if cd_dir_path:
            os.chdir(os.path.abspath(cd_dir_path))
        output = check_output(cmd_to_list, stderr=STDOUT).decode()
        return output
    except CalledProcessError as e:
        log.error('e: {}'.format(e))
def get_last_commit_cc_cluster():
    cd_dir_path = "/repos/cc_manager/cc_cluster"
    cmd = "git log --name-status HEAD^..HEAD --date=iso"
    result = cmd_std_output(cd_dir_path, cmd)
    return result

log.debug("Output: {}".format(get_last_commit_cc_cluster()))
Output: "commit 3b3daaaaaaaa2bb0fc4f1953af149fa3921e\nAuthor: user1<[email protected]>\nDate:   2020-04-23 09:58:49 +0200\n\n
jturi
quelle