web-dev-qa-db-ja.com

条件付きパイプライン

次のパイプラインがあるとします。

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
42
Pierre

いつもの&&および||演算子:

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

(行がパイプで終了する場合、末尾のバックスラッシュは必要ないことに注意してください。)

Jonasの観測 に従って更新
cmd3がゼロ以外の終了コードで終了する可能性があり、catに残りの入力を処理させたくない場合は、ロジックを逆にします。

cmd1 < input.txt |
cmd2 |
( [ -z "$DEFINED" ] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
37
manatwork

if/else/fiは機能します。ボーンのようなシェルを想定すると、

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

これまでに出されたすべての回答は、_cmd3_をcatに置き換えます。また、次のコマンドを使用してコマンドを実行しないようにすることもできます。

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

これはPOSIXですが、bashスクリプトでbashshモードでない場合(_#! /path/to/bash_で始まるスクリプトの場合など)、 _shopt -s expand_aliases_(または_set -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の代わりに、splice() + read()の代わりにwrite()を使用する_pv -q_を使用できます。 2つのパイプ間でデータを渡すため、カーネル空間とユーザー空間の間でデータが2回移動するのを防ぎます。

11

manatworkの承認済み回答 の補遺として、and-false-or gotchaとストリームとの相互作用に注意してください。例えば、

true && false || echo foo

fooを出力します。驚くことではないが、

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

そして

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

どちらもbazを出力します。 (ご了承ください echo foo | grep barはfalseであり、出力はありません)。しかしながら、

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

何も出力しません。これは必要な場合とそうでない場合があります。

9
Jonas Kölker

私はbash functionsで解決した同様の状況にありました:

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

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

以下は、可能な代替ソリューションです。
疑似パイプラインを作成するための名前付きパイプの連結:

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"
4
Claudio

今後の参考のために、ここにfishの条件付きパイプを探している場合は、次のようにします。

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
2
Jorge Bucaran