Python subprocess.check_call() 无法识别 pushd 和 popd

Ray*_*Ray 1 python bash subprocess python-3.x ubuntu-15.04

我在 Ubuntu 15.04(显然不是选择)和 Python 3.4.3 上,我正在尝试执行如下内容。

subprocess.check_call("pushd /tmp", shell=True)
Run Code Online (Sandbox Code Playgroud)

我需要 ,shell=True因为我尝试执行的实际代码包含需要解释的通配符。但是,这给了我以下错误。

/usr/lib/python3.4/subprocess.py in check_call(*popenargs, **kwargs)
    559         if cmd is None:
    560             cmd = popenargs[0]
--> 561         raise CalledProcessError(retcode, cmd)
    562     return 0
    563 

CalledProcessError: Command 'pushd /tmp' returned non-zero exit status 127
Run Code Online (Sandbox Code Playgroud)

我试过在我的 Mac(El Capitan 和 Python 3.5.1)上做同样的事情,它工作得很好。我还尝试subprocess.check_call("ls", shell=True)使用 Python 3.4.3(用于完整性检查)在 Ubuntu 15.04 上执行,并且运行良好。作为最后的健全性检查,我pushd /tmp && popd在 Ubuntu 15.04 上的 Bash 中尝试了该命令,它也运行良好。所以不知何故,在(我的)Ubuntu 15.04 和 Python 3.4.3 上,subprocess.check_call()无法识别pushdpopd!为什么?

Bak*_*riu 6

您的代码有两个问题。第一个是默认使用的shell/bin/sh不支持pushdpopd。在您的问题中,您未能提供整个错误输出,在其顶部您应该看到以下行:

/bin/sh: 1: popd: not found
Run Code Online (Sandbox Code Playgroud)

下次记得发布整个错误消息,而不仅仅是(错误地)认为相关的部分。

您可以subprocess通过executable参数告诉模块使用哪个 shell 来解决这个问题:

>>> subprocess.check_call('pushd ~', shell=True, executable='/bin/bash')
~ ~
0
Run Code Online (Sandbox Code Playgroud)

第二个问题是,即使这样,如果您使用多个check_call调用,您也会收到错误:

>>> subprocess.check_call('pushd ~', shell=True, executable='/bin/bash')
~ ~
0
>>> subprocess.check_call('popd', shell=True, executable='/bin/bash')
/bin/bash: riga 0: popd: stack delle directory vuoto
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/subprocess.py", line 581, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'popd' returned non-zero exit status 1
Run Code Online (Sandbox Code Playgroud)

这是因为每次调用都会check_call启动一个新的子shell,因此您之前是否调用过并不重要,pushd因为目录堆栈将始终为空。

请注意,如果您尝试组合pushdpopd在单个调用中,它们确实可以工作:

>>> subprocess.check_call('pushd ~ && popd', shell=True, executable='/bin/bash')
~ ~
~
0
Run Code Online (Sandbox Code Playgroud)

现在的事实是,如果您想从 python 中以这种方式使用pushdpopd...它们是无用的。那是因为您可以通过cwd参数指定当前工作目录,因此您可以从 python 跟踪工作目录堆栈,而不必依赖pushdpopd

current_working_dirs = []

def pushd(dir):
    current_working_dirs.append(os.path.realpath(os.path.expanduser(dir)))

def popd():
    current_working_dirs.pop()


def run_command(cmdline, **kwargs):
    return subprocess.check_call(cmdline, cwd=current_working_dirs[-1], **kwargs)
Run Code Online (Sandbox Code Playgroud)

更换check_call('pushd xxx')pushd('xxx')check_call('popd')popd和使用run_command(...)代替check_call(...)


正如您所建议的,更优雅的解决方案是使用上下文管理器:

class Pwd:
    dir_stack = []

    def __init__(self, dirname):
        self.dirname = os.path.realpath(os.path.expanduser(self.dirname))

    def __enter__(self):
        Pwd.dir_stack.append(self.dirname)
        return self

    def __exit__(self,  type, value, traceback):
        Pwd.dir_stack.pop()

    def run(self, cmdline, **kwargs):
        return subprocess.check_call(cmdline, cwd=Pwd.dir_stack[-1], **kwargs)
Run Code Online (Sandbox Code Playgroud)

用作:

with Pwd('~') as shell:
    shell.run(command)
    with Pwd('/other/directory') as shell:
        shell.run(command2)   # runs in '/other/directory'
    shell.run(command3)       # runs in '~'
Run Code Online (Sandbox Code Playgroud)