虽然我仍然编写了大量的 shell,但我通常会尽量避免使用它。对于其他人来说,它很难阅读,有很多尖锐的边缘,容易吞下错误,并且不能很好地处理不寻常的情况。但让我回过头来的一件事是我可以轻松地建立流程树。
假设我有一个程序,可以一次性读取两个文件 [1] 并写出一些内容。您拥有的输入已被压缩,因此您需要对其进行解压缩,并且在将输出写入存储之前也需要对其进行压缩。你可以这样做:
# 下载文件 aws s3 cp "$path1" 。 aws s3 cp "$path2" 。 # 解压文件 枪压缩“$file1” 枪压缩“$file2” # 运行命令 cmd -1 "$file1" -2 "$file2" > "$fileOut" # 压缩输出 gzip“$fileOut” # 上传输出 aws s3 cp "$fileOut.gz" "$pathOut"
这可行,但对于大文件来说,速度很慢并且需要太多空间。我们等待每个步骤完成后再开始下一步,并且我们在本地计算机上存储一些非常大的中间文件。
相反,我们希望向下传输输入,边解压它们,边压缩输出,然后向上传输输出。在 bash 中,写起来相当简单:
cmd -1 <(aws s3 cp "$path1" - |gunzip) \ -2 <(aws s3 cp "$path2" - |gunzip) \ |压缩包 | aws s3 cp - “$pathOut”
这几乎不使用磁盘空间,并且并行解压缩、命令和重新压缩。不过也是壳子…
我倾向于使用 python 来做这种事情,我将东西粘合在一起并希望它清楚我在做什么。看起来应该可以使用subprocess模块来做这种事情,但是虽然我已经玩过几次了,但我还没有弄清楚。我想要一个像这样的 API:
管道 = subprocess.Pipeline() dl1 = 管道. 进程( [“aws”,“s3”,“cp”,路径1,“-”]) gunzip1 = 管道. 进程( [“gunzip”],stdin = dl1.stdout) dl2 = 管道. 进程( [“aws”,“s3”,“cp”,路径2,“-”]) gunzip2 = 管道. 进程( [“gunzip”],stdin = dl2.stdout) cmd = 管道. 进程( [“cmd”,“-1”,dl1.stdout, “-2”,dl2.stdout]) gzip = 管道. 进程( [“gzip”],stdin = cmd.stdout) 管道. 进程( [“aws”,“s3”,“cp”,“-”,pathOut], 标准输入=gzip.stdout) pipeline.check_call()
或者:
从子进程导入 check_call、PIPE、InputFile 检查调用([ “命令”, “-1”,输入文件([ “aws”,“s3”,“cp”,路径1,“-”, 管道,“gunzip”]), “-2”,输入文件([ “aws”,“s3”,“cp”,路径2,“-”, 管道,“gunzip”]), 管道,“gzip”, 管道,“aws”,“s3”,“cp”,“-”,pathOut])
它们的长度是 bash 版本的 5 倍和 3 倍,但我愿意忍受这一点,因为有一些更强大的东西。在实践中,差异也会更小,因为命令通常会有很多参数。
我看到这些堆栈溢出答案建议命名管道,但它们看起来很尴尬,难以阅读,而且很容易出错。有没有更好的办法?当 bash 非常适合做一些事情时,我是否应该坚持使用 bash,尤其是现在人们可以将我的代码粘贴到 LLM 中并获得其正在做什么的解释?
[1] 交错的fastq
文件,其中文件 1 中的第 N 条记录对应于文件 2 中的第 N 条记录。
原文: https://www.jefftk.com/p/process-substitution-without-shell