web-dev-qa-db-ja.com

ファイルシステム全体でのsedインプレース行削除?

まだ診断されていないアプリケーションのバグが原因で、ディスクがいっぱいのサーバーが数百台あります。重複する行で満たされたファイルが1つあります。ログファイルではなく、変数定義を含むユーザー環境ファイルです(そのため、ファイルを削除することはできません)。

誤って追加された行をチェックして削除する簡単なsedコマンドを記述し、ファイルのローカルコピーでテストしました。意図したとおりに機能しました。

しかし、ディスク全体を使用してサーバーで試したところ、およそ次のエラーが発生しました(コピーと貼り付けではなく、メモリからのものです)。

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

もちろん、私は知っていますスペースが残っていません。だから私はものを削除しようとしています! (私が使用しているsedコマンドは、4000行以上のファイルを約90行に削減します。)

私のsedコマンドはsed -i '/myregex/d' /path/to/file/filenameです

ディスクがいっぱいでもこのコマンドを適用する方法はありますか?

(クイックフィックスとして数百台のサーバーに適用する必要があるため、自動化する必要があります。)

(明らかにアプリケーションのバグを診断する必要がありますが、その間サーバーは正しく動作していません...)


更新:削除できることがわかった他のものを削除することで、私が直面した状況は解決されましたが、this質問。これは将来、他の人に役立つでしょう。

/tmpは立ち入り禁止です。同じファイルシステム上にあります。

ディスク領域を解放する前に、ファイルを開いて:g/myregex/dを実行し、:wqで変更を正常に保存することで、viの行を削除できることをテストしました。一時ファイルを保持するために別のファイルシステムに頼ることなく、これを自動化することは可能であるようです...(?)

11
Wildcard

-iオプションは、実際には元のファイルを上書きしません。出力で新しいファイルを作成し、元のファイル名に名前を変更します。この新しいファイルのためのファイルシステム上のスペースがないため、失敗します。

スクリプトでそれを自分で行う必要がありますが、別のファイルシステムに新しいファイルを作成します。

また、正規表現に一致する行を削除するだけの場合は、grepの代わりにsedを使用できます。

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

一般に、プログラムが同じファイルを入力と出力として使用することはほとんどありません。ファイルへの書き込みを開始するとすぐに、プログラムがファイルから読み取っている部分には、元の内容が表示されなくなります。したがって、最初に元のファイルをどこかにコピーするか、新しいファイルに書き込んで名前を変更する必要があります。

一時ファイルを使用したくない場合は、ファイルの内容をメモリにキャッシュしてみてください。

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename
10
Barmar

これがsedの仕組みです。 _-i_(インプレース編集)と共に使用した場合、sedは、処理されたファイルの新しい内容で一時ファイルを作成します。 sedが終了すると、現在の作業ファイルを一時ファイルに置き換えます。ユーティリティはファイルin-placeを編集しません。それがすべてのエディターの動作です。

シェルで次のタスクを実行するようなものです。

_sed 'whatever' file >tmp_file
mv tmp_file file
_

この時点でsedは、fflush()システムコールを使用して、エラーメッセージに示されているファイルにバッファデータをフラッシュしようとします。

出力ストリームの場合、fflush()は、ストリームの基になる書き込み関数を介して、指定された出力または更新ストリームのすべてのユーザー空間のバッファーデータを強制的に書き込みます。


あなたの問題については、別のファイルシステム(たとえば、十分なメモリがある場合はtmpfs、または外部ストレージデバイス)をマウントして、そこにいくつかのファイルを移動し、そこで処理して、元に戻す解決策を見つけました。 。

4
chaos

この質問を投稿してから、exはPOSIX準拠のプログラムであることを学びました。それはほぼ普遍的にvimにシンボリックリンクされていますが、どちらにしても、(POSIX仕様から取られた)ファイルシステムに関連するexの重要なポイントは次のとおりです(私はそう思います)。

このセクションでは、edit bufferという用語を使用して、現在の作業テキストを説明します。この用語は特定の実装を意味するものではありません。すべての編集変更は編集バッファーで実行され、エディターコマンドがファイルを書き込むまで、変更はファイルに影響しません。

「... any ファイルに影響を与える...」ファイルシステムに何かを(一時ファイルでさえ)置くと、「すべてのファイルに影響を与える」と見なされると思います。多分?*

POSIX仕様のex の注意深い調査は、オンラインで見られるexの一般的なスクリプトでの使用( vim固有のコマンド。)

  1. +cmdの実装は、POSIXではオプションです。
  2. 複数の-cオプションを許可することもオプションです。
  3. グローバルコマンド:gは、エスケープされていない次の改行まですべてを「食べます」(したがって、正規表現で一致が見つかるたびに、最後に一度ではなく実行されます)。したがって、-c 'g/regex/d | x'oneインスタンスのみを削除してからファイルを終了します。

したがって、私が調べたところによると、特定の正規表現に一致するすべての行を削除するために完全なファイルシステム上のファイルをインプレース編集するPOSIX準拠の方法は次のとおりです:

ex -sc 'g/myregex/d
x' /path/to/file/filename

これは、ファイルをバッファにロードするのに十分なメモリがある場合に機能します。

*他に何かを示すものを見つけた場合は、コメントにその旨を記載してください。

3
Wildcard

パイプ、ルークを使用してください!

ファイルを読む|フィルター| 書き戻し

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

この場合、sedは新しいファイルを作成せず、パイプで出力をddに送信するだけで、同じファイルが開かれます。もちろん、特定の場合にはgrepを使用できます

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

次に truncate 残り。

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT
2
Leben Gleben

オフセットまでのバイト数を取得でき、行が始点から終点まで発生する場合は、ファイルを非常に簡単に切り捨てることができます。

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

または、${TMPDIR:-/tmp}が他のファイルシステムにある場合:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

(most)シェルは、ヒアドキュメントを削除された一時ファイルに入れます。 <<FILE記述子が最初から最後まで維持され、${TMPDIR:-/tmp}に必要なスペースがある限り、完全に安全です。

一時ファイルを使用しないシェルはパイプを使用するため、この方法を使用しても安全ではありません。これらのシェルは通常、ashbusybox、BSD dash-shzshbash、Bourne Shellなどのksh派生ですが、すべて一時ファイルを使用します。

どうやら私は 小さなシェルプログラムを書いた 昨年7月にこのようなことをするために


/tmpが実行可能でない場合は、ファイルをメモリに収めることができる限り、...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

...一般的なケースとして、少なくとも最初のsedプロセスによってファイルが完全にバッファリングされてから、入出力ファイルを切り捨てようとします。

よりターゲットを絞った効率的なソリューションは次のとおりです。

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

...とにかく削除するつもりのバッファリング行を邪魔しないからです。

一般的なケースのテスト:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1
mikeserv

他の回答で述べたように、sed -iは、ファイルを新しいファイルにコピーします同じディレクトリ内、プロセスに変更を加え、新しいファイルを元のファイルに移動します。それが機能しない理由です。 ed(元のラインエディター)はやや似た方法で機能しますが、前回チェックしたときは、スクラッチファイルに/tmpを使用しています。 /tmpがいっぱいのファイルシステムとは異なるファイルシステムにある場合、edが代わりに機能します。

これを試してください(インタラクティブなシェルプロンプトで)。

$ ed / path/to/file/filename
 P 
 g /myregex/d
w
q

Pcapital P)は厳密には必要ありません。プロンプトをオンにします。それがなければ、あなたは暗闇の中で働いています、そして何人かの人々はこの当惑を見つけます。 wおよびqw riteおよびq uitです。

edは、不可解な診断で悪名高いです。いずれかの時点で、プロンプト(*)以外の何か、または明らかに正常な操作の確認であるもの(特に?が含まれている場合)が表示される場合、 しないでくださいファイルを書き込みます(wを使用)。 (q)を終了してください。うまくいかない場合は、もう一度qと言ってみてください。

/tmpディレクトリがいっぱいのファイルシステム上にある場合(またはそのファイルシステムがいっぱいの場合も)、どこかにスペースを見つけてみてください。混乱は、tmpfsまたは外部ストレージデバイス(フラッシュドライブなど)のマウントに言及しました。ただし、複数のファイルシステムがあり、それらがallフルでない場合は、他の既存のファイルシステムの1つを使用できます。 chaosは、ファイルを他のファイルシステムにコピーし、そこで編集(sedを使用)してから、元に戻すことをお勧めします。この時点で、それが最も簡単な解決策になる可能性があります。ただし、代わりに、空き領域のあるファイルシステムに書き込み可能なディレクトリを作成し、そのディレクトリを指すように環境変数TMPDIRを設定してから、edを実行することもできます。 (開示:これが機能するかどうかはわかりませんが、害はありません。)

edが動作したら、次のようにして自動化できます

ed ファイル名 << EOF 
 g /myregex/d
w
q
EOF

スクリプトで。またはprintf '%s\n' 'g/myregex/d' w q | ed -s filename、don_crisstiによって提案されました。

この答えは this other answerthis other answer からアイデアを借用しますが、それらに基づいて、より一般的に適用できる答えを作成します。

num_bytes = $(sed '/ myregex/d' / path/to/file/filename | wc -c)
 sed '/ myregex/d' / path/to/file/filename 1 <> / path/to/file/filename
 dd if =/dev/null of =/ path/to/file/filename bs = "$ num_bytes" seek = 1

1行目はsedコマンドを実行し、出力はファイルではなく標準出力に書き込まれます。具体的には、wcへのパイプを使用して文字をカウントします。 2行目はsedコマンドも実行し、出力は標準出力に書き込まれます。この場合、このファイルは、説明されている読み取り/書き込み上書き(切り捨てなし)モードで入力ファイルにリダイレクトされます こちら 。これはやや危険なことです。フィルターコマンドneverがデータ(テキスト)の量を増やす場合にのみ安全です。つまり、読み取るすべてのnバイトに対して、nまたは少ないバイト。もちろん、これはsed '/myregex/d'コマンド;読み取るすべての行について、まったく同じ行を書き込むか、何も書き込まない。 (その他の例:s/foo/fu/またはs/foo/bar/は安全ですが、s/fu/foo/およびs/foo/foobar/はしません。)

例えば:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

これらの32バイトのデータは:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

これらの25文字で上書きされました:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

7バイトを残すnight.\n最後に残っています。

最後に、ddコマンドは、新しいスクラブされたデータ(この例ではバイト25)の最後までシークし、ファイルの残りを削除します。つまり、その時点でファイルが切り捨てられます。


何らかの理由で1<>トリックは機能しません。実行できます

sed '/ myregex/d' / path/to/file/filename | dd of =/ path/to/file/filename conv = notrunc

また、行を削除するだけの場合は、grep -v myregexBarmar で指摘されているように)。