w
私のコマンド(macOS 13.1のsed)は、(bash 3.2)を使って入力ファイルを編集sed
できるようです。cat
printf "hello\nworld\n" > foo.txt
cat foo.txt | sed 's/l/L/g' | sed -n 'w foo.txt'
cat foo.txt
> heLLo
> worLd
私は見たhttps://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.htmlしかし、リダイレクトなどをfoo.txt
使用する場合とは異なり、上記のパイプラインを正常に編集できる理由は何であるかよくわかりません。cat foo.txt | sed 's/l/L/g' > foo.txt
POSIX指定されていないフラグまたは一時ファイルが利用可能であることを知っていますが、(書き込み)コマンドを使用して入力ファイルを編集するのが安全かどうかを知りたいです-i
。w
編集する:
頑張った
printf "%d hello world\n" {1..100000} > foo.txt
cat foo.txt | sed 's/l/L/g' | sed -n 'w foo.txt'
もう正常に動作しないことがわかりました。結果はfoo.txt
4000〜8000行にすぎません。
答え1
使用sponge
(からその他のユーティリティ、または一時ファイルにリダイレクトし、元のファイルに名前を変更します。または使用編集する(またはex
vi/vim/nviで)sed
- 覚えておいてください。これはsed
ストリーム指向バージョンですed
。 ed
= editor
、sed
=小川編集する。
注:ed、sed、ex(そしてvi - viはもともと肉edのフルバージョン)はすべて共通のルートを持っているので、共通コマンドのサブセットを共有します。しかし、それぞれが異なる方向に開発され、異なる拡張機能を持っています。それぞれ異なる機能を持ついくつかのバージョンがあります。他の多くのプログラムは少なくともいくつかの共通コマンドを借用しました(たとえば、rogueとnethackは両方hjkl移動キーを借用しました)。明確でない場合でも注目に値する。ex
コマンドは:
vi内のコマンドであり、コマンドの親セットです(使用する実装ed
によって異なります)。vi
3つの方法すべての例です。
sed -e 's/l/L/g' foo.txt | sponge foo.txt
sed -e 's/l/L/g' foo.txt > foo.new && mv foo.new foo.txt
printf '%s\n' %s/l/L/g w q | ed -s foo.txt
printf '%s\n' %s/l/L/g w q | ex foo.txt
ところで、ソースman sponge
:
sponge
標準入力を読み取り、指定されたファイルに書き込みます。シェルリダイレクトとは異なり、スポンジは出力ファイルに書き込む前にすべての入力を吸収します。これにより、同じファイルを読み書きするパイプラインを構築できます。出力ファイルがすでに存在する場合、Sponge はファイルの権限を保持します。
メモ:
Spongeは基本的にメソッドをリダイレクトして名前を変更する便利なツールです。
リダイレクトと名前変更は、ソース出力ファイルの権限を保持しません。ユーザーが決定した権限で新しいファイルを生成します
umask
(他の作成した新しいファイルと同様)。 umaskによっては、これらの権限は元の権限と同じでも同じでもない場合があります。違いは次のとおりです。
sponge
確実にする新しいファイルには元のファイルと同じ権限がありますが、単純なリダイレクトではありません。and を使用する
ed
と、ex
各コマンド ( write および finally quit でs///
置き換えられる ) が 1 行に 1 つずつ印刷され、 or にパイプされて foo.txt を開き、コマンドを実行します。w
q
printf '%s\n'
ed
ex
また、注:ed
両方ex
とも元のファイルを上書きします(元のファイルのinode番号を保持するため、そのファイルへのハードリンクは壊れません)。 sponge
一時ファイルへの書き込みと名前変更は、異なるinode番号を持つ新しいファイルを生成するため、ハードリンクが壊れます。ほとんどの場合(つまり、1つ以上のファイルへのハードリンクがない場合)、これはまったく重要ではありませんが、知っておくべきことです。
たとえば、次のようにinode番号がどのように変更されるかを確認しますsponge
。
$ printf "hello\nworld\n" > foo.txt
$ ls -li foo.txt
2251637 -rw-rw-r-- 1 cas cas 12 Feb 6 18:07 foo.txt
$ sed -e 's/l/L/g' foo.txt | sponge foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:07 foo.txt
リダイレクトでファイルを上書きしても、inode番号は変更されず、ex(またはed)で編集されません。
$ printf "hello\nworld\n" > foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:08 foo.txt
$ printf '%s\n' %s/l/L/g w q | ex foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:09 foo.txt
必要に応じて、次のようにリダイレクトと名前変更方法を使用して元のインデックスノードを保存できます。
sed -e 's/l/L/g' foo.txt > foo.new
cat foo.new > foo.txt
rm foo.new
cat
はい、それは必要ではないことを知っています。<
タスクもリダイレクトします。私は、コマンドラインの先頭からリダイレクトしたり、実際のコマンドなしでリダイレクトしたりするのが嫌だと思っています。連合大学
そしてStephen Kittがコメントで指摘したように、cp foo.new foo.txt
この機能も機能し、元の権限をそのまま維持します。
答え2
このw
sed
コマンドは、最初に呼び出されたときに出力ファイルを開きます(ここでは、sed
パイプからデータブロックを読み取ってから最初の行を処理するとき)O_WRONLY | O_TRUNC
。したがって、この時点でファイルは空になります(カットated)したがって、コマンドがファイルを読み込んでいる場合(あなたの場合はまだcat
読み込みが完了していない場合)、残りの部分を読み取ることはできません。
代わりに、次のようにすることができます。
sed 's/l/L/g' < file 1<> file
シェルはstdinでsedを使用しO_RDONLY
、sedのstdoutで独立してファイルを開きますO_RDWR
が、もっと重要なことは、O_TRUNC
それを使用しないとsed
独自の入力を上書きすることです。
これは、ここで示すように、常に読み取った行とまったく同じサイズ(バイト単位)の出力行を書き込む場合にのみ機能しますsed
。それ以外の場合は、まだ読み取っていない行を上書きする可能性があります。
また、作成した内容が読み取った内容より短い場合は、ファイルの末尾に古いデータを残します。この問題は、端が切り捨てられた標準出力の内容を呼び出すことで解決できます。たとえば、次のようになります。
{ sed 's/hello/hi/g'; perl -e 'truncate STDOUT, tell STDOUT'; } < file 1<> file
ただし、これを使用するには、コピーしたいくつかの実装をperl
使用することをお勧めします。-i
sed
perl -pi -e 's/hello/hi/g' file