まだ診断されていないアプリケーションのバグが原因で、ディスクがいっぱいのサーバーが数百台あります。重複する行で満たされたファイルが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
の行を削除できることをテストしました。一時ファイルを保持するために別のファイルシステムに頼ることなく、これを自動化することは可能であるようです...(?)
-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
これがsed
の仕組みです。 _-i
_(インプレース編集)と共に使用した場合、sed
は、処理されたファイルの新しい内容で一時ファイルを作成します。 sed
が終了すると、現在の作業ファイルを一時ファイルに置き換えます。ユーティリティはファイルin-placeを編集しません。それがすべてのエディターの動作です。
シェルで次のタスクを実行するようなものです。
_sed 'whatever' file >tmp_file
mv tmp_file file
_
この時点でsed
は、fflush()
システムコールを使用して、エラーメッセージに示されているファイルにバッファデータをフラッシュしようとします。
出力ストリームの場合、
fflush()
は、ストリームの基になる書き込み関数を介して、指定された出力または更新ストリームのすべてのユーザー空間のバッファーデータを強制的に書き込みます。
あなたの問題については、別のファイルシステム(たとえば、十分なメモリがある場合はtmpfs
、または外部ストレージデバイス)をマウントして、そこにいくつかのファイルを移動し、そこで処理して、元に戻す解決策を見つけました。 。
この質問を投稿してから、ex
はPOSIX準拠のプログラムであることを学びました。それはほぼ普遍的にvim
にシンボリックリンクされていますが、どちらにしても、(POSIX仕様から取られた)ファイルシステムに関連するex
の重要なポイントは次のとおりです(私はそう思います)。
このセクションでは、edit bufferという用語を使用して、現在の作業テキストを説明します。この用語は特定の実装を意味するものではありません。すべての編集変更は編集バッファーで実行され、エディターコマンドがファイルを書き込むまで、変更はファイルに影響しません。
「... any ファイルに影響を与える...」ファイルシステムに何かを(一時ファイルでさえ)置くと、「すべてのファイルに影響を与える」と見なされると思います。多分?*
POSIX仕様のex
の注意深い調査は、オンラインで見られるex
の一般的なスクリプトでの使用( vim
固有のコマンド。)
+cmd
の実装は、POSIXではオプションです。-c
オプションを許可することもオプションです。:g
は、エスケープされていない次の改行まですべてを「食べます」(したがって、正規表現で一致が見つかるたびに、最後に一度ではなく実行されます)。したがって、-c 'g/regex/d | x'
はoneインスタンスのみを削除してからファイルを終了します。したがって、私が調べたところによると、特定の正規表現に一致するすべての行を削除するために完全なファイルシステム上のファイルをインプレース編集するPOSIX準拠の方法は次のとおりです:
ex -sc 'g/myregex/d
x' /path/to/file/filename
これは、ファイルをバッファにロードするのに十分なメモリがある場合に機能します。
*他に何かを示すものを見つけた場合は、コメントにその旨を記載してください。
オフセットまでのバイト数を取得でき、行が始点から終点まで発生する場合は、ファイルを非常に簡単に切り捨てることができます。
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}
に必要なスペースがある限り、完全に安全です。
一時ファイルを使用しないシェルはパイプを使用するため、この方法を使用しても安全ではありません。これらのシェルは通常、ash
、busybox
、BSD dash
-sh
、zsh
、bash
、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
他の回答で述べたように、sed -i
は、ファイルを新しいファイルにコピーします同じディレクトリ内、プロセスに変更を加え、新しいファイルを元のファイルに移動します。それが機能しない理由です。 ed
(元のラインエディター)はやや似た方法で機能しますが、前回チェックしたときは、スクラッチファイルに/tmp
を使用しています。 /tmp
がいっぱいのファイルシステムとは異なるファイルシステムにある場合、ed
が代わりに機能します。
これを試してください(インタラクティブなシェルプロンプトで)。
$ ed / path/to/file/filename P g /myregex/d w q
P
(capital P)は厳密には必要ありません。プロンプトをオンにします。それがなければ、あなたは暗闇の中で働いています、そして何人かの人々はこの当惑を見つけます。 w
およびq
はw 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 answer と this 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 myregex
( Barmar で指摘されているように)。