我有一些Python脚本,每个脚本都大量使用排序,统一,计数,gzipping和gunzipping,以及awking。作为第一次运行代码,我使用subprocess.call
(是的,我知道安全风险,这就是为什么我说它是第一次通过)shell=True
。我有一个小帮手功能:
def do(command):
start = datetime.now()
return_code = call(command, shell=True)
print 'Completed in', str(datetime.now() - start), 'ms, return code =', return_code
if return_code != 0:
print 'Failure: aborting with return code %d' % return_code
sys.exit(return_code)
脚本使用此助手,如以下代码段所示:
do('gunzip -c %s | %s | sort -u | %s > %s' % (input, parse, flatten, output))
do("gunzip -c %s | grep 'en$' | cut -f1,2,4 -d\|| %s > %s" % (input, parse, output))
do('cat %s | %s | gzip -c > %s' % (input, dedupe, output))
do("awk -F ' ' '{print $%d,$%d}' %s | sort -u | %s | gzip -c > %s" % params)
do('gunzip -c %s | %s | gzip -c > %s' % (input, parse, output))
do('gunzip -c %s | %s > %s' % (input, parse, collection))
do('%s < %s >> %s' % (parse, supplement, collection))
do('cat %s %s | sort -k 2 | %s | gzip -c > %s' % (source,other_source,match,output)
还有更多像这样的,有些甚至更长的管道。
我注意到的一个问题是,当管道中的某个命令失败时,整个命令仍然会以退出状态0成功。在bash中我用
修复此问题set -o pipefail
但我不知道如何在Python中完成此操作。我想我可以明确地调用bash,但这似乎是错误的。是吗?
代替对该特定问题的回答,我很想听到在纯Python中实现这种代码的替代方法而不诉诸shell=True
。但是,当我尝试使用Popen
和stdout=PIPE
时,代码大小就会爆炸。将管道作为字符串写在一行上有一些好处,但如果有人知道优雅的多行“正确和安全”的方式在Python中这样做我很想听到它!
抛开:这些脚本都没有用户输入;他们在具有已知shell的机器上运行批处理作业,这就是为什么我真正冒险进入邪恶的shell=True
只是为了看看事情的样子。它们看起来很容易阅读,代码似乎所以简洁!如何在原始Python中删除shell=True
并运行这些长管道,同时如果早期组件失败,仍然可以获得中止流程的优势?
答案 0 :(得分:7)
您可以在系统调用中设置pipefail
:
def do(command):
start = datetime.now()
return_code = call([ '/bin/bash', '-c', 'set -o pipefail; ' + command ])
...
或者,正如@RayToal在评论中指出的那样,使用shell的-o
选项来设置此标志:call([ '/bin/bash', '-o', 'pipefail', '-c', command ])
。