
我最近問一個問題關於如何刪除出現在另一個特定字元之後的換行符。
Unix 文字處理工具非常強大,但幾乎所有工具都處理文字行,當輸入適合可用記憶體時,這在大多數情況下都很好。
但是,如果我想替換一個不包含任何換行符的大檔案中的文字序列,我該怎麼辦?
例如替換<foobar>
為\n<foobar>
而不逐行讀取輸入? (因為只有一行並且有 2.5G 個字元長)。
答案1
面對這類問題時,我首先想到的是更改記錄分隔符號。在大多數工具中,這是\n
預設的,但可以更改。例如:
珀爾
perl -0x3E -pe 's/<foobar>/\n$&/' file
解釋
-0
:這將輸入記錄分隔符號設定為給定的字符十六進位值。在本例中,我將其設定為>
十六進位值為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 單行檔案上進行了測試:
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
格薩爾 (一般搜尋和取代)正是用於此目的的一個非常有用的工具。
這個問題的大多數答案都使用基於記錄的工具和各種技巧來使它們適應問題,例如將預設的記錄分隔符切換為假設在輸入中經常出現的字符,以免每個記錄太大而無法處理。
在許多情況下,這是非常好的,甚至是可讀的。我確實喜歡可以使用隨處可用的工具(例如 、 和 bourne shell)輕鬆/有效地解決awk
的tr
問題sed
。
在具有隨機內容的任意大檔案中執行二進位搜尋和替換不太適合這些標準 UNIX 工具。
你們中的一些人可能認為這是作弊,但我不認為使用正確的工具來完成工作怎麼可能是錯的。在本例中,它是一個名為的 C 程序,gsar
其許可權為通用公共授權 v2,所以讓我感到非常驚訝的是,兩個版本中都沒有這個非常有用的工具的軟體包巴布亞紐幾內亞,紅帽,也不烏班圖。
gsar
使用二進位變體Boyer-Moore 字串搜尋演算法。
用法很簡單:
gsar -F '-s<foobar>' '-r:x0A<foobar>'
其中-F
表示“過濾”模式,即stdin
讀寫stdout
。也有對文件進行操作的方法。-s
指定搜尋字串和-r
替換字串。冒號表示法可用來指定任意位元組值。
支援不區分大小寫的模式 ( -i
),但不支援正規表示式,因為演算法使用搜尋字串的長度來最佳化搜尋。
該工具也可以僅用於搜索,有點像grep
.gsar -b
輸出匹配的搜尋字串的位元組偏移量,並gsar -l
列印文件名和匹配數(如果有),有點像grep -l
與wc
.
該工具的編寫者是托莫德·查伯格(初始)和漢斯·彼得·凡爾納(改進)。
答案3
在目標字串和替換字串長度相同的狹窄情況下,記憶體映射可以來救援。如果需要就地進行更換,這尤其有用。您基本上是將檔案對應到進程的虛擬記憶體中,並且 64 位元尋址的位址空間非常大。請注意,文件不一定會一次全部映射到物理記憶體中,因此可以處理數倍於機器上可用實體記憶體大小的檔案。
這是一個 Python 範例,替換foobar
為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 對連續的記錄進行操作。它可以使用任何字元作為記錄分隔符號(許多實作中的空位元組除外)。某些實作支援任意正規表示式(不匹配空字串)作為記錄分隔符,但這可能很麻煩,因為記錄分隔符在被存放之前從每個記錄的末尾被截斷$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 文字實用程式支援使用空位元組而不是換行符作為分隔符號。