Jon*_*ley 2 python bash shell subprocess
我编写了一个小*nix实用程序,每次检测到文件系统更改时都会'重新运行'给定的命令.所以我将命令作为引用参数运行,例如
rerun "my command"
Run Code Online (Sandbox Code Playgroud)
重新运行是用Python编写的,最后调用:
subprocess.call("my command", shell=True, executable=USERS_DEFAULT_SHELL)
Run Code Online (Sandbox Code Playgroud)
在我的情况下,我的默认shell是'/ bin/bash'.但是,subprocess.call调用的shell不是"交互式"shell,因此无法识别我的.bashrc中定义的shell函数和别名.
man bash告诉我,为了启动交互式shell,我将'-i'传递给/ bin/bash.但是,可以预见,
subprocess.call(..., executable='/bin/bash -i')
Run Code Online (Sandbox Code Playgroud)
不起作用 - 它无法找到该名称的可执行文件.(即使它确实有效,我也试图为用户默认的shell设置此功能,而不仅仅是Bash.可能'-i'对所有其他shell都没有做同样的事情.)
我怎样才能从Python中执行"我的命令",就像用户在终端中键入它时所解释的一样?
Posix需要-ishell选项以"交互模式"启动shell.交互模式的精确定义因shell而异 - 显然zsh并csh不会尝试解释命令- 但使用标志应该使用所有合理的shell做正确的事..bashrc-i
通常,您可以通过使用列表调用subprocess.call(或某些Popen变体)来传递参数:
subprocess.call(['bash', '-i'])
Run Code Online (Sandbox Code Playgroud)
当然,这不会尊重用户的shell偏好.您应该能够从SHELL环境变量中获取:
subprocess.call([os.getenv('SHELL'), '-i'])
Run Code Online (Sandbox Code Playgroud)
为了让shell执行特定的命令行,您需要使用-c命令行选项,它也是Posix标准,因此它应该适用于所有shell:
subprocess.call([os.getenv('SHELL'), '-i', '-c', command_to_run])
Run Code Online (Sandbox Code Playgroud)
这在许多情况下都可以正常工作,但是如果shell决定exec使用最后一个(或唯一的)命令,它可能会失败command_to_run(有关详细信息,请参阅http://unix.stackexchange.com上的此答案.)然后您尝试调用另一个shell来执行另一个命令.
例如,考虑一下简单的python程序:
import subprocess
subprocess.call(['/bin/bash', '-i', '-c', 'ls'])
subprocess.call(['/bin/bash', '-i', '-c', 'echo second']);
Run Code Online (Sandbox Code Playgroud)
第一个bash过程开始了.由于它是交互式shell,因此它会创建一个新的进程组并将终端附加到该进程组.然后它检查要运行的命令,确定它是一个运行外部实用程序的简单命令,因此它可以执行exec该命令.所以它就是这样做的,用ls实用程序代替它,它现在是终端进程组的领导者.当ls终止时,终端处理组变为空的,但端子仍连接到它.因此,当第二个bash进程启动时,它会尝试创建一个新进程组并将终端连接到该进程组,但这是不可能的,因为终端处于一种不确定状态.根据Posix标准(基本定义,§11.1.2):
当不再有进程ID或进程组ID与前台进程组ID匹配的进程时,终端不应具有前台进程组.当进程ID与前台进程组ID匹配但进程组ID不匹配时,终端是否具有前台进程组是未指定的.除了分配控制终端或成功调用之外,POSIX.1-2008中定义的任何动作都不
tcsetpgrp()应使进程组成为终端的前台进程组.
使用bash,只有当作为-c参数值传递的字符串是一个简单命令时才会发生这种情况,所以有一个简单的解决方法:确保字符串不是一个简单的命令.例如,
subprocess.call([os.getenv('SHELL'), '-i', '-c', ':;' + command_to_run])
Run Code Online (Sandbox Code Playgroud)
它为命令添加了一个no-op,使其成为一个复合命令.但是,这不适用于在尾部调用优化中更具攻击性的其他shell.因此,一般解决方案需要遵循Posix建议的路径,同时还要注意tcsetpgrp系统调用的描述:
尝试
tcsetpgrp()从与其控制终端相关联的fildes上的后台进程组成员的进程中使用将导致向进程组发送SIGTTOU信号.如果调用线程阻塞SIGTTOU信号或进程忽略SIGTTOU信号,则应允许进程执行操作,并且不发送信号.
由于SIGTTOU信号的默认操作是停止进程,我们需要忽略或阻止信号.所以我们最终得到以下结果:
!/usr/bin/python
import signal
import subprocess
import os
# Ignore SIGTTOU
signal.signal(signal.SIGTTOU, signal.SIG_IGN)
def run_command_in_shell(cmd):
# Run the command
subprocess.call([os.getenv('SHELL'), '-i', '-c', cmd])
# Retrieve the terminal
os.tcsetpgrp(0,os.getpgrp())
run_command_in_shell('ls')
run_command_in_shell('ls')
Run Code Online (Sandbox Code Playgroud)