これについては、 Advanced Bash-Scripting GuideのI / O Redirectionの章で詳しく説明されています 。
特に、ファイルを読み取って何らかの方法で処理し(たとえば、特定の正規表現に適合する行のみを選択する)、結果を同じファイルに書き込むことが必要になる場合があります。 ファイルの名前が「messages.log」で、「Success」という単語で始まる行、コロン、スペースのみを残したい(そして他のすべての行を削除したい)とします。
このコマンドはこれに適していると想定できます。
grep "^Success:\s" messages.log > messages.log
しかし、この仮定は間違っていることが判明します。この行が実行されると、messages.logファイルが書き込み用に開かれ、grepが検索を開始する前にクリアされます。
ただし、興味深いのは、grepがまだ実行されているときに、出力が読み取ろうとしているのと同じファイルにリダイレクトされ、すぐに次のメッセージで終了することです。
grep:入力ファイル「messages.log」も出力です
GNU catは同じことを行います(cat messages.log> messages.logを実行してみてください):
cat:messages.log:入力ファイルは出力ファイルです
これは、入力ファイルのデバイスとiノードを、標準出力の書き込みに使用されるファイルの対応する値と比較することにより行われます。 src / cat.cでこのアプローチの実装を参照してください。
ちなみに、BSD catはそのようなチェックを提供しませんが、この場合はそれほど重要ではありません。ファイルは何らかの方法ですでにクリアされているので、読み書きするものがないため、catは終了します。
ただし、別の例を取り上げます。
cat messages.log >> messages.log
この場合、messages.logは消去しませんが、catコマンドの出力をファイルの最後に追加します。 また、catがこれら2つのファイルが一致して完了することを確認すると、ファイルは同じ状態のままになり、ユーザーにエラーが表示されます。 しかし、そのようなチェックがない場合、猫はループに入り、場所がなくなるか、ユーザーがプロセスを完了するまでファイルを補完します。
次に、読み取り中のファイルと同じファイルに出力を書き込む方法を考えてみましょう。 明らかな解決策は、一時ファイルを使用することです。 それは:
mv messages.log tmpmessages.log grep "^Success:\s" tmpmessages.log > messages.log rm tmpmessages.log
これは非常に便利だと言うことではありませんが、少なくともタスクは完全に解決されます。
もう1つのオプションは、sedを使用できることです。
sed -i -n -e '/^Success:\s/{p}' messages.log
しかし、このソリューションはもちろん普遍的ではありません。結局、正規表現で一致する行の選択は、テキスト処理に関連する多くのタスクの1つにすぎません。 さらに、この場合の構文はすでにはるかに複雑です。
ちなみに、実際にはsedは一時ファイルも使用します。これは、straceの出力を見ることで確認できます。
オープン( "messages.log"、O_RDONLY)= 3 ... オープン( "./ sedWiaEAG"、O_RDWR | O_CREAT | O_EXCL、0600)= 4 ... 読み取り(3、 "成功:123 \ nエラー:123 \ n"、4096)= 24 書き込み(4、 "成功:123 \ n"、13)= 13 読み取り(3、 ""、4096)= 0 ... 閉じる(3)= 0 ... 閉じる(4)= 0 ... rename( "./ sedWiaEAG"、 "messages.log")= 0 閉じる(1)= 0 閉じる(2)= 0 exit_group(0)=?
明らかに、なんらかの方法で中間ファイルなしでできるようにする必要があります。 そして、そのような機会があります-これは、moreutilsのスポンジプログラムです。
spongeは標準入力を読み取り、指定されたファイルに書き出します。 シェルリダイレクトとは異なり、スポンジは出力ファイルを開く前にすべての入力を吸収します。 これにより、同じファイルを読み書きするパイプラインを構築できます。
spongeは標準入力を読み取り、指定されたファイルに書き込みます。 シェルリダイレクトとは異なり、スポンジは、書き込まれたファイルを開く前に渡されたすべての入力を「吸収」します。 これにより、読み取りが書き込みと同じファイルから行われるようなパイプラインを使用できます。
したがって、スポンジを使用して、この例からシェルリダイレクトを削除し、代わりに、結果を書き込むファイルの名前をspongeコマンドの引数として渡すことができます。 パイプを使用してgrepコマンドの出力を渡します。
grep "^Success:\s" messages.log | sponge messages.log
原則として、ブログの投稿全体をこの例にまとめることができますが、より興味深いものになり、読者の一部が以前は知らなかったいくつかのニュアンスについて話すことさえできたと思います。
素晴らしい金曜日をお祈りします!