【问题标题】:Equivalent of set -o pipefail in Python?相当于 Python 中的 set -o pipefail?
【发布时间】:2014-02-12 23:58:33
【问题描述】:

我有一些 Python 脚本,每个脚本都大量使用排序、uniq-ing、计数、gzipping 和 gunzipping 以及 awking。作为第一次运行我使用subprocess.callshell=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 中实现这种代码的替代方法。但是当我尝试使用Popenstdout=PIPE 时,代码大小会爆炸。在一行上将管道编写为字符串有一些好处,但是如果有人知道在 Python 中使用优雅的多行“正确且安全”的方式来执行此操作,我很想听听!

顺便说一句:这些脚本都不接受用户输入;他们在具有已知外壳的机器上运行批处理作业,这就是为什么我实际上冒险进入邪恶的shell=True 只是为了看看事情会如何。而且它们看起来确实很容易阅读,而且代码看起来如此简洁!如何删除 shell=True 并在原始 Python 中运行这些长管道,同时在早期组件失败时仍能获得中止进程的优势?

【问题讨论】:

  • 为什么不创建一个 bash 脚本来执行您想要的操作,然后从 Python 调用该脚本?然后你可以更好地控制整个管道。
  • 或者更好的是,要么只制作一个纯 Bash 脚本,要么将所有 shell 外部调用转换为原生 Python
  • 啊,对do 的调用只是更大的 Python 脚本的一部分。它们中的逻辑太多(围绕子进程调用)无法使用 bash,这对管道非常有用,但在处理数组和条件逻辑时效果不佳。

标签: python bash shell pipe


【解决方案1】:

您可以在系统调用中设置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 ])

【讨论】:

  • 谢谢,很好。我最终使用了call([ '/bin/bash', '-o', 'pipefail', '-c', command ])
  • 不过,这个想法完全基于您的回答。 :) 我从没想过调用 bash。现在很明显了。
  • 这个答案完全违背了 shell=False 提供的安全性。在这种情况下,我宁愿使用 shell=True。
  • @DavidRissatoCruz 是的,OP 明确询问有关执行作为字符串提供的管道的问题。为此,您需要一个shell,可以是使用shell=True 获得的隐式shell,也可以是通过我的回答获得的显式shell。在这两种情况下,您都将执行一个字符串,因此您需要信任该字符串。但我看不出shell=True 在这里有什么帮助,除非你的意思是它使信任问题更加明显。
  • 我从来没有说你的解决方案不起作用,请不要这样想。然而,他问“在不使用 shell=True 的情况下在纯 Python 中实现这种代码的替代方案”......“正确和安全的方式”,我的观点是调用 bash -c 与使用 shell=True 一样不安全。从安全角度来看,两种解决方案完全相同。
猜你喜欢
  • 2013-07-19
  • 2014-05-29
  • 1970-01-01
  • 2011-04-28
  • 2011-05-16
  • 2017-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多