Sottoprocesso che cambia directory


98

Voglio eseguire uno script all'interno di una sottodirectory / superdirectory (devo prima essere all'interno di questa sotto / superdirectory). Non riesco subprocessa entrare nella mia sottodirectory:

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 lancia OSError e non so perché. Non importa se provo ad entrare in una sottodirectory esistente o a salire di una directory (come sopra) - finisco sempre con lo stesso errore.


1
Cosa succede se os.chdir()invece usi .
Greole

Risposte:


152

Quello che il tuo codice cerca di fare è chiamare un programma denominato cd ... Quello che vuoi è chiamare un comando denominato cd.

Ma cdè un guscio interno. Quindi puoi chiamarlo solo come

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

Ma è inutile farlo. Poiché nessun processo può cambiare la directory di lavoro di un altro processo (di nuovo, almeno su un sistema operativo simile a UNIX, ma anche su Windows), questa chiamata farà cambiare la directory alla subshell e uscirà immediatamente.

Quello che vuoi può essere ottenuto con os.chdir()o con il subprocessparametro denominato cwdche cambia la directory di lavoro immediatamente prima di eseguire un sottoprocesso.

Ad esempio, per eseguire lsnella directory root, puoi farlo

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

o semplicemente

subprocess.Popen("ls", cwd="/")

1
cddi solito esiste anche come binario, non solo come built-in di shell. Il vero problema dell'OP era che chiamava un binario cd .., sì. (E il tuo terzo paragrafo sarebbe stato il suo prossimo problema, quindi buona risposta.)
Leon Weber,

@LeonWeber Come dovrebbe cdessere in grado di funzionare come binario? Non può cantare la directory di lavoro dei suoi genitori.
glglgl

2
Stavo parlando di Linux. Buon punto però. Mi stavo chiedendo, ed ecco la risposta: /usr/bin/cdconsiste di builtin cd "$@"- quindi chiama anche il built-in della shell cd.
Leon Weber

1
@The_Diver Ecco perché cddeve essere implementato come comando della shell interna. Non c'è altro modo per farlo. I comandi della shell interna vengono eseguiti nello stesso processo della shell. Quello che intendevo per subshell è la shell eseguita per shell=True. Ottiene il comando da eseguire, lo esegue ed esce.
glglgl

1
Penso che un esempio o due del tuo approccio suggerito sarebbero utili.
sscirrus

57

Per eseguire your_commandcome sottoprocesso in una directory diversa, passare il cwdparametro, come suggerito nella risposta di @ wim :

import subprocess

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

Un processo figlio non può cambiare la directory di lavoro del suo genitore ( normalmente ). L'esecuzione cd ..in un processo di shell figlio utilizzando un sottoprocesso non cambierà la directory di lavoro dello script Python genitore, ovvero l'esempio di codice nella risposta di @ glglgl è sbagliato . cdè un builtin della shell (non un eseguibile separato), può cambiare la directory solo nello stesso processo.


24

Si desidera utilizzare un percorso assoluto per l'eseguibile e utilizzare cwdkwarg di Popenper impostare la directory di lavoro. Vedi i documenti .

Se cwd non è None, la directory corrente del bambino verrà cambiata in cwd prima di essere eseguita. Nota che questa directory non viene presa in considerazione durante la ricerca dell'eseguibile, quindi non puoi specificare il percorso del programma relativo a cwd.


Dipende da se deve essere eseguito un altro sottoprocesso. Se è così, la tua strada è quella giusta. Ma solo per avere il proprio programma che agisce all'interno di una directory diversa, ciò non aiuta.
glglgl

Cosa vuoi dire che non aiuterà? Questo è l'unico modo ovvio per farlo.
wim

1
No, poiché cambia solo il cwd del processo che sto per avviare, ad esempio subprocess.call(['ls', '-l'], cwd='/'). Questo cambia il cwd in /e quindi viene eseguito lscon -lcome argomento. Ma se voglio fare os.chdir('/')e poi open('etc/fstab', 'r'), non posso sostituire os.chdir()con nulla subprocess.XXX(cwd='/')in quanto non aiuta, come detto. Questi sono due scenari completamente diversi.
glglgl

Ecco perché la mia risposta dice di utilizzare un percorso assoluto per l'eseguibile, ti sei perso quella parte?
wim

2
No, non l'ho fatto. Penso di rinunciare. Se voglio cambiare la directory di lavoro corrente e aprire un file, non ho alcun eseguibile. È una situazione completamente diversa. BTW: non è necessario utilizzare un percorso assoluto se lo uso cwd=come previsto. Anch'io posso farlo subprocess.call(['bin/ls', '-l'], cwd='/').
glglgl

18

subprocess.calle altri metodi nel subprocessmodulo hanno un cwdparametro.

Questo parametro determina la directory di lavoro in cui si desidera eseguire il processo.

Quindi puoi fare qualcosa del genere:

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

Controlla la documentazione subprocess.popen-constructor


7

Un'altra opzione basata su questa risposta: https://stackoverflow.com/a/29269316/451710

Ciò consente di eseguire più comandi (ad esempio cd) nello stesso processo.

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'))

1
Questo è solo un modo shell=True, executable='/bin/bash'
tortuoso

3

Immagino che in questi giorni faresti:

import subprocess

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

0

Se vuoi avere la funzionalità cd (assumendo shell = True) e vuoi comunque cambiare la directory in termini di script Python, questo codice permetterà ai comandi 'cd' di funzionare.

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

-1

Se è necessario cambiare directory, eseguire un comando e ottenere anche l'output std:

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<user1@email.com>\nDate:   2020-04-23 09:58:49 +0200\n\n

Stai reinventando check_call, male.
tripleee
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.