為什麼 awk 不忽略「空格」作為分隔符號?

為什麼 awk 不忽略「空格」作為分隔符號?

我的腳本有問題。

序幕 首先,我有一個 100 行文件列表,如下所示:

100;TEST ONE
101;TEST TWO
...
200;TEST HUNDRED

每行有 2 個參數。例如,第一行的參數是:「645」、「TEST ONE」。所以分號是分隔符號。

我需要將兩個參數放入兩個變數中。假設它將是 $id 和 $name。對於每一行,$id 和 $name 值都會不同。例如,對於第二行 $id = "646" 和 $name = "TEST TWO"。

之後,我需要取得範例檔案並將預定義關鍵字變更為 $id 和 $name 值。範例文件如下所示:

xxx is yyy

因此我想要 100 個具有不同內容的檔案。每個文件必須包含每一行的 $id 和 $name 資料。並且它必須由它的 $name 值命名。

有我的腳本:

#!/bin/bash -x
rm -f output/*

for i in $(cat list)
    do

        id="$(printf "$i" | awk -F ';' '{print $1}')"
        name="$(printf "$i" | awk -F ';' '{print $2}')"

        cp sample.xml output/input.tmp

        sed -i -e "s/xxx/$id/g" output/input.tmp
        sed -i -e "s/yyy/$name/g" output/input.tmp

        mv output/input.tmp output/$name.xml


    done

所以,我只是嘗試逐行讀取我的列表文件。對於每一行,我都會獲得兩個變量,然後使用它們替換範例檔案中的關鍵字(xxx 和 yyy),然後儲存結果。

但出了點問題

結果我只有 1 個輸出檔。而且調試看起來很糟。

這是調試窗口,我的列表文件中只有 2 行。我只得到一個輸出檔。檔案名稱只是“TEST”,它包含一個字串:“101 is TEST”。

需要兩個檔案:“測試一”、“測試二”,並且必須包含“100 是測試一”和“101 是測試二”。

偵錯截圖

如您所看到的,第二個變數中有一個空格(例如“TEST ONE”)。我認為這個問題與空間特殊符號有關,但我不知道為什麼。我將 -F awk 參數設為“;”,因此 awk 必須僅將分號解釋為分隔符號!

我做錯了什麼?

答案1

如果我理解正確的話,您可以使用 while 循環和變數擴展

while IFS= read -r line; do 
  id="${line%;*}"
  name="${line#*;}"
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

正如@steeldriver 所提議的,這是一個(更優雅的)選項:

while IFS=';' read -r id name; do 
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

答案2

引用!這一行的引用遺失了:

mv output/input.tmp output/$name.xml

它應該是:

mv output/input.tmp output/"$name".xml

以避免檔案名稱帶有空格的問題。

並且, 的展開$(cat list)被外殼分割(和成團),這也打破了空間。

也許你可以更改為這個腳本:

#!/bin/bash -x
rm -f output/*

inputfile=output/input.tmp

while read -r line
do
    id=${line%%;*}
    name=${line##*;}

    cp sample.xml "$inputfile"
    sed -i -e "s/xxx/$id/g" "$inputfile"
    sed -i -e "s/yyy/$name/g" "$inputfile"
    mv "$inputfile"  output/"$name".xml; echo

done <list

答案3

awk 未產生預期結果的原因是您迭代文件的方式。當您使用 進行迭代時for i in $(cat file),您是在單字(由 IFS 分割)上迭代,而不是在行上迭代。若要逐行讀取文件,請使用while read

while read -r line; do
    ...
done < file

如需進一步閱讀,請參閱以下 bash 常見問題:如何逐行(和/或逐字段)讀取檔案(資料流、變數)?

答案4

作為一種替代方法,你可以用 awk 完成這項工作在 1 個進程中,而不是每行 4 個進程中。如果清單中有很多行但 example.xml 很小,這很可能是有益的。

awk -F';' 'FNR==NR{x=x $0 RS; next} 
{t=x; gsub(/xxx/,$1,t); gsub(/yyy/,$2,t); f="output/"$2".xml"; printf "%s",t >f; close(f)}
' sample.xml list
# shown with unnecessary linebreaks for clarity, but you can put it all on one line

如果清單具有 CRLF 行結尾(又稱 DOS 或 Windows 格式),如您的 Q 上所註釋的那樣,並且您不能(輕鬆)或不想先刪除它們,awk 也可以處理它;就在第二次{插入之後sub(/\r$/,"",$0);(或者$2如果您願意的話)。

perl 也可以做到這一點(perl 幾乎可以完成 awk 可以做的所有事情),但稍微冗長一些,雖然 perl 很常用,但它不像 awk 那樣是 POSIX。

相關內容