Каталог изменения подпроцесса

98

Я хочу выполнить сценарий внутри подкаталога / суперкаталога (сначала мне нужно быть внутри этого подкаталога / суперкаталога). Я не могу subprocessвойти в свой подкаталог:

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 выдает OSError, и я не знаю почему. Не имеет значения, пытаюсь ли я перейти в существующий подкаталог или подняться на один каталог вверх (как указано выше) - я всегда получаю одну и ту же ошибку.

Ducin
источник
1
Что будет, если использовать os.chdir()вместо.
greole

Ответы:

152

Ваш код пытается вызвать программу с именем cd ... Вам нужно вызвать команду с именем cd.

Но cdэто оболочка внутренняя. Так что вы можете называть это только как

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

Но делать это бессмысленно. Поскольку ни один процесс не может изменить рабочий каталог другого процесса (опять же, по крайней мере, в UNIX-подобной ОС, но также и в Windows), этот вызов заставит подоболочку изменить свой каталог и немедленно выйти.

То, что вы хотите, может быть достигнуто с помощью os.chdir()или с помощью subprocessуказанного параметра, cwdкоторый изменяет рабочий каталог непосредственно перед выполнением подпроцесса.

Например, для выполнения lsв корневом каталоге вы можете сделать

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

или просто

subprocess.Popen("ls", cwd="/")
glglgl
источник
1
cdобычно также существует как двоичный файл, а не только как встроенная оболочка. Настоящая проблема OP заключалась в том, что он вызывал двоичный файл cd .., да. (И ваш третий абзац был бы его следующей проблемой, так что хороший ответ.)
Леон Вебер
@LeonWeber Как cdуметь работать как бинарник? Он не может изменять рабочий каталог своего родителя.
glglgl
2
Я говорил о Linux. Но хороший момент. Мне самому было интересно, и вот ответ: /usr/bin/cdсостоит из builtin cd "$@"- поэтому он также просто вызывает встроенную оболочку cd.
Леон Вебер
1
@The_Diver Вот почему cdдолжна быть реализована как внутренняя команда оболочки. Другого пути нет. Команды внутренней оболочки выполняются в том же процессе, что и оболочка. Под подоболочкой я имел в виду оболочку, для которой выполняется shell=True. Он получает команду на выполнение, выполняет ее и завершает работу.
glglgl
1
Я думаю, что один или два примера из предложенного вами подхода были бы полезны.
sscirrus
57

Чтобы запустить your_commandкак подпроцесс в другом каталоге, передайте cwdпараметр, как предлагается в ответе @wim :

import subprocess

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

Дочерний процесс не может изменить рабочий каталог своего родителя ( обычно ). Запуск cd ..в дочернем процессе оболочки с использованием подпроцесса не изменит рабочий каталог вашего родительского скрипта Python, т.е. пример кода в ответе @ glglgl неверен . cdявляется встроенной оболочкой (а не отдельным исполняемым файлом), он может изменять каталог только в том же процессе.

jfs
источник
24

Вы хотите использовать абсолютный путь к исполняемому файлу и использовать cwdkwarg of Popenдля установки рабочего каталога. См. Документы .

Если cwd не равно None, текущий каталог дочернего элемента будет изменен на cwd перед его выполнением. Обратите внимание, что этот каталог не учитывается при поиске исполняемого файла, поэтому вы не можете указать путь к программе относительно cwd.

вим
источник
Это зависит от того, предполагается ли выполнение другого подпроцесса. Если так, то ваш путь правильный. Но из-за того, что собственная программа действует только в другом каталоге, это не поможет.
glglgl
Что значит не поможет? Это один из очевидных способов сделать это.
wim
1
Нет, поскольку он просто изменяет cwd процесса, который я собираюсь запустить, например subprocess.call(['ls', '-l'], cwd='/'). Это изменяет cwd на, /а затем запускается lsс -lаргументом as. Но если я хочу сделать , os.chdir('/')а потом open('etc/fstab', 'r'), я не могу заменить os.chdir()ни с чем о , subprocess.XXX(cwd='/')как это не поможет, как сказал. Это два совершенно разных сценария.
glglgl
Вот почему в моем ответе сказано использовать абсолютный путь к исполняемому файлу, вы пропустили эту часть?
wim
2
Нет, не знал. Думаю, я сдаюсь. Если я хочу изменить текущий рабочий каталог и открыть файл, у меня нет исполняемого файла. Совершенно другая ситуация. BTW: нет необходимости использовать абсолютный путь, если я использую cwd=по назначению. Я тоже могу subprocess.call(['bin/ls', '-l'], cwd='/').
glglgl
18

subprocess.callи другие методы в subprocessмодуле имеют cwdпараметр.

Этот параметр определяет рабочий каталог, в котором вы хотите выполнить свой процесс.

Итак, вы можете сделать что-то вроде этого:

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

Ознакомьтесь с документами subprocess.popen-constructor

l__flex__l
источник
7

Другой вариант, основанный на этом ответе: https://stackoverflow.com/a/29269316/451710

Это позволяет вам выполнять несколько команд (например cd) в одном процессе.

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
Это просто окольный и неэффективный способ сделать этоshell=True, executable='/bin/bash'
тройняшек
3

Думаю, в наши дни вы бы сделали:

import subprocess

subprocess.run(["pwd"], cwd="sub-dir")
Франсуа
источник
0

Если вы хотите иметь функциональность cd (при условии, что shell = True) и все же хотите изменить каталог с точки зрения сценария Python, этот код позволит работать командам cd.

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

Если вам нужно сменить каталог, запустите команду и также получите вывод 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
jturi
источник
Вы изобретаете заново check_call, плохо.
Tripleee