
私は最近尋ねました質問特定の文字の後に改行文字が出現した場合にその改行文字を削除する方法について説明します。
Unix のテキスト処理ツールは非常に強力ですが、そのほとんどがテキスト行を処理するため、入力が使用可能なメモリに収まる場合はほとんどの場合問題ありません。
しかし、改行を含まない巨大なファイル内のテキストシーケンスを置き換えたい場合はどうすればよいでしょうか?
たとえば、入力を 1 行ずつ読み取らずに、 を置き換えますか? (行は 1 つだけで、長さは 2.5G 文字であるため) <foobar>
。\n<foobar>
答え1
この種の問題に直面したときに最初に思いつくのは、レコード区切り文字を変更することです。ほとんどのツールでは、これは\n
デフォルトで設定されていますが、変更することができます。例:
パール
perl -0x3E -pe 's/<foobar>/\n$&/' file
説明
-0
: これは、入力レコードセパレータを、その文字に設定された文字に設定します。16進数値>
. この場合、を の 16 進数値に設定します3E
。一般的な形式は です-0xHEX_VALUE
。これは、行を扱いやすいチャンクに分割するための単なるトリックです。-pe
: 指定されたスクリプトを適用した後、各入力行を出力します-e
。s/<foobar>/\n$&/
: 単純な置換。 は$&
一致したもので、この場合は です<foobar>
。
awk
awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
説明
RS="<"
: 入力レコード区切り文字を に設定します>
。gsub(/foobar>/,"\n<foobar>")
foobar>
:のすべてのケースを に置き換えます。 が に設定されている\n<foobar>
ため、すべてが入力ファイルから削除されることに注意してください (これが の動作の仕組みです)。そのため、なしで を一致させてに置き換える必要があります。RS
<
<
awk
foobar>
<
\n<foobar>
printf "%s",$0
: 置換後の現在の「行」を出力します。 は$0
の現在のレコードなのでawk
、 の前にあったものはすべて保持されます<
。
次のコマンドで作成された 2.3 GB の 1 行ファイルでテストしました。
for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file
awk
と はどちらもperl
ごくわずかな量のメモリを使用しました。
答え2
グサール (一般的な検索と置換)まさにこの目的に非常に役立つツールです。
この質問に対する回答のほとんどは、レコードベースのツールとさまざまなトリックを使用して、それらを問題に適応させます。たとえば、デフォルトのレコード区切り文字を、各レコードが処理できないほど大きくならないように、入力で頻繁に発生すると想定される文字に切り替えるなどです。
awk
多くの場合、これは非常に優れており、読みやすいです。 、、Bourne Shellなどtr
、どこでも利用できるツールで簡単かつ効率的に解決できる問題が好きです。sed
ランダムな内容を持つ任意の巨大なファイル内でバイナリ検索と置換を実行することは、これらの標準の Unix ツールにはあまり適していません。
これを不正行為だと思う人もいるかもしれませんが、適切なツールを使用することが間違っているとは思えません。この場合、ライセンスgsar
がGPL v2なので、この非常に便利なツールのパッケージがどちらにも存在しないことに私はかなり驚いています。ジェンツー、レッドハット、 またはウブントゥ。
gsar
バイナリバリアントを使用するボイヤー・ムーア文字列検索アルゴリズム。
使い方は簡単です:
gsar -F '-s<foobar>' '-r:x0A<foobar>'
ここで、-F
は「フィルター」モード、つまりstdin
への読み取り書き込みを意味しますstdout
。ファイルを操作するメソッドもあります。 は-s
検索文字列と-r
置換を指定します。コロン表記を使用して任意のバイト値を指定できます。
大文字と小文字を区別しないモードはサポートされています ( -i
) が、アルゴリズムは検索文字列の長さを使用して検索を最適化するため、正規表現はサポートされていません。
このツールは、 と少し似て、検索のみに使用することもできますgrep
。gsar -b
は、一致した検索文字列のバイト オフセットを出力し、一致した場合はファイル名と一致数を出力します。 と組み合わせた場合とgsar -l
少し似ています。grep -l
wc
このツールはトルモド・ティアベリ(初期)およびハンス・ペーター・ヴェルネ(改善)。
答え3
対象文字列と置換文字列が同じ長さである狭いケースでは、メモリマッピング助けになることがあります。これは、置換をインプレースで実行する必要がある場合に特に便利です。基本的に、ファイルをプロセスの仮想メモリにマッピングしますが、64 ビット アドレス指定のアドレス空間は巨大です。ファイルは必ずしも一度に物理メモリにマップされるわけではないことに注意してください。そのため、マシンで使用可能な物理メモリのサイズの数倍のファイルも処理できます。
foobar
以下はPythonの例です。XXXXXX
#! /usr/bin/python
import mmap
import contextlib
with open('test.file', 'r+') as f:
with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
pos = 0
pos = m.find('foobar', pos)
while pos > 0:
m[pos: pos+len('XXXXXX')] = 'XXXXXX'
pos = m.find('foobar', pos)
答え4
Awk は連続するレコードに対して動作します。レコード区切り文字として任意の文字を使用できます (多くの実装では null バイトを除く)。実装によっては、レコード区切り文字として任意の正規表現 (空の文字列に一致しない) がサポートされていますが、レコード区切り文字は各レコードの末尾から切り捨てられて格納されるため扱いにくい場合があります$0
(GNU awk は、現在のレコードの末尾から切り捨てられたレコード区切り文字を変数に設定します)。は、出力を出力レコード区切り文字で終了するRT
ことに注意してください。これはデフォルトでは改行文字であり、入力レコード区切り文字とは独立して設定されます。print
ORS
RS
awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'
を使用して改行文字をその文字と交換することで、他のツールのレコード区切り文字として別の文字 ( sort
、 、…) を効果的に選択できます。sed
tr
tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'
多くの GNU テキスト ユーティリティは、区切り文字として改行の代わりに null バイトの使用をサポートしています。