次のスクリプトを考えてみましょう。
tmpfile=$(mktemp)
cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS
cat <(tail -1 "$tmpfile") "$tmpfile"
これは働き、出力されます:
line 3
line 1
line 2
line 3
入力ソースが実際のファイルではなく標準入力であるとします。
cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS
コマンドを修正するには:
cat <(tail -1 "$tmpfile") "$tmpfile"
それでは、他の状況でも同じ出力が生成されますか?
メモ:私が使用している特定のHeredocとHeredoc自体の使用は単なる例です。許容される回答は次のとおりです。stdin経由で任意のデータを受信する。
答え1
努力する:
awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
はい
入力として変数を定義します。
$ input="line 1
> line 2
> line 3"
次のコマンドを実行してください。
$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3
もちろんhere-docを使うこともできます。
$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3
どのように動作しますか?
x=x $0 ORS
これにより、入力の各行が変数に追加されます
x
。まったく
ORS
そうではありません。出力レコード区切り記号。デフォルトでは改行文字です。END{printf "%s", $0 ORS x}
ファイル全体を読み取った後、最後の行が印刷され、
$0
その後にファイル全体の内容が印刷されますx
。
これは入力全体をメモリに読み込むため、例えばギガバイト)入力。
答え2
stdinが検索可能ファイルを指している場合(ここのbash(他のすべてのシェルではない)のドキュメントが一時ファイルとして実装されている場合)、内容全体を読み取る前に尾を取得してから戻ることができます。
探すzsh
演算子はtcl / perl / pythonなどのシェルまたはスクリプト言語で使用できますが、使用する必要があるksh93
場合はいつでもより高いレベルのインタプリタを呼び出すことができますbash
。bash
bash
ksh93 -c 'tail -n1; cat <#((0))' <<...
または
zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...
stdinが検索できないファイル(パイプやソケットなど)を指している場合、これは機能しません。その場合、唯一のオプションは、入力全体を読み取って保存することです(メモリまたは一時ファイルに...)。
メモリ内ストレージのためのいくつかのソリューションが提供されました。
一時ファイルを使用するには、zsh
次のようにします。
seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'
Linuxでここで説明されているように一時ファイルを使用するシェルを使用している場合は、bash
実際にzsh
この資料で生成された一時ファイルを使用して出力を保存できます。
seq 10 | {
chmod u+w /dev/fd/3 # only needed in bash5+
cat > /dev/fd/3
tail -n1 /dev/fd/3
cat <&3
} 3<<EOF
EOF
答え3
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS
これを使用されるものに変換する際の問題は、ファイルの終わりを見つけるためにファイル全体を読む必要があることtail
です。tail
パイプラインでそれを使用するには、次のものが必要です。
- 文書の全内容を に提供してください
tail
。 - それを提供する再び到着する
cat
。 - そのような順序で。
トリッキーな部分は、文書の内容をコピーすること(そうすること)ではなく、tee
中間一時ファイルを使用せずに文書の残りの部分が出力される前に出力を取得することです。tail
使用sed
(またはawk
、John1024 本当に)データをメモリに保存することで、データの二重解析とソートの問題を排除します。
私が提案する解決策sed
は
1{h;d;}
、最初の行を予約済みスペースにそのまま保存し、次の行にジャンプします。H
、改行文字を含む予約済みスペースに行を追加します。${G;p;}
、新しい行を含む最後の行に予約済みスペースを追加し、結果データを印刷します。
これはJohn1024のソリューションを文字通り翻訳したものですが、sed
POSIX規格は少なくとも8192バイト(8KiB)の予約済みスペースしか保証しないことに注意してください。おすすめこのバッファは必要に応じて動的に割り当てられ、拡張されます。sed
GNUとBSDの両方がsed
これを行います。
名前付きパイプの使用を許可する場合:
mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe
tee
これは、データを送信するのmypipe
と同時にデータを送信するために使用されますcat
。ユーティリティcat
は最初に出力を読み取り、次にtail
(読み取りmypipe
からtee
書き込みまで)出力から直接文書のコピーを追加しますtee
。
ただし、文書が次の場合、重大な欠陥があります。大きすぎる(パイプのバッファサイズより大きい) (名前が指定されていない) パイプがクリアされるのを待っている間、書き込みおよびブロックされますtee
。mypipe
読む前は消去されませんcat
。cat
終わるまで読まないでくださいcat
。tail
そしてtail
それが終わるまでは終わらないでしょう。tee
これは典型的なデッドロック状態です。
多様性
tee >( tail -n 1 >mypipe ) | cat mypipe -
同じ問題があります。
答え4
順序に興味がない場合。これでこれが機能しますcat lines | tee >(tail -1)
。他の人が言ったように。必要な順序で操作を実行するには、ファイルを2回読み取るか、ファイル全体をバッファリングする必要があります。