仅当另一个命令失败时,如何通过管道传输到一个命令(保留原始输入)?

仅当另一个命令失败时,如何通过管道传输到一个命令(保留原始输入)?

我希望这不是重复的,但我做了一些尽职调查试图找到并回答。仅当第一个命令失败时,我才想将原始输出通过管道传输到第二个命令。

例如,cat file.txt | command1 || command2command1 和command2 均从STDIN 读取。

我希望 command1 读取所有内容cat file.txt,但是,如果失败。我想运行 command2 中的所有输入。

编辑:我现在意识到我的问题有点模棱两可。我说 command1 和 command2 都从 STDIN 读取,但我的意思是我希望两者都从cat file.txt(不一定是特定命令)接收 STDOUT

我想我最初的愿望是将这样的东西放在别名中。 5个月后,我不记得这个问题是如何解决的。最有可能的是通过编写 bash 脚本。

答案1

欢迎来到堆栈交换!这里有很多好信息。首先感谢您的尽职调查。

我看到你的命令的问题是它是“回避问题”。换句话说,它假设您在开始之前就知道答案。

让我们仔细看看: cat file.txt
“file.txt”的内容将转到 STDOUT。
如果“cat”命令生成任何错误,它们将执行 STDERR。
命令完成后,将设置退出代码。 0 = 成功;其他任何内容都是错误。

这里的问题是,您不知道是否应该执行,command1或者command2直到输出已经发送为止。这意味着,它消失了。

与此相比:
cat file.txt | sort
这实际上是 2 个命令:
1: cat file.txt 1> temp_file
2:sort < temp_file
好吧,不是真正的 Unix 命令,这样做是为了澄清。
1:将 STDOUT 发送到“tempfile”(即 1> 部分)。还将 STDERR 发送到控制台。
2:运行排序命令,并从“tempfile”读取STDIN。
将管道放在|两个命令之间就不需要临时文件了。管道的含义是:将 STDOUT 从“cat”重定向到“sort”的 STDIN。

您的问题:
您想要将 STDOUT 重定向到另一个命令的 STDIN,但您还不知道那是哪个命令,因此无法重定向它。

在讨论可能的解决方案之前,让我先介绍一下背景知识:
在 Unix 中,有一种方法可以按照您想要的方式执行命令。以机智:
my_command argument1 argument2 && echo "Success" || echo "Failure"

my_command在控制台上使用 STDOUT 和 STDERR 运行(并返回其退出状态)后,运行其中一个或另一个 echo。
&&意思是“如果最后一个命令成功则执行此操作”
||意思是“如果最后一个命令失败则执行此操作”

您与原始命令非常接近。您有:
cat file.txt | command1 || command2
让我们将其更改为:
cat file.txt && command1 || command2
除了输出之外,这将为您提供您想要的一切。

问题 1:
如果cat file.txt有效,那么应该command1读取 STDOUT 并忽略 STDERR 吗?
同样,如果失败,那么应该command2读取 STDERR 并忽略 STDOUT 吗?
或者,任一命令应该同时读取 STDOUT 和 STDERR 吗?

为了完整起见,我将回答这两个问题。
无论哪种方式,您都需要一种方法来存储输出以供以后处理。通常是一个临时文件。

在同一个 temp_file 中捕获 STDOUT 和 STDERR:
cat file.txt > /tmp/temp_file 2>&1

将 STDOUT 捕获到 temp_file#1 并将 STDERR 捕获到 temp_file#2:
cat file.txt > /tmp/out_file 2 >/tmp/err_file

问题 2:
是否command1和/或command2允许在命令行上使用文件名?
command1 temp_file

是否command1和/或command2允许从文件重定向?
command2 < temp_file

执行command1和/或command2读取 STDIN?
cat temp_file | command1

注意:字数统计命令是一个示例。
wc temp_file
cat temp_file | wc
wc < temp_file
第二种形式在 && || 中可能有问题形式。为了完整起见,我将其放在这里。

最终,您必须决定做什么command1以及command2需要做什么。

我们现在可以将它们组合在一起:
cat file.txt > /tmp/out_file 2>/tmp/err_file && command1 < /tmp/out_file || command2 < /tmp/err_file

然后删除临时文件:
rm /tmp/out_file /tmp/err_file

这应该会给你你想要的。
我确信这里的其他大师可以想出一种不使用 temp_file 的方法。我喜欢保持事情简单易懂,因为我不在编程商店,我不知道谁将维护我的代码。

答案2

我认为做到这一点的唯一方法是将输出写入command1文件并读取command2

set -o errexit
command1 < file.txt > 1.out
command2 < 1.out

或者

if command1 < file.txt > 1.out
then
    command2 < 1.out
fi

当然,此时管道的异步处理优势就消失了。

答案3

简单的做法是将文件流保存到 a 中file,然后

command1 < file || command2 < file

或者

if command1 < file; then
    echo 'first command was successful'
else 
    command2 < file
fi

或任何类似的语法,具体取决于您想要输出的内容。

如果您希望同一个文件流同时被 2 个命令使用,您必须使用类似的方法tee,或者任何类似的方法,这意味着要么command2必须在command1返回之前开始处理,要么应该使用某种缓冲,也许与一些信号一起。良好且可扩展的缓冲是磁盘上的文件。

相关内容