web-dev-qa-db-ja.com

パイプラインを元に戻す

人はそれを考えるかもしれません

echo foo >a
cat a | rev >a

aにはoofが含まれます。代わりに空のままにします。

  1. どうして?
  2. それ以外の場合、revaにどのように適用しますか?
24
Toothrot

そのためのアプリがあります! spongemoreutilsコマンドは、まさにこのために設計されています。 Linuxを実行している場合は、すでにインストールされている可能性があります。そうでない場合は、オペレーティングシステムのリポジトリでspongeまたはmoreutilsを検索してください。次に、次のことができます。

echo foo >a
cat a | rev | sponge a

または、 oC を避けます:

rev a | sponge a

この動作の理由は、コマンドが実行される順序にあります。 > aは実際には最初に実行されるものであり、> fileはファイルを空にします。例えば:

$ echo "foo" > file
$ cat file
foo
$ > file
$ cat file
$

したがって、cat a | rev >aを実行すると、実際に> aが最初に実行されてファイルが空になるため、cat aを実行すると、ファイルはすでに空です。これがまさにspongeが書かれた理由です(man sponge、強調鉱山から):

スポンジは標準入力を読み取り、それを指定されたファイルに書き込みます。 シェルリダイレクトとは異なり、スポンジはすべての入力を吸収してから出力ファイルを書き込みます。これにより、同じファイルから読み書きするパイプラインを構築できます。

31
terdon
  1. 出力の切り捨ては非常に早く行われるため、catは空のファイルを見つけます。
  2. 最初のファイルが一時ファイルとして作成されるか、またはrevの出力が一時ファイルに送られ、一時ファイルに名前が変更されます。
10
stolenmoment

これを修正する別の方法は、切り捨てない書き込みメソッドを使用することです

  rev a | dd conv=notrunc of=a

これは次の理由でのみ機能します:

  1. revは出力を生成する前にコンテンツを読み取り、出力はすでに読み取られた量より長くなることはありません

  2. 新しいファイルのコンテンツは元のサイズと同じかそれよりも大きい(この場合は同じサイズ)

  3. ddは、ファイルを切り捨てずに、書き込むファイルを開きます。

このアプローチは、一時的なコピーを保持するには大きすぎるファイルのインプレース変更に役立つ場合があります。

7
Jasen
_cat a | rev > a
_

なぜ[aが空のままになっている]のですか?

上記のパイプラインでは、シェルは2つのサブプロセスを分岐し、パイプラインの2つの部分のそれぞれに1つずつです。次に、これらのサブプロセスは問題のコマンドを実行し、最初にリダイレクトを処理し、次にexec*()関数の1つを呼び出して外部ユーティリティを起動します。サブプロセスは並行して実行され、それらの間のタイミングの保証はありません

プロセスの実行はそれほど高速ではないため、通常は、右側のシェルがcatがファイルを読み取る機会を得る前にリダイレクトを設定することが起こります。出力リダイレクト_> a_はファイルを切り捨てるので、catに読み取るものはなく、revはデータを受信せず、データを生成しません。左側のリダイレクトも使用した場合(_cat < a | rev > a_)、aは切り捨てられる前に読み取り用に開かれる可能性がありますが、catにはまだ時間がありません実際にそれを読む前に。

一方、これは私のシステムで一貫して_a contains: foo_を出力します:

_echo foo > a; cat < a | tee a > /dev/null ; echo "a contains: $(cat a)"
_

ここでは、ファイルを切り捨てるのはteeなので、これはexec()およびcatがファイルを読み取る時間を持つ可能性が高くなった後に発生します。ただし、ファイルが十分に大きい場合は、読み取り中に途中で切り捨てられる可能性があります。

私はmightおそらくと言いました、OSが別の方法でプロセスをスケジュールすることを決定した場合。

それ以外の場合、revaにどのように適用しますか?

通常の解決策は一時ファイルを使用することです:

_cat a | rev > b && mv b a
_

一時ファイル名が使用可能であることを確認できない限り、既存のファイルを上書きする可能性があるという通常の問題があります。おそらくmktempを使用する必要があります:

_f=$(mktemp ./tmp.XXXXXX)
cat a | rev > "$f" && mv "$f" a || rm "$f"
_

または、 sponge tool を使用することもできます。これにより、出力ファイルを開く前に、取得したすべての入力を読み取ることができます(それ以外の場合は、catのようになります)。

_cat a | rev | sponge a
_

あるいは単に

_rev < a | sponge a
_

_sponge > a_は、元のコマンドが機能しないのと同じ理由で間違いです。


スポンジは moreutils からのものであり、標準的なツールではありません。 別のコマンドにパイプする前にコマンド出力を完全にバッファーしますか?

一部のユーティリティは、同様の機能を自分で実装する場合があります。 _sort -o outputfile_は、終了後にのみ出力ファイルを開きます。参照 `sed --in-place`のように、ソートはファイルのインプレースソートをサポートしていますか?

3
ilkkachu

VimはExモードで使用できます。

ex -s -c '%!rev' -c x a.txt
  • %すべての行を選択
  • !コマンドを実行
  • x保存して閉じる
0
Fourteen