【问题标题】:Make a shell pipeline started from subprocess.Popen fail if the left-hand side of a pipe fails如果管道的左侧失败,则使从 subprocess.Popen 启动的 shell 管道失败
【发布时间】:2021-09-14 14:03:43
【问题描述】:

我在 python 中使用 subprocess.popen 运行 bash 命令:

cmd = "bwa-mem2/bwa-mem2 mem -R \'@RG\\tID:2064-01\\tSM:2064-01\\tLB:2064-01\\tPL:ILLUMINA\\tPU:2064-01\' reference_genome/human_g1k_v37.fasta BHYHT7CCXY.RJ-1967-987-02.2_1.fastq BHYHT7CCXY.RJ-1967-987-02.2_2.fastq -t 14 | samtools view -bS -o dna_seq/aligned/2064-01/2064-01.6.bam -"

process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

问题是即使第一个命令失败,我也会得到返回码 0。 我用谷歌搜索并发现了 pipefail ,似乎这是我应该使用的。

但是,我不明白在哪里写。我试过了:

"set -o pipefail && bwa-mem2/bwa-mem2 mem -R \'@RG\\tID:2064-01\\tSM:2064-01\\tLB:2064-01\\tPL:ILLUMINA\\tPU:2064-01\' reference_genome/human_g1k_v37.fasta BHYHT7CCXY.RJ-1967-987-02.2_1.fastq BHYHT7CCXY.RJ-1967-987-02.2_2.fastq -t 14 | samtools view -bS -o dna_seq/aligned/2064-01/2064-01.6.bam -"

给出:/bin/sh: 1: set: Illegal option -o pipefail

有什么想法我应该如何整合它?

编辑:

我不确定在回复答案时编辑我的答案是否正确?评论中没有足够的字符来回复:/

无论如何, 我在没有shell=True@Charles Duffy 的情况下尝试了您的第二种方法。

(cmd_1 和 cmd_2 等于你在解决方案中写的)

这是我使用的代码:

try:
        
   p1 = Popen(shlex.split(cmd_1), stdout=PIPE)
   p2 = Popen(shlex.split(cmd_2), stdin=p1.stdout, stdout=PIPE, stderr=STDOUT, text=True)
   p1.stdout.close()
   output, error = p2.communicate()
   p1.wait()    
    
   rc_1 = p1.poll()
   rc_2 = p2.poll()
   print("rc_1:", rc_1)
   print("rc_2:", rc_2)

   if rc_1 == 0 and rc_2 == 0:
       self.log_to_file("DEBUG", "# Process ended with returncode = 0")
       if text: self.log_to_file("INFO", f"{text} succesfully 
            
   else:
       print("Raise exception")
       raise Exception(f"stdout: {output} stderr: {error}")

except Exception as e:
    print(f"Error: {e} in misc.run_command()")
    self.log_to_file("ERROR", f"# Process ended with returncode != 0, {e}")

这是我通过重命名一个文件故意导致错误时得到的结果:

[E::main_mem] failed to open file `/home/jonas/BASE/dna_seq/reads/2064-01/test_BHYHT7CCXY.RJ-1967-987-02.2_2.fastq.gz'.
free(): double free detected in tcache 2
rc_1: -6
rc_2: 0
Raise exception
Error: stdout:  stderr: None in misc.run_command()
 ERROR: # Process ended with returncode != 0, stdout:  stderr: None

它似乎捕获了错误的返回码。

但是为什么stdout 是空的而stderr= None 呢? 如何在进程成功和失败时捕获输出以将其记录到记录器中?

【问题讨论】:

  • 您必须强制您的 shell 为 bash,而不是 sh,以便 pipefail 成为可用选项。
  • 或者,更好的是,完全停止使用任何 shell,只使用两个单独的 Popen 对象,一个用于管道的每一侧,两者都使用 shell=False
  • 顺便说一句,我不确定要求“正确”返回码的原始标题是否合适。有时,默认的 shell 行为 pipefail 行为更“正确”——例如,当 headcurl 之前停止读取时,想想 curl ... | head 如何具有非零退出状态完成写入,导致 EPIPE,因此即使一切顺利,管道的左侧也会出现失败的退出状态。
  • ofc 你是绝对正确的!谢谢:)

标签: linux bash shell subprocess


【解决方案1】:

首先,使用外壳

默认情况下,不要让shell=True 指定sh,而是明确指定bash 以确保pipefail 是可用的功能:

shell_script = r'''
set -o pipefail || exit
bwa-mem2/bwa-mem2 mem \
  -R '@RG\tID:2064-01\tSM:2064-01\tLB:2064-01\tPL:ILLUMINA\tPU:2064-01' \
  reference_genome/human_g1k_v37.fasta \
  BHYHT7CCXY.RJ-1967-987-02.2_1.fastq \
  BHYHT7CCXY.RJ-1967-987-02.2_2.fastq \
  -t 14 \
  | samtools view -bS \
    -o dna_seq/aligned/2064-01/2064-01.6.bam -
'''

process = subprocess.Popen(["bash", "-c", shell_script],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           text=True)

工作,但它不是最好的选择。


第二,完全没有外壳

p1 = subprocess.Popen(
  ['bwa-mem2/bwa-mem2', 'mem',
   '-R', r'@RG\tID:2064-01\tSM:2064-01\tLB:2064-01\tPL:ILLUMINA\tPU:2064-01',
   'reference_genome/human_g1k_v37.fasta',
   'BHYHT7CCXY.RJ-1967-987-02.2_1.fastq',
   'BHYHT7CCXY.RJ-1967-987-02.2_2.fastq', '-t', '14'],
  stdout=subprocess.PIPE)
p2 = subprocess.Popen(
  ['samtools', 'view', '-bS',
   '-o', 'dna_seq/aligned/2064-01/2064-01.6.bam', '-'],
  stdin=p1.stdout,
  stdout=subprocess.PIPE,
  stderr=subprocess.PIPE,
  text=True)
p1.stdout.close()
output, _ = p2.communicate() # let p2 finish running
p1.wait()                    # ensure p1 has properly exited

print(f'bwa-mem2 exited with status {p1.returncode}')
print(f'samtools exited with status {p2.returncode}')

...它可以让您分别检查p1.returncodep2.returncode

【讨论】:

  • 非常感谢您提供有用的答案!我会试试这个并返回我的结果!:)
  • btw...当您使用 p1.stdout 作为 p2 的标准输入时,它是如何工作的? p1 和 p2 是否同时运行,并且随着时间的推移 p1 将其输出转发到 p2?为什么使用 p1.stdout.close()?如果你不使用它会发生什么? .communicate() 是如何工作的?这是否与 popen 持续通信或仅在完成后一次通信?为什么是 p1.wait() 而不是 p2.wait?很抱歉有很多问题,但我真的很想了解所有这些:)
  • 是的,它们同时运行——这也是 shell 管道的工作方式!关闭 Python 进程的 p1.stdout 副本可确保一旦 p1 关闭其一侧,p2 就会看到 EOF,因为其他地方没有文件描述符的其他副本。
  • 相对于只需要调用p1.wait(),那是因为wait()隐含在communicate()中。如果我们只调用p2.communicate() 而不是p1.wait(),则存在风险,Python 将不会收到 p1 的退出状态,因此不会设置 returncode 属性。当一切正常时,如果解释器从操作系统获得SIGCHILD,通知它它开始退出的程序,但“显式优于隐式”在很多地方都存在,解释器也会填写它,这可以说是一个其中——信号传递并不总是 100% 可靠。
  • @biomedswe,直到刚才我才看到你编辑了这个问题。一般来说,如果是关于使答案无法使用的事情,那么后续行动在 cmets 中属于答案;或者在一个全新的问题中,如果你解决了你的第一个问题但有一个新的不同的问题。
猜你喜欢
  • 1970-01-01
  • 2019-03-18
  • 2016-10-05
  • 1970-01-01
  • 2022-07-22
  • 2015-10-12
  • 2019-05-20
  • 2018-03-30
  • 2022-10-05
相关资源
最近更新 更多