関連する質問はここ。
大容量ファイルを編集してみると、途中で数行を削除する必要があることがよくあります。削除する行を知っており、通常は次のことを行います。
sed "linenum1,linenum2 d" input.txt > input.temp
または、-iオプションを追加してインラインで実行することもできます。行番号を知っていますが、ストリームの編集を避け、特定の行だけを削除する命令がありますか? input.txtは最大50GBまで可能です。
答え1
ファイルのコピーを書き込まない場合は、次のようにファイル自体にファイルを書き込むことをお勧めします。
{
sed "$l1,$l2 d" < file
perl -le 'truncate STDOUT, tell STDOUT'
} 1<> file
バックアップコピーがないため危険です。
または、これを防ぐために、sed
manatworkのアイデアの一部を盗んでください。
{
head -n "$(($l1 - 1))"
head -n "$(($l2 - $l1 + 1))" > /dev/null
cat
perl -le 'truncate STDOUT, tell STDOUT'
} < file 1<> file
最初の項目が上書きされるため、この問題はまだ改善される可能性があります。l1-1これを行う必要はありませんが、これを避けることは、perl
最終的に効率が低い可能性があるすべてのタスクを実行するなど、プログラミングにさらに参加することを意味します。
perl -ne 'BEGIN{($l1,$l2) = ($ENV{"l1"}, $ENV{"l2"})}
if ($. == $l1) {$s = tell(STDIN) - length; next}
if ($. == $l2) {seek STDOUT, $s, 0; $/ = \32768; next}
if ($. > $l2) {print}
END {truncate STDOUT, tell STDOUT}' < file 1<> file
次の出力から1000000〜1000050行のいくつかのタイミングを削除しますseq 1e7
。
sed -i "$l1,$l2 d" file
:16.2秒- 最初のソリューション:1.25秒
- 2番目のソリューション:0.057秒
- 3番目のソリューション:0.48秒
< file
すべて同じ原則に従います。ファイルの2つのファイル記述子を開きます。 1つは読み取り専用モード(0)で略語forを使用し、もう1つは0< file
読み取り/書き込みモード(1)で(will be)を使用します。このファイル記述子は2つを指します。1<> file
<> file
0<> file
ファイル説明を開くそれぞれ電流があります。カーソル位置それらに関連するファイルから。
たとえば、2番目のソリューションでは、最初のソリューションはfd 0から行データをhead -n "$(($l1 - 1))"
読み取り、そのデータをfd 1に書き込みます。$l1 - 1
したがって、コマンドが終了すると、カーソルは2つのコマンドの間にあります。ファイル説明を開くfds 0と1に関連する項目は、3行目の先頭にあります$l1
。
次に、head -n "$(($l2 - $l1 + 1))" > /dev/null
同じhead
行を読みます。$l2 - $l1 + 1
ファイル説明を開くまだ接続されているfd 0を介して、fd 0のカーソルはその行の次の行の先頭に移動します$l2
。
ただし、fd 1はにリダイレクトされているため、/dev/null
fd 1を作成した後はカーソルを次に移動しません。ファイル説明を開く{...}
fd 1が指します。
したがって、起動時にcat
カーソルは次の位置にあります。ファイル説明を開くfd 0が指す位置は次の行の先頭にあり$l2
、fd 1のカーソルはまだ$l1
行3の先頭にあります。つまり、対応する2head
行目は入力では削除のためにスキップされますが、出力では削除されません。これで、最初の行は次の行で上書きされ、cat
このように続行されます。$l1
$l2
cat
fd 0のファイルの終わりに達すると返されます。ただし、fd 1はまだ上書きされていないファイルの場所を指します。このセクションは消える必要があり、ファイルの最後に移動された削除された行が占めるスペースに対応します。私たちに必要なのは、現在fd 1が指す正確な場所からファイルを切り取ることです。
これはftruncate
システムコールを介して行われます。残念ながら、これを実行できる標準のUnixユーティリティがないため、fd 1に関連する現在のカーソル位置をperl
提供することに依存しています。私たちはPerlのシステムコールインターフェイスをtell STDOUT
使ってこのオフセットからファイルを切り取りますftruncate
。truncate
head
3番目のソリューションでは、最初のコマンドのfd 1書き込みをシステムコールに置き換えますlseek
。
答え2
これは使いやすい方法ですsed
。明らかに、ファイルをストリーミングし(長いファイルには問題ありません)、簡単に一般化してより多くの作業を実行できます。しかし、あなたが望むならシンプルファイルを編集する方法所定の位置に、最も簡単な方法は以下を使用することed
ですex
。
(echo 10,31d; echo wq) | ed input.txt
無制限のサイズ(およびRAMが許可する1行)のファイルを処理することを保証するより良いアプローチは、ファイルをperl
内部で編集する次の1行のコードです。
perl -n -i -e 'print if $. < 10 || $. > 31' input.txt
説明する:
-n
:各行にスクリプトを適用します。他の出力は生成しません。
-i
:ファイルを所定の位置に編集します(-i.bck
バックアップ目的で)。
-e ...
:10〜31行を除くすべての行を印刷します。
答え3
50GiBを読み書きする必要がある場合〜する何をしても長い時間を投資してください。行の長さが固定されていない場合、または削除する行がどこにあるかを知る他の方法がない場合は、削除する最後の行までファイルを読み取ることができません。改行文字だけをカウントし、後でブロック全体をコピーするカスタムプログラムは少し速いかもしれませんが、sed(1)
これはボトルネックではないと確信しています。time(1)
時間がどのように割り当てられるかを理解するには、を使用してみてください。
答え4
ファイルをその場で編集したい場合、ほとんどのシェルツールは役に立ちません。これは、書き込み用にファイルを開くときに既存の内容を上書きせずに切り取り(>
)または追加()のみを選択できるためです。注目すべき例外です。バラより>>
dd
ファイルを適切に変更する方法はありますか?
export LC_ALL=C
lines_to_keep=$((linenum1 - 1))
lines_to_skip=$((linenum2 - linenum1 + 1))
deleted_bytes=$({ { head -n "$lines_to_keep"
head -n "$lines_to_skip" >&3;
cat
} <big_file | dd of=big_file conv=notrunc;
} 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek="$(($(wc -c <big_file) - $deleted_bytes))"
(警告:テストされていません!)