![为什么 awk 不忽略“空格”作为分隔符?](https://linux22.com/image/170659/%E4%B8%BA%E4%BB%80%E4%B9%88%20awk%20%E4%B8%8D%E5%BF%BD%E7%95%A5%E2%80%9C%E7%A9%BA%E6%A0%BC%E2%80%9D%E4%BD%9C%E4%B8%BA%E5%88%86%E9%9A%94%E7%AC%A6%EF%BC%9F.png)
我的脚本有问题。
序幕 首先,我有一个 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。