stdinの最後の行をstdin全体に付加


9

次のスクリプトについて考えてみます。

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"

それで、この異なるコンテキストで、同じ出力を生成しますか?

注:ここで取り上げている特定のヘレドック、およびヘレドック自体の使用は、単なる例示にすぎません。許容できる回答は、stdinを介して任意のデータを受け取っていると想定する必要があります。


1
stdinは常に「実際のファイル」です(fifo / socket / etcもファイルです。すべてのファイルがシークできるわけではありません)。あなたの質問への答えは、簡単な「一時ファイルを使用する」か、メモリ全体にファイル全体をロードする恐怖です。「どこにも保存せずにストリームから古いデータを取得するにはどうすればよいですか?」良い答えはありません。
モスビー

1
@mosvy追加したい場合、これは完全に許容できる回答です。
ジョナ

2
@mosvyジョナが言ったように、答えは答え箱に投稿されるべきです。現時点でウェブサイトを読むのは難しいことは承知していますが、ゆっくりとあなたのビジョンに垂れ下がっている赤を無視して、下部のテキストエリアを使用してください。
wizzwizz4

回答:


7

試してください:

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

あるいは、もちろん、ヒアドキュメントを使用することもできます。

$ 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

    awkでは、ORS出力レコード区切り文字です。デフォルトでは、改行文字です。

  • END{printf "%s", $0 ORS x}

    ファイル全体を読み込んだ後、最後の行を出力し$0、続いてファイル全体の内容を出力しますx

これはメモリに入力全体を読み込んでいるので、それは(大型のために適切ではない例えばギガバイト)入力。


ジョンに感謝します。OPの名前付きファイルの例と同様の方法でこれを行うことはできませんか?stdinがどういうわけか複製されることを想像していました...一種の方法teeはそうしますが、stdinとファイルの場合、同じstdinを2つの異なるプロセス置換にパイプします。またはそれとほぼ同等のものは何ですか?
ジョナ

5

stdinがシーク可能なファイルを指している場合(テンポラリファイルで実装されているbashの(他のすべてのシェルではない)hereドキュメントの場合のように)、末尾を取得してから、コンテンツ全体を読み取る前にシークできます。

シーク演算子は、zshまたはksh93シェル、またはtcl / perl / pythonなどのスクリプト言語で使用できますが、では使用できません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

4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

これを使用するものに変換する際の問題tailは、tailファイル全体を読み取ってその終わりを見つける必要があることです。これをパイプラインで使用するには、次のことが必要です。

  1. 文書の完全な内容をに提供してくださいtail
  2. 再度提供てくださいcat
  3. その順番で。

トリッキーなビットは、ドキュメントのコンテンツを複製するteeこと(それを行う)tailではなく、中間一時ファイルを使用せずに、ドキュメントの残りの部分が出力される前にの出力を取得することです。

使用するsed(またはawkとして、John1024はありませんが)メモリにデータを格納することで、データの二重の解析と秩序の問題を取り除きます。

sed私が提案することをソリューションにあり

  1. 1{h;d;}、最初の行をそのままホールドスペースに格納し、次の行にスキップします。
  2. H、改行を埋め込んだホールドスペースに他の各行を追加します。
  3. ${G;p;}、改行が埋め込まれた最後の行に保留スペースを追加し、結果のデータを出力します。

これは、John1024のソリューションをsedにリテラルに変換したものです。POSIX標準では、ホールドスペースが最低でも8192バイト(8 KiB)であることが保証されているだけですが、このバッファーは、必要に応じて動的に割り当て、拡張することをお勧めします。sedと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と同時にに送信するために使用しますcatcatユーティリティは、最初からの出力読み込みますtail(から読み込みmypipeteeに書き込みを行っているが)、その後から直接文書のコピーを追加しますtee

重大な欠陥は、文書がある場合ということで、このかかわらでありますが大きすぎる(パイプのバッファーサイズより大きい)、teeのへの書き込みmypipeとは、cat(無名の)パイプを空にすることを待っている間にブロックされます。cat読み取るまでは空になりません。完了catするまでそれから読みませんtail。そしてtail、完了するまで完了しませんでしたtee。これは典型的なデッドロック状況です。

バリエーション

tee >( tail -n 1 >mypipe ) | cat mypipe -

同じ問題があります。


2
sed入力は一行のみ(多分を持っている場合は、1つは動作しませんsed '1h;1!H;$!d;G')。また、いくつかのsed実装では、パターンとホールドスペースのサイズに下限があることに注意してください。
ステファンChazelas

名前付きパイプソリューションは、私が探していたものです。制限は残念です。「そして、ティーが終了するまでテールが終了しない」以外の説明を理解しました。その理由を詳しく説明していただけますか?
ジョナ

2

pee通常は「moreutils」という名前でパッケージ化されたコマンドラインユーティリティのコレクションに名前が付けられたツールがあります(またはホームページから取得できます)。

あなたのシステムにそれを置くことができるなら、あなたの例と同等のものは次のようになります:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

コマンドpeeは提供された順序で実行されるため、実行されるコマンドの順序は重要です。


1

試してください:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

全体がリテラルデータ(「here-isドキュメント」)であり、それと目的の出力との違いは取るに足らないものなので、そのリテラルデータをすぐにマッサージして出力と一致させます。

ここでline 3、どこかから来て、次の変数に格納されているとしますlastline

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

ヒアドキュメントでは、変数を代入してテキストを生成できます。それだけでなく、コマンド置換を使用してテキストを計算できます。

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

複数のラインを補間することができます:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

一般に、ヒアドキュメントテンプレートのテキスト処理は避けてください。補間されたコードを使用して生成してみてください。


1
これが冗談かどうか正直に言えない。cat <<EOS...OPでちょうどポスト具体的かつ明確な質問をするために「任意のファイルをcatting」の例の代役でした。それはあなたにとって本当に明白ではなかったのですか、それとも質問を文字通り解釈するのが賢明だと思いましたか?
ジョナ

@Jonah質問には、「実際のファイルではなく、入力ソースが代わりにstdin:だったとしましょう」と明記されています。「任意のファイル」については何もありません。それはここのドキュメントについてです。ヒアドキュメントは任意ではありません。これはプログラムへの入力ではなく、プログラマが選択する構文の一部です。
Kaz

1
あなたの解釈が正しいために、私も返信した他のポスターも、コードの行。それにもかかわらず、私はそれを明確にするために質問を編集します。
ジョナ

1
カズ、返信ありがとうございます。ただし、編集しても質問の意図を逃していることに注意してください。パイプを介し任意の複数行入力を受け取っています。どうなるか分からない。あなたの仕事は、入力の最後の行を出力し、その後に入力全体を出力することです。
ジョナ

1
カズ、入力は例としてのみあります。私を含むほとんどの人は、抽象的な質問だけでなく、実際のインプットと期待されるアウトプットの例があると役立ちます。これで混乱したのはあなただけです。
ジョナ

0

注文を気にしない場合。その後、これは動作しcat lines | tee >(tail -1)ます。他の人が言ったように。要求した順序でファイルを読み取るには、ファイルを2回読み取るか、ファイル全体をバッファリングする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.