Subprocesso de diretório de mudança

94

Quero executar um script dentro de um subdiretório / superdiretório (preciso estar dentro deste sub / superdiretório primeiro). Não consigo subprocessentrar em meu subdiretório:

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 lança OSError e não sei por quê. Não importa se tento entrar em um subdiretório existente ou subir um diretório (como acima) - sempre acabo com o mesmo erro.

Ducin
fonte
1
O que acontece se usar em os.chdir()vez disso.
greole

Respostas:

145

O que seu código tenta fazer é chamar um programa chamado cd ... O que você quer é chamar um comando chamado cd.

Mas cdé um shell interno. Então você só pode chamá-lo de

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

Mas é inútil fazer isso. Como nenhum processo pode alterar o diretório de trabalho de outro processo (novamente, pelo menos em um sistema operacional semelhante ao UNIX, mas também no Windows), esta chamada fará com que o subshell altere seu diretório e saia imediatamente.

O que você deseja pode ser alcançado com os.chdir()ou com o subprocessparâmetro nomeado, cwdque altera o diretório de trabalho imediatamente antes de executar um subprocesso.

Por exemplo, para executar lsno diretório raiz, você pode fazer

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

ou simplesmente

subprocess.Popen("ls", cwd="/")
glglgl
fonte
1
cdgeralmente também existe como binário, não apenas como um shell embutido. O verdadeiro problema do OP era que ele estava chamando um binário cd .., sim. (E seu terceiro parágrafo teria sido seu próximo problema, então boa resposta.)
Leon Weber
@LeonWeber Como deve cdser capaz de trabalhar como binário? Ele não pode cantar o dir de trabalho de seu pai.
glglgl de
2
Eu estava falando sobre Linux. Bom ponto, entretanto. Eu estava me perguntando, e aqui está a resposta: /usr/bin/cdconsiste em builtin cd "$@"- portanto, também chama o shell embutido cd.
Leon Weber
1
@The_Diver É por isso que cddeve ser implementado como um comando shell interno. Não há outra maneira de fazer isso. Os comandos internos do shell são executados no mesmo processo do shell. O que quero dizer com subshell é o shell executado para shell=True. Ele pega o comando a ser executado, o executa e sai.
glglgl
1
Acho que um ou dois exemplos de sua abordagem sugerida seriam úteis.
sscirrus
55

Para executar your_commandcomo um subprocesso em um diretório diferente, passe o cwdparâmetro, conforme sugerido na resposta de @wim :

import subprocess

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

Um processo filho não pode alterar o diretório de trabalho de seu pai ( normalmente ). Executar cd ..em um processo shell filho usando subprocesso não mudará o diretório de trabalho do script pai Python, ou seja, o exemplo de código na resposta de @glglgl está errado . cdé um shell embutido (não um executável separado), ele pode alterar o diretório apenas no mesmo processo.

jfs
fonte
24

Você deseja usar um caminho absoluto para o executável e usar o cwdkwarg de Popenpara definir o diretório de trabalho. Veja a documentação .

Se cwd não for Nenhum, o diretório atual do filho será alterado para cwd antes de ser executado. Observe que este diretório não é considerado ao pesquisar o executável, portanto, você não pode especificar o caminho do programa relativo ao cwd.

wim
fonte
Depende se outro subprocesso deve ser executado. Se sim, seu caminho é o certo. Mas por apenas ter o próprio programa atuando em um diretório diferente, isso não ajudará.
glglgl de
O que quer dizer que não vai ajudar? Esta é a única maneira óbvia de fazer isso.
wim
1
Não, apenas muda o cwd do processo que vou lançar, como subprocess.call(['ls', '-l'], cwd='/'). Isso altera o cwd para /e, em seguida, executa lscom -lcomo argumento. Mas se eu quiser fazer os.chdir('/')e depois open('etc/fstab', 'r'), não posso substituir os.chdir()por nada, subprocess.XXX(cwd='/')pois não vai adiantar, como disse. Estes são dois cenários completamente diferentes.
glglgl de
É por isso que minha resposta diz para usar um caminho absoluto para o executável, você perdeu essa parte?
wim
2
Não, não disse. Acho que desisto. Se eu quiser mudar o diretório de trabalho atual e abrir um arquivo, não tenho um executável. É uma situação completamente diferente. BTW: Não há necessidade de usar um caminho absoluto se eu usar cwd=como pretendido. Eu também posso fazer subprocess.call(['bin/ls', '-l'], cwd='/').
glglgl de
17

subprocess.calle outros métodos no subprocessmódulo têm umcwd parâmetro.

Este parâmetro determina o diretório de trabalho onde você deseja executar seu processo.

Então você pode fazer algo assim:

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

Confira docs subprocess.popen-constructor

l__flex__l
fonte
7

Outra opção com base nesta resposta: https://stackoverflow.com/a/29269316/451710

Isso permite que você execute vários comandos (por exemplo cd) no mesmo 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'))
Eyal Levin
fonte
1
Esta é apenas uma forma rotatória e ineficiente de fazershell=True, executable='/bin/bash'
tripleee
2

Acho que hoje em dia você faria:

import subprocess

subprocess.run(["pwd"], cwd="sub-dir")
François
fonte
1

Se você precisar mudar de diretório, execute um comando e obtenha a saída std também:

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
fonte
0

Se você deseja ter a funcionalidade cd (assumindo shell = True) e ainda deseja alterar o diretório em termos do script Python, este código permitirá que os comandos 'cd' funcionem.

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
Tux Tático
fonte