文字列置換のための非行指向ツール?

文字列置換のための非行指向ツール?

私は最近尋ねました質問特定の文字の後に改行文字が出現した場合にその改行文字を削除する方法について説明します。

Unix のテキスト処理ツールは非常に強力ですが、そのほとんどがテキスト行を処理するため、入力が使用可能なメモリに収まる場合はほとんどの場合問題ありません。

しかし、改行を含まない巨大なファイル内のテキストシーケンスを置き換えたい場合はどうすればよいでしょうか?

たとえば、入力を 1 行ずつ読み取らずに、 を置き換えますか? (行は 1 つだけで、長さは 2.5G 文字であるため) <foobar>\n<foobar>

答え1

この種の問題に直面したときに最初に思いつくのは、レコード区切り文字を変更することです。ほとんどのツールでは、これは\nデフォルトで設定されていますが、変更することができます。例:

  1. パール

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    説明

    • -0: これは、入力レコードセパレータを、その文字に設定された文字に設定します。16進数値>. この場合、を の 16 進数値に設定します3E。一般的な形式は です-0xHEX_VALUE。これは、行を扱いやすいチャンクに分割するための単なるトリックです。
    • -pe: 指定されたスクリプトを適用した後、各入力行を出力します-e
    • s/<foobar>/\n$&/: 単純な置換。 は$&一致したもので、この場合は です<foobar>
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    説明

    • RS="<": 入力レコード区切り文字を に設定します>
    • gsub(/foobar>/,"\n<foobar>")foobar>:のすべてのケースを に置き換えます。 が に設定されている\n<foobar>ため、すべてが入力ファイルから削除されることに注意してください (これが の動作の仕組みです)。そのため、なしで を一致させてに置き換える必要があります。RS<<awkfoobar><\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 ツールにはあま​​り適していません。

これを不正行為だと思う人もいるかもしれませんが、適切なツールを使用することが間違っているとは思えません。この場合、ライセンスgsarGPL v2なので、この非常に便利なツールのパッケージがどちらにも存在しないことに私はかなり驚いています。ジェンツーレッドハット、 またはウブントゥ

gsarバイナリバリアントを使用するボイヤー・ムーア文字列検索アルゴリズム

使い方は簡単です:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

ここで、-Fは「フィルター」モード、つまりstdinへの読み取り書き込みを意味しますstdout。ファイルを操作するメソッドもあります。 は-s検索文字列と-r置換を指定します。コロン表記を使用して任意のバイト値を指定できます。

大文字と小文字を区別しないモードはサポートされています ( -i) が、アルゴリズムは検索文字列の長さを使用して検索を最適化するため、正規表現はサポートされていません。

このツールは、 と少し似て、検索のみに使用することもできますgrepgsar -bは、一致した検索文字列のバイト オフセットを出力し、一致した場合はファイル名と一致数を出力します。 と組み合わせた場合とgsar -l少し似ています。grep -lwc

このツールはトルモド・ティアベリ(初期)およびハンス・ペーター・ヴェルネ(改善)。

答え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ことに注意してください。これはデフォルトでは改行文字であり、入力レコード区切り文字とは独立して設定されます。printORSRS

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

を使用して改行文字をその文字と交換することで、他のツールのレコード区切り文字として別​​の文字 ( sort、 、…) を効果的に選択できます。sedtr

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

多くの GNU テキスト ユーティリティは、区切り文字として改行の代わりに null バイトの使用をサポートしています。

関連情報