ファイルを入力として使用してさまざまなタスクを実行する大規模なスクリプトがあります。これはテストバージョンです:
echo "cat: $1"
cat $1
echo "grep: $1"
grep hello $1
echo "sed: $1"
sed 's/hello/world/g' $1
私のスクリプトでプロセス置換を使用できるようにしたいのですが、最初のコマンド(cat
)のみが機能し、残りは機能しません。パイプラインだからだと思います。
$ myscript.sh <(echo hello)
以下を印刷する必要があります。
cat: /dev/fd/63
hello
grep: /dev/fd/63
hello
sed: /dev/fd/63
world
可能ですか?
答え1
この<(…)
構成はパイプを生成します。パイプは同様のfilenameを渡します/dev/fd/63
が、これは特別な種類のファイルです。実際にパイプを開くとは、ファイル記述子63をコピーすることを意味する。 (終了参照この回答もっと説明したい。 )
パイプから読むのは破壊的な仕事です。一度バイトをキャプチャすると、元に戻すことはできません。したがって、スクリプトはパイプの出力を保存する必要があります。一時ファイル(入力が大きい場合は優先)または変数(入力が小さい場合は優先)を使用できます。一時ファイルの使用:
tmp=$(mktemp)
cat <"$1" >"$tmp"
cat <"$tmp"
grep hello <"$tmp"
sed 's/hello/world/g' <"$tmp"
rm -f "$tmp"
(2つの呼び出しをcat
にまとめることができますtee <"$1" -- "$tmp"
。)変数の使用:
tmp=$(cat)
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'
コマンド置換は、$(…)
コマンド出力の末尾にある改行を切り取ります。これを防ぐには、文字を追加してから削除してください。
tmp=$(cat; echo a); tmp=${tmp%a}
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'
さて、変数置換の二重引用符を忘れないでください。
答え2
ファイルを操作するときは、そのデータを複数回読み取ることができます。名前付きパイプ(実際にはプロセス置換によって生成されます)を使用している場合は、一度だけ読み取ることができます。したがってgrep
、sed
コマンドは空の入力を受け取ります。
(パイプラインを理解する方法よく読んでみるとよさそうです。 )
プロセス交換のために、次のように書くことができます。
cat $1 | tee >(echo "cat: $1"; cat) | tee >(echo "grep: $1"; grep hello) | (echo "sed: $1"; sed 's/hello/world/g')
ただし、この場合、2番目cat
とgrep
2sed
番目は並列に実行され、出力はインターリーブされます。これはもっと役に立つかもしれません:
cat $1 | tee >(cat > cat.txt) | tee >(grep hello > grep.txt) | sed 's/hello/world/g' > sed.txt
答え3
一般的なアプローチは、$1
パラメータを選択的にすることです。その後、FILE=${1-/dev/stdin}
複数回定義して使用できます。FILE
ただし、パイプへの複数の読み出しは順次読み込まれ、データは繰り返されません。
この問題を解決する最も簡単な方法は、一時ファイルを使用することです。
if [ -z "$1" ] ; then FILE=$(mktemp); cat >FILE; else FILE=$1; fi
一部のファイル名を明示的に(最終的に)渡すには、/dev/fd/x
同じ一時ファイルトリックを使用できます。
FILE=$(mktemp); cat "$1" >FILE
tee
複雑な方法を使用して、stdinファイル記述子の入力を複数の異なるファイル記述子にコピーすることもできます。しかし、結局のところ、この方法は非常に面倒です。
答え4
プロセス置換によって取得されたIファイルは、デフォルトの実装では検索できないため、複数回読み取ることはできません。