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


11

まだ診断されていないアプリケーションのバグが原因で、ディスクがいっぱいのサーバーが数百台あります。重複する行で満たされたファイルが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

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

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

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


更新:私が直面した状況は、自分が削除できることがわかった他のものを削除することで解決しましたが、この質問への回答は今後も他の人にも役立つと思います。

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

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



1
sed -i処理する一時的なコピーを作成します。私edは実際の解決策を禁止するのに十分に精通していませんが、これにはより良いと思います
Eric Renouf

2
ではed、あなたは実行したい:printf %s\\n g/myregex/d w q | ed -s infileしかし、いくつかの実装でも同じように一時ファイルを使用して覚えておいてくださいsed(あなたが試みることができるbusyboxのEDを -それは一時ファイルを作成しません。私の知る限り)
don_crissti

1
@Wildcard-確実にw /ではありませんecho。使用しますprintf。そして作るsedあなたは後続の空白を失うことを避けることができるので、あなたが最後の行にドロップし、いくつかの文字を追加します。また、シェルは単一のコマンドラインでファイル全体を処理できる必要があります。それがあなたのリスクです-最初にテストしてください。bashそれは特に悪いです(スタックスペースで行うと思いますか?)。いつでも気分が悪くなるかもしれません。2つのsed「si推奨」は、少なくともカーネルのパイプバッファーを使用して両者の間で効果を発揮しますが、方法はかなり似ています。コマンドのサブものはfile、sed w / inが成功したかどうかも切り捨てます。
mikeserv、2015

1
@ワイルドカード-試してみsed '/regex/!H;$!d;x' <file|{ read v && cat >file;}て、それが機能するかどうか、残りの私の答えを読んでください。
mikeserv 2015

回答:


10

この-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

1
権限、所有権、タイムスタンプは保持されましたか?多分ここrsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"から
Hastur

@Hastur-そのsed -iことを保持することを意味することを意味しますか?
mikeserv

2
@Hastur sed -iはそれらのいずれも保存しません。自分が所有していないファイルで試してみましたが、自分が所有しているディレクトリにあり、ファイルを置き換えることができました。交換品の所有者は私であり、元の所有者ではありません。
Barmar

1
@RalphRönnquist次の2つのステップでそれを行う必要があるだろう、確かに:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar

1
@Barmar-機能しません-入力を正常に開いたことがわかりません。非常にあなたができる少なくともですv=$(<file)&& printf %s\\n "$v" >fileが、あなたも使用しません&&。質問者はそれをスクリプトで実行することについて話している-それ自体の一部でファイルを上書きすることを自動化する。少なくとも入力と出力を正常に開くことができることを検証する必要があります。また、シェルが爆発する可能性があります。
mikeserv 2015

4

それが機能する方法sedです。-i(インプレイス編集)と共に使用するとsed、処理済みファイルの新しい内容を含む一時ファイルが作成されます。終了するsedと、現在の作業ファイルを一時ファイルに置き換えます。ユーティリティはファイルその場で編集しません。それがすべてのエディターの動作です。

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

sed 'whatever' file >tmp_file
mv tmp_file file

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

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


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


3

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

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

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

POSIX仕様をex注意深く調査すると、exオンラインで見つかる一般的なスクリプトでの使用(特定のvimコマンドが散らばっている)と比較した場合の、意図されたポータブルな使用についての「落とし穴」がいくつかあります。

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

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

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

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

※特に指摘のある箇所がありましたら、コメント欄にご記入ください。


2
しかしexはtmpfilesに書き込みます...常に。その仕様では、バッファを定期的にディスクに書き込みます。ディスク上のtmpファイルバッファを見つけるためのspec'dコマンドさえあります。
mikeserv 2016年

@Wildcard共有いただきありがとうございます。SOの同様の投稿にリンクしました。私ex +g/match/d -scx fileはPOSIXにも準拠していると思いますか?
kenorb 2016年

スペックを読んだ結果によると、@ kenorbはまったくそうではありません。上の回答の私のポイント1を参照してください。POSIXからの正確な引用は、「exユーティリティは、「-」の不特定の使用法を除いて、XBDユーティリティ構文ガイドラインに準拠する必要があり、「+」、オプションの区切り文字および「-」として認識される場合あります。」
ワイルドカード2016年

1
常識に訴えることを除いて、私はそれを証明することはできませんが、あなたは仕様書から実際にそこにあるよりもその声明をもっと読んでいると思います。より安全な解釈は、編集バッファーへの変更は、編集セッションの開始前に存在していたファイル、またはユーザーが指定したファイルに影響を与えないということです。私の回答に対する私のコメントも参照してください。
G-Manは 'Reinstate Monica'

@ G-Man、私は実際にあなたが正しいと思います。私の最初の解釈はおそらく希望的な考えでした。ただし、ファイルの編集は完全なファイルシステムでvi 機能するので、ほとんどの場合exそれでも機能すると思います。ただし、巨大なファイルの場合はそうではありません。 sed -iファイルサイズに関係なく、完全なファイルシステムでは機能しません。
ワイルドカード2016

2

ルーク、パイプを使って!

ファイルを読む| フィルター| 返事を書く

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

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

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

その後、残りを切り捨てます。

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

1
質問の「完全なファイルシステム」の部分に気づきましたか?
ワイルドカード2016年

1
@Wildcard、sed常に一時ファイルを使用しますか? grepとにかくしない
Leben Gleben

これはspongeコマンドの代替手段のようです。はい、sed-i常に000権限を持つ「seduyUdmw」lilkeファイルを作成します。
Pablo A

1

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

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

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

P(ある首都 P)厳密には必要ではありません。プロンプトをオンにします。それがなければ、あなたは暗闇の中で働いています、そして何人かの人々はこの当惑を見つけます。あるwは儀式やQ UIT。wq

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

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

ed作業を開始したら、次のようにして自動化できます

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

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


うーん。別のファイルシステムではなくメモリが使用されるように、同じことを(edまたはを使用してex)行うことができますか?それが私が実際に行っていた理由です(そして、私が答えを受け入れなかった理由です)
ワイルドカード

うーん。これは私が思っていたよりも複雑かもしれません。私はed何年も前に広範囲に源泉を研究しました。16ビットのコンピューターなど、プロセスは64K(!)のアドレス空間に制限されていたため、ファイル全体をメモリに読み込むエディターのアイデアは、最初はありませんでした。もちろん、それ以来、メモリは大きくなっていますが、ディスクやファイルも増えています。ディスクは非常に大きいので、人々は/tmpスペース不足の不測の事態に対処する必要を感じません。最近のバージョンののソースコードをざっと見てみましたがed、それでも…(続き)
G-Manは 'Reinstate Monica'

(続き)…「編集バッファ」を一時ファイルとして無条件に実装する—そして、ed(またはexまたはvi)のどのバージョンでも、バッファをメモリに保持するオプションを提供しているという兆候を見つけることができません。  一方、 edとviを使用したテキスト編集–第11章:テキスト処理–パートII:Red Hat Linuxの探索– Red Hat Linux 9 Professional Secrets – Linuxシステムでは、ed編集バッファーはメモリに常駐していると言われています…(続き)
G-Manは 'Reinstate Monica'

(続き)…そして、Balaasubramaniam SrinivasanによるUNIX文書処理と組版は、同じことを言っていますvi(これはと同じプログラムですex)。私は、彼らがずさんで不正確な表現を使用しているだけだと思いますが、それがインターネット上(または印刷物)にある場合、それは真実であるに違いありません。あなたはあなたのお金を支払い、あなたはあなたの選択をする。
G-Manは 'Reinstate Monica'

とにかく、私は新しい答えを追加しました。
G-Manは 'Reinstate Monica'

1

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

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

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

一時ファイルを使用しないシェルはパイプを使用するため、この方法を使用しても安全ではありません。これらのシェルは、典型的にはashのようなデリバティブbusyboxdashBSD、 - shzshbashkshおよびBourneシェル、しかし、すべての使用の一時ファイル。

どうやら私昨年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

私は以前に詳細にあなたの答えを読んだことがなかったことを告白します。それは、バイト数(多くのサーバーのそれぞれの間で異なる)を/tmp含み、同じファイルシステム上にある、実行できない(私にとって)ソリューションから始まるためです。私はあなたのデュアルsedバージョンが好きです。、のようなものを私はBarmarの組み合わせを考えると、あなたの答えはおそらく最高のようになります。myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar (この場合のために、私は末尾の改行を維持を気にしないでください)
ワイルドカード

2
@ワイルドカード-それは可能性があります。しかし、データベースのようにシェルを使用するべきではありません。sed| cat上記のものは、ファイル全体をすでにバッファリングしており、すべてのファイルを出力に書き始める準備ができていない限り、出力を開くことはありませんsed。ファイルをバッファリングしようとして失敗した場合- 最初の改行を読み取るにパイプでreadEOFが見つかり、メモリから完全に書き出すまでは発生しないため、成功しません。オーバーフローまたはそれと同様のものは単に失敗します。また、パイプライン全体が毎回成功または失敗を返します。varに格納するのはより危険です。|cat >out
mikeserv

@ワイルドカード- 変数でも本当に必要な場合、idは次のようにすると思います。file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shite出力ファイルと変数が同時に書き込まれ、どちらかまたは効果的なバックアップが作成されます。これが唯一の理由です。必要以上に複雑なこと。
mikeserv

@mikeserv:私は今OPと同じ問題を扱っており、あなたのソリューションは本当に便利だと思います。しかし、私はの使い方を理解していないread scriptと、read vあなたの答えでは。詳しく説明していただければ幸いです。
sylye 2016

1
@sylyeは- $scriptであるsedあなたが望んでいたものは何でもあなたのファイルの一部のターゲットに使用するスクリプト。ストリームで必要な最終結果を得るスクリプトです。v空の行の単なるプレースホルダーです。でbashシェル必要があるためではありませんbash自動的に使用されます$REPLY、あなたがいずれかを指定いけない場合、その代わりにシェル変数は、しかしPOSIXlyあなたはいつもそうする必要があります。ところで、あなたはそれが便利だと思ってうれしいです。頑張ってください。詳細については、mikeserv @ gmailをご覧ください。数日後にまたコンピューターが必要になるでしょう
mikeserv

0

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

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コマンドを実行して出力を標準出力に書き込みます。この場合、このファイルは、ここで説明する読み取り/書き込み上書き(切り捨てなし)モードで入力ファイルにリダイレクトされます。これはやや危険なことです。filterコマンドがデータ(テキスト)の量を決して増加させない場合にのみ安全です。すべてのために、すなわち、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

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

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


何らかの理由で1<>トリックがうまくいかない場合は、

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

また、行を削除している限り、必要なのはgrep -v myregexBarmarによって指摘されているように)だけであることに注意してください


-3

sed -i 'd' / path / to / file / filename


1
こんにちは!ソリューションがどのように機能し、質問に答えるかに関連する限り詳細に説明するのが最善です。
dhag

2
これはひどい無回答です。(a)元のコマンドと同じように、ファイルシステム全体で失敗します。(b)成功した​​場合、正規表現に一致する行だけではなく、WHOLEファイルが空になります。
ワイルドカード2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.