条件管道


39

说我有以下管道:

cmd1 < input.txt |\
cmd2 |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt

在某些情况下,我想cmd3cmd2和之间添加一个cmd4。有没有一种方法可以创建一种有条件的管道,而无需将cmd2的结果保存到临时文件中?我会想到类似:

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

Answers:


36

只是平常&&||运算符:

cmd1 < input.txt |
cmd2 |
( [[ "${DEFINED}" ]] && cmd3 || cat ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

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

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

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

随手提示!
反转


2
逆转逻辑无济于事。如果cat返回的退出状态为非零(获取SIGPIPE时很常见),cmd3则将运行该状态。最好在Thor的答案或其他避免运行的解决方案中使用if / then / else cat
斯特凡Chazelas

所以猫可以用来连接事物,有点像“直通”的溪流
Alexander Mills

19

if/ else/ fi作品。假设任何类似伯恩的壳:

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

10

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

if [ -n "$DEFINE" ]; then
  alias maybe_cmd3='cmd3 |'
else
  alias maybe_cmd3=''
fi
cmd1 |
cmd2 |
maybe_cmd3
cmd4 |
... |
cmdN > result.txt

那是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"

要么:

eval "
  cmd1 |
  cmd2 |
  ${DEFINE:+cmd3 |}
  cmd4 |
  ... |
  cmdN > result.txt"

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


9

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

true && false || echo foo

输出foo。这并不奇怪,

true && (echo foo | grep bar) || echo baz

echo foo | (true && grep bar || echo baz)

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

echo foo | (true && grep bar || sed -e abaz)

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


我看不到这里有什么意义。该else(或||)应作为一个空操作保持不变的输入。因此,更换catecho改变一切使你的代码无关。关于“和假或陷阱”,我认为不会对管道造成任何干扰:pastebin.com/u6Jq0bZe
manatwork 2012年

5
@manatwork:有一件事乔纳斯指出的是,在[[ "${DEFINED}" ]] && cmd3 || catcmd3cat不是相互排斥thenelse相同的分支if,但如果cmd3失败,那么cat也将被执行。(当然,如果cmd3总是成功,或者如果它消耗了所有的输入,所以没有什么剩下stdincat工艺,那么它可能并不重要。)如果你需要同时thenelse分支机构,最好是使用一个明确的if命令,不&&||
musiphil

1
感谢您的解释,@ musiphil。现在我明白了乔纳斯的论点。
manatwork 2012年

4

我有类似的情况,我用bash 函数解决了:

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

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

3

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

dir="$(mktemp -d)"
fifo_n='0'
function addfifo {
 stdin="$dir/$fifo_n"
((fifo_n++))
[ "$1" ] || mkfifo "$dir/$fifo_n"
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"; exit 0

2

供将来参考,如果您要来这里寻找鱼类中的条件管道,请按照以下步骤操作:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.