为什么 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。

相关内容