はじめに
Bash に限らずシェルスクリプトでも発生する事象ですが、 cat や < で読み込んだファイルに対して > (リダイレクト) で上書きを行ったにも関わらず、ファイルが空になってしまうという事象に一度は遭遇したことがある方も多いでしょう。
シェル操作を行うことが多い貴兄にはよくある話ですが、この問題への対処法を紹介します。
検証環境
| |
「リダイレクトで上書きするとファイルが空になる」問題とは?
対処法の話をする前に、「リダイレクトで上書きするとファイルが空になる」問題とは何かについて説明しておきます。
例えば、以下のようなファイルがあるとします。
| |
1~10 の数値が書かれている data.txt というファイルを用意しました。
このファイルにから、 2 と 5 の数値が登場する行を除外し、 data.txt に上書きします。
以下のようなコマンドを実行すれば、 2 と 5 の数値を含む行が除外されることを確認できます。
| |
実行結果を data.txt に書き込みます。
| |
data.txt の中身を確認してみます。
| |
このように、リダイレクトされたファイルは空になってしまいます。
原因
なぜファイルが空になってしまうのか。
先程のコマンドを少し変更してみます。
パイプの真ん中に tee hoge.txt というコマンドを挿入します。
hoge.txt ファイルには grep に渡る直前の情報が保存されます。
| |
cat data.txt の実行結果が空になっていることがわかります。
パイプで繋いだコマンドの一番最後に記述されている > data.txt がポイントです。
コマンド全体が実行されると、一番最初に > data.txt が実行され、ファイルが空にされてしまうのです。
処理ステップを文字で表現すると以下のようになリます。
cat data.txt:data.txtを読み込む準備を行う(Open)> data.txt:data.txtに書き込む準備を行う(Open)data.txtを読み込むgrep -v -e 2 -e 5:2と5を含む行を除外 ( 入力が空なので、出力結果も空 )data.txtに書き込む
対処法 1 : 一時的に別のファイルに書き出す
「> による上書き」をやめる、というのが一番確かです。
tmp.txt という一時ファイルに結果を書き出した後、 data.txt にリネームしてみます。
| |
問題なく結果が保存されました。
対処法 2 : 「サブシェル(+ファイル削除)」を使う
サブシェル内でファイルを削除を行うというハックを行うことで、この問題に対処することができます。
リダイレクトで結果をファイルにリダイレクトする前に、削除
| |
もしサブシェルを使ってもファイルの削除処理を行わなければ、やはり空になります。
| |
処理ステップを文字で表現すると以下のようになリます。
cat data.txt:data.txtを読み込む準備を行う(Open)rm data.txt:data.txtを削除> data.txt:data.txtに書き込む準備を行う(Open)data.txtを読み込むgrep -v -e 2 -e 5:2と5を含む行を除外 ( 入力が空なので、出力結果も空 )data.txtに書き込む
お恥ずかしながら、なぜこの方法で上書きファイルが空になる問題が解消されるか理解できておりません。 若い頃に先輩エンジニアに教えていただいた方法です。
2022-09-27追記
いつもアドバイスくださる @ko1nksm さんからコメントを頂き、記事を修正しました。 ありがとうございます!
ひとこと
rm を実行するのは少し怖いこともあり、可能なら一時ファイルを作成した後 mv で上書きするほうが確実かつ可読性が髙いです。