如何等待程序完成,并在完成后将 stdin 通过管道传输到 stdout?

如何等待程序完成,并在完成后将 stdin 通过管道传输到 stdout?

我试图弄清楚如何等待命令完成,然后将标准输入传输到标准输出。我使用的是 Mac,但我认为我的问题更多是关于如何等待进程完成并通过管道传输输出,然后是与 Mac 相关的任何事情。

我注意到,在 Mac 上,我可以一起运行几个命令,如果我使用运算符将​​它们连接起来say,它们会等待每个短语完全说出后再开始下一个短语。&&

$ say "stage 1" && say "stage 2"

这是真正的用例 - 我有一个 bash 脚本,我希望它只是将 stdin 传递到 stdout,它说完了。

$ cat /etc/passwd | say_and_pass "stage 1" | grep -v test | say_and_pass "stage 2"

所以从概念上讲,这会大声说“阶段 1”,然后立即说“阶段 2”,然后将 grep 的内容转储/etc/password到标准输出。

我对脚本的初步破解say_and_pass是这样的:

说并通过

#!/usr/bin/env bash
OUT="$*"
say "$OUT" && cat 

但它似乎不起作用;-)

编辑:我更改了上面的示例以用作say_and_pass "stage2"最终命令,这是需要的我的解决方案上班...

答案1

这就是真正的用例 - 我有一个 bash 脚本,我希望它在完成某些操作后将 stdin 传递到 stdout。

$ cat /etc/password | say_and_pass "stage 1" | grep -v test | say "stage 2"

您在这里尝试执行的操作的问题是您似乎有两个数据流 - 一个用于文本say处理,另一个用于文本处理(即catgrep)。

您不能为此使用单个管道,因为它只会混合数据。 cat /etc/passwd | say将使您的计算机尝试朗读该文件的全部内容。此外,say不写任何事物到标准输出,因此管道中不会再有任何内容。

如果您确实想“中断”数据处理流以处理到其他实用程序(例如 )的单独输出say,则需要为您的一个或另一个数据流设置 FIFO(即“命名管道”),或者使用写入磁盘的临时文件。

您的演示用例对于示例来说并不是很有帮助,因为尽管有says,您只是grepping 一个文件,这可以通过grep -v test /etc/passwd( cat file | grep patternis a无用的使用cat)。

综上所述,这是一个使用临时文件的示例:

scratch=$(mktemp)
trap "rm -f $scratch" EXIT
cat /etc/passwd > $scratch
say "Stage one"
grep -v test $scratch
say "Stage two"

其中一个人使用命名管道来say

mkfifo youtalktoomuch
say youtalktoomuch &
exec 3> youtalktoomuch
do_thing_one
echo "Stage one" > youtalktoomuch
do_thing_two
echo "Stage two" > youtalktoomuch
exec 3>&-
rm youtalktoomuch

Once again, though, if you need to connect the output of `do_thing_one` to the input of `do_thing_two` without piping one into the other, you will need to use either another named pipe or a scratch file on disk to hold the data.

答案2

这个版本的say_and_pass功能符合OP的要求:

#!/usr/bin/env bash

# Create a temporary file to store stdin
TEMP_STDIN="$(mktemp)"

# Capture stdin and save it to temporary file
cat - > "${TEMP_STDIN}"

# say message, but prevent any output to stdout
say "$@" > /dev/null

# Dump saved stdin to stdout
cat "${TEMP_STDIN}"

# Clean up, but prevent any output to stdout
rm -f "${TEMP_STDIN}" > /dev/null

它使用一个临时文件,就像 @DopeGhoti 建议的那样,但将任何其他逻辑(例如grep)保留在脚本之外。这使得它say_and_pass更加通用和可重用。

我对目标/目的的解释是,我们需要一种可听的方式来监控可能很长的管道的进度。

所以运行时:

cmd1 | say_and_pass "stage 1" | cmd2 | say_and_pass "stage 2"

作为用户,您可以通过声音知道已经cmd1完全完成其执行;权衡是在完成cmd2之前无法开始处理其标准输入。cmd1但OP似乎意识到了这种权衡并愿意做出这种权衡。

这与 @BradParks 的答案类似,但不受 bash 可用内存的限制(正如 @agc 指出的那样),并且在避免意外污染 stdout 方面稍微更加小心。

另请注意,无需将消息捕获到其自己的变量中,因此我们也只需转发"$@"say.

答案3

事实证明,我所要做的就是在脚本开始时消耗所有标准输入...我不希望它在接收到标准输入时处理它,而是当管道链中的整个前一个命令完成时。

所以我的say_and_pass脚本就是这样,它适用于我的用例!

#!/usr/bin/env bash

OUT=$(cat -)

MSG="$*"
say "$MSG"

echo "$OUT"

答案4

H!

我建议,根据您自己的答案,将标准输入转储到标准输出,删除将其存储到变量的部分。这应该减少内存消耗,并使管道链按照​​管道应有的方式工作(一行输入,它被处理,并将结果发送到下一个进程)。

#!/usr/bin/env bash

MSG="$*"
say "$MSG"

cat -

相关内容