【发布时间】:2014-02-12 23:58:33
【问题描述】:
我有一些 Python 脚本,每个脚本都大量使用排序、uniq-ing、计数、gzipping 和 gunzipping 以及 awking。作为第一次运行我使用subprocess.call 和shell=True 的代码(是的,我知道安全风险,这就是我说这是第一次通过的原因)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)
脚本在以下 sn-ps 中使用此帮助器:
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 ,但这似乎是错误的。是吗?
代替对这个特定问题的回答,我很想听到在不诉诸shell=True 的情况下在纯 Python 中实现这种代码的替代方法。但是当我尝试使用Popen 和stdout=PIPE 时,代码大小会爆炸。在一行上将管道编写为字符串有一些好处,但是如果有人知道在 Python 中使用优雅的多行“正确且安全”的方式来执行此操作,我很想听听!
顺便说一句:这些脚本都不接受用户输入;他们在具有已知外壳的机器上运行批处理作业,这就是为什么我实际上冒险进入邪恶的shell=True 只是为了看看事情会如何。而且它们看起来确实很容易阅读,而且代码看起来如此简洁!如何删除 shell=True 并在原始 Python 中运行这些长管道,同时在早期组件失败时仍能获得中止进程的优势?
【问题讨论】:
-
为什么不创建一个 bash 脚本来执行您想要的操作,然后从 Python 调用该脚本?然后你可以更好地控制整个管道。
-
或者更好的是,要么只制作一个纯 Bash 脚本,要么将所有 shell 外部调用转换为原生 Python
-
啊,对
do的调用只是更大的 Python 脚本的一部分。它们中的逻辑太多(围绕子进程调用)无法使用 bash,这对管道非常有用,但在处理数组和条件逻辑时效果不佳。