Text-Processing

處理大記錄/段落

  • July 12, 2021

我有一個大文本文件(300 MB),其中包含\n\n作為分隔符的記錄。每行都是一個欄位,以一個數字(欄位標記/名稱)開頭,後跟一個 TAB 字元和欄位內容/值:

110    something from record 1, field 110
149    something else
111    any field could be repeatable
111    any number of times
120    another field

107    something from record 2, field 107
149    fields could be repeatable
149    a lot of times
149    I mean a LOT!
130    another field

107    something from record 3
149    something else

每條記錄不應大於 100 KB。

通過從這些記錄/“段落”中刪除行尾獲取其長度,我可以找到一些有問題的記錄(大於限制):

cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"

我正在嘗試找到一種方法來處理這些無效記錄,或者:

  • 刪除大於限制的記錄。
  • 刪除所有出現的特定已知問題標籤(上例中的 149 個)。如果刪除所有以 149 欄位開頭的行,則沒有記錄將超過限制的假設是可以接受的。

可能,刪除足夠多的特定欄位/標籤以適應限制應該有一個完整的腳本。最好先刪除最後一個。

這與稱為ISO 2709的古老圖書管理員文件格式有關。

另一種awk方法:

awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
{while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file

149如果記錄長度超過變數中指定的限制,這將逐漸刪除行lim,通過用“無”替換它們,直到保持限製或不再可能減少(由實際替換的數量表示為 0)。然後它將僅列印最終長度小於限制的記錄。

**缺點:**它將刪除149從第一行開始的行,因此如果它們構成連續文本的單個元素,則該文本將變得有些難以理解。

**注意:**指定RS=""而不是顯式RS="\n\n"是在“段落模式”中使用的可移植方式,因為POSIX 規範沒有定義awk多字元的行為。RS但是,如果您的文件中可能有空記錄,它們將被忽略awk,因此不會出現在輸出中。如果這不是您想要的,您可能不得不使用顯式RS="\n\n"表示法 - 大多數awk實現會將其視為正則表達式,並執行“天真”期望的操作。

如果您只想跳過有問題的記錄:

awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file

這將列印每條少於或等於 100k 個字元的記錄。

如果記錄太大,要刪除以特定正整數開頭的欄位:

awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
   length <= 100*1000 { print; next }
   {
       # This is a too long record.
       # Re-create it without any fields whose first tab-delimited
       # sub-field is the number in the variable number.

       # Split the record into an array of fields, a.
       nf = split($0,a)

       # Empty the record.
       $0 = ""

       # Go through the fields and add back the ones that we
       # want to the output record.
       for (i = 1; i <= nf; ++i) {
           split(a[i],b,"\t")
           if (b[1] != number) $(NF+1) = a[i]
       }

       # Print the output record.
       print
   }' file

就像以前一樣,這會列印簡短的記錄。對於第一個以製表符分隔的子欄位為數字number(此處在命令行中為 149)的所有欄位,較長的記錄將被刪除。

對於大型記錄,重新創建記錄時不包含我們不想要的欄位。內部循環通過拆分選項卡上的欄位並附加其第一個以製表符分隔的子欄位不是的欄位來重新創建輸出記錄number

for (i = 1; i <= nf; ++i) {
   split(a[i],b,"\t")
   if (b[1] != number) $(NF+1) = a[i]
}

由於 POSIX 規範awk留下了當您在RS未指定中具有多字元值時會發生的情況(大多數實現將其視為正則表達式),您可以使用RS=""; ORS="\n\n"而不是ORS=RS="\n\n"在使用嚴格符合的awk實現時。如果您這樣做,請注意數據中的多個空行將不再分隔空記錄。

引用自:https://unix.stackexchange.com/questions/603913