条件管道

Pie*_*rre 51 shell pipe shell-script

假设我有以下管道:

cmd1 < input.txt |\
cmd2 |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)

在某些条件下,我想cmd3cmd2和之间添加一个cmd4。有没有办法创建一种条件管道而不将结果保存cmd2到临时文件中?我会想到这样的事情:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)

man*_*ork 51

只是通常的&&||运营商:

cmd1 < input.txt |
cmd2 |
( [ -n "$DEFINED" ] && cmd3 || cat ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)

虽然,正如此答案所指定的那样if ... else,如果您使用 if-else 语法,您通常会更喜欢:

  ...
  cmd2 |
  if [ -n "$DEFINED" ]; then cmd3; else cat; fi |
  cmd4 |
  ...
Run Code Online (Sandbox Code Playgroud)

(请注意,当行以管道结尾时,不需要尾部反斜杠。)

根据Jonas 的观察更新
如果 cmd3 可能以非零退出代码终止并且您不想cat处理剩余的输入,请反转逻辑:

cmd1 < input.txt |
cmd2 |
( [ -z "$DEFINED" ] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)

  • 请注意陷阱:http://unix.stackexchange.com/a/39043/19055 (7认同)
  • 颠倒逻辑无济于事。如果 `cat` 以非零退出状态返回(在获取 SIGPIPE 时很常见),则将运行 `cmd3`。更好地使用 if/then/else,如 [Thor 的回答](http://unix.stackexchange.com/a/38312/22565) 或其他避免运行 `cat` 的解决方案。 (3认同)

Tho*_*hor 25

if/ else/fi作品。假设任何类似 Bourne 的 shell:

cmd1 < input.txt |
cmd2 |
if [ -n "$DEFINED" ]; then cmd3; else cat; fi |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)


Sté*_*las 11

到目前为止给出的所有答案都替换cmd3cat. 您还可以避免运行任何命令:

if [ -n "$DEFINE" ]; then
  alias maybe_cmd3='cmd3 |'
else
  alias maybe_cmd3=''
fi
cmd1 |
cmd2 |
maybe_cmd3
cmd4 |
... |
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)

那是 POSIX,但请注意,如果在不是-mode的bash脚本中(例如以 开头的脚本),则需要使用(或)启用别名扩展。bashsh#! /path/to/bashshopt -s expand_aliasesset -o posix

仍然不运行任何不必要命令的另一种方法是使用 eval:

if [ -n "$DEFINE" ]; then
  maybe_cmd3='cmd3 |'
else
  maybe_cmd3=''
fi
eval "
  cmd1 |
  cmd2 |
  $maybe_cmd3
  cmd4 |
  ... |
  cmdN > result.txt"
Run Code Online (Sandbox Code Playgroud)

或者:

eval "
  cmd1 |
  cmd2 |
  ${DEFINE:+cmd3 |}
  cmd4 |
  ... |
  cmdN > result.txt"
Run Code Online (Sandbox Code Playgroud)

在 Linux 上(至少),cat您可以使用pv -qwhichsplice()代替read()+write()在两个管道之间传递数据,从而避免数据在内核空间和用户空间之间移动两次。


Jon*_*ker 9

作为manatwork 已接受答案的附录:请注意 and-false-or gotcha 及其与流的交互。例如,

true && false || echo foo
Run Code Online (Sandbox Code Playgroud)

输出foo。这并不奇怪,

true && (echo foo | grep bar) || echo baz
Run Code Online (Sandbox Code Playgroud)

echo foo | (true && grep bar || echo baz)
Run Code Online (Sandbox Code Playgroud)

两者输出baz。(请注意,这echo foo | grep bar是错误的并且没有输出)。然而,

echo foo | (true && grep bar || sed -e abaz)
Run Code Online (Sandbox Code Playgroud)

什么都不输出。这可能是也可能不是您想要的。

  • @manatwork:乔纳斯指出的一件事是,在`[[ "${DEFINED}" ]] &amp;&amp; cmd3 || cat`、`cmd3` 和`cat` 不是同一个`if` 的互斥`then` 和`else` 分支,而是如果`cmd3` 失败,那么`cat` 也会被执行。(当然,如果 `cmd3` 总是成功,或者如果它消耗了所有输入,那么 `stdin` 中没有任何东西留给 `cat` 处理,那么这可能无关紧要。)如果你同时需要 `then` 和 ` else` 分支,最好使用显式的 `if` 命令,而不是 `&amp;&amp;` 和 `||`。 (7认同)
  • 主要要点是 `condition &amp;&amp; command1 || command2` 与 `if 条件**不**相同;然后命令1;否则命令2;菲` (2认同)

jfg*_*956 6

我有一个类似的情况,我用 bash函数解决了这个问题:

if ...; then
  my_cmd3() { cmd3; }
else
  my_cmd3() { cat; }
if

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Run Code Online (Sandbox Code Playgroud)


Cla*_*dio 5

这是另一种可能的解决方案:
连接命名管道以创建伪管道:

dir="$(mktemp -d)"
addfifo() {
  stdin="$dir/$fifo_n"
  [ "$1" ] || mkfifo "$dir/$((fifo_n=fifo_n+1))"
  stdout="$dir/$fifo_n"
}

addfifo; echo "The slow pink fox crawl under the brilliant dog" > "$stdout" &

# Restoring the famous line: "The quick brown fox jumps over the lazy dog"
# just replace 'true' with 'false' in any of the 5 following lines and the pseudo-pipeline will still work
true && { addfifo; sed 's/brilliant/lazy/' < "$stdin" > "$stdout" & }
true && { addfifo; sed 's/under/over/'     < "$stdin" > "$stdout" & }
true && { addfifo; sed 's/crawl/jumps/'    < "$stdin" > "$stdout" & }
true && { addfifo; sed 's/pink/brown/'     < "$stdin" > "$stdout" & }
true && { addfifo; sed 's/slow/quick/'     < "$stdin" > "$stdout" & }

addfifo -last; cat < "$stdin"

wait; rm -r "$dir"
Run Code Online (Sandbox Code Playgroud)