Text-Processing

零/空分隔符打破列命令

  • April 11, 2021

問題

我想解析一些結構為行(\n分隔)的數據,欄位由NUL字元分隔\0

許多 linux 命令使用諸如--zeroforfind-0for之類的選項來處理此分隔符,或者xargs將分隔符定義為\0for gawk

我沒能理解如何將column解釋NUL作為分隔符。

例子

如果您生成以下數據集(2 行 3 列,用 分隔\0):

echo -e "line1\nline2" | awk 'BEGIN {OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}'

您將獲得預期的以下輸出(\0不會顯示分隔符,而是分隔每個欄位):

line1columnAline1columnBline1columnC
line2columnAline2columnBline2columnC

但是,當我嘗試使用 column 來顯示我的列時,儘管通過\0了 ,但由於某種原因,輸出僅顯示第一列:

echo -e "line1\nline2" \ | awk 'BEGIN {FS="\0"; OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}' | column -s '\0'
line1columnA    line2columnA

實際上,即使不提供分隔符,列似乎也會在 nul 字元上中斷:

echo -e "line1\nline2" \ | awk 'BEGIN {FS="\0"; OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}' | column
line1columnA    line2columnA

問題

  • 有沒有辦法在中\0用作欄位/列分隔符column
  • 可選/獎勵問題:為什麼列的行為是這樣的(\0如果不進行管理,我希望將完全忽略,並且將整行列印為單個欄位)?
  • 可選/獎勵問題 2:這些列中的一些數據將是文件路徑,我想將其\0用作最佳實踐。您是否有更好的做法來建議在文件中儲存“隨機字元串”而不必轉義它們可能包含的潛在衝突欄位分隔符?

有沒有辦法在 column 中使用 \0 作為欄位/列分隔符?

不,因為column(我知道)的兩種實現,即歷史上的 BSDutil-linux 包中的一種,都使用標準 C 庫的字元串操作函式來解析輸入行,並且這些函式在假設下工作該字元串是 NUL 終止的。換句話說,一個 NUL 字節意味著總是標記任何字元串的結尾。

可選/獎勵問題:為什麼列的行為是這樣的(如果不進行管理,我希望 \0 將被完全忽略,並且整行將作為單個欄位列印)?

除了我上面解釋的內容之外,請注意該選項-s需要文字字元。它不會解析類似的轉義語法\0(也不會\n如此)。這意味著您告訴column將 a\和 a0作為其輸入的有效分隔符。

$''如果您使用支持它的眾多 shell 之一(例如,它在 中可用bash 但在 中不可用),則可以通過字元串語法提供轉義序列dash。因此,例如column -s $'\n',如果由這些 shell 之一執行,它將是有效的(指定 <newline> 作為列分隔符)。

作為旁注,我不清楚您對column. 即使它確實支持 NUL 作為分隔符,它也會將該輸入的每一行轉換為輸出的一整列。也許您還想使用-t以便為每行的單個欄位分列?

可選/獎勵問題 2:這些列中的一些數據將是文件路徑,我想使用 \0 作為最佳實踐。您是否有更好的做法來建議在文件中儲存“隨機字元串”而不必轉義它們可能包含的潛在衝突欄位分隔符?

我所知道的唯一一種方法是在每個欄位前面加上它的長度,以你認為合適的文本或二進製表示。但是,您肯定無法將它們通過管道傳輸到column.

此外,如果您關心的是文件路徑,那麼您應該考慮不要\n其用作“結構”分隔符,因為這對於文件名來說是一個完全有效的字元。

就像概念驗證一樣,基於您的範例,但使用 NUL 作為結構/記錄分隔符和指定長度的欄位:(我還對您的範例字元串進行了一些處理以涉及多字節字元)

echo -e 'line1\nline2 ò' \ | LC_ALL=C awk '
   BEGIN {
       ORS="\0"
# here we just move arguments away from ARGV
# so that awk reads input from stdin
       for (i in ARGV) {
           c[i]=ARGV[i]
           delete ARGV[i]
       }
   }
   {
# first field is the line read
       printf "%4.4d%s", length, $0
# then a field for each argument
       for(i=1; i&lt;length(c); i++)
           printf "%4.4d%s", length(c[i]), c[i]
       printf "%s", ORS
   }
' "€ column A" $'colu\nmnB' "column C"

使用參數awk傳遞任意數量的任意列字元串。

然後,一個假設的對應腳本awk(實際上必須是gawkmawk要處理RS="\0"):

LC_ALL=C awk '
   BEGIN { RS="\0" }
   {
       nf=0; while(length) {
           field_length = substr($0, 1, 4)
           printf "field %d: \"%s\""ORS, ++nf, substr($0, 5, field_length)
           $0 = substr($0, 5+field_length)
       }
       printf "%s", ORS
   }
'

請注意,為兩個腳本指定相同的語言環境以匹配字元大小非常重要。指定LC_ALL=C兩者都很好。

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