我试图同时读取两个文件(名称,编号(,并获取每个可能的对的值。两个文件是这样的:
*name1
John
*name2
Paul
*number1
25
*number2
45
我要获得的标签和结果如下:
*name1 *number1 John 25
*name2 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45
由于我来自Python,因此我尝试使用这样的两个循环进行操作:
name=/home/davide/name.txt
number=/home/davide/number.txt
while read name; do
if [[ ${name:0:1} == "*" ]]; then
n=$(echo $name)
else
while read number; do
if [[ ${number:0:1} == "*" ]]; then
echo $number $n
else
echo $name $number
fi
done < $number
fi
done < $name
我有前两对,所以我的猜测是我需要一个命令才能从数字的开头开始(例如python上的seek(0((,但是我没有找到类似的bash。我也会得到"模棱两可的重定向"错误,但我不明白为什么。
设置输入文件后:
printf >name.txt '%sn' '*name1' John '*name2' Paul
printf >number.txt '%sn' '*number1' 25 '*number2' 45
...以下代码:
#!/usr/bin/env bash
name_file=name.txt
number_file=number.txt
while IFS= read -r name1 && IFS= read -r value1; do
while IFS= read -r name2 && IFS= read -r value2; do
printf '%sn' "$name1 $name2 $value1 $value2"
done <"$number_file"
done <"$name_file"
...正确输出:
*name1 *number1 John 25
*name1 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45
什么改变了?
- 我们在文件名中都停止使用
name
和number
,以及从它们中读取的值。因此,当您运行<$number
时,它在第一次迭代后不再带有文件名number.txt
。同样适用于$name
。 - 我们开始引用所有扩展(
"$foo"
,而不是$foo
(。请参阅http://shellcheck.net/警告SC2086,并查看Bashpitfalls#14,解释了为什么即使echo $foo
也很麻烦。 - 使用
-r
参数运行read
,将IFS
设置为空值,可以防止其消耗字面的后斜切或修剪领先和拖延新线。 - 在每个
while
循环的条件内使用两个read
s,让我们一次从每个文件中读取两行(如果适用,给定意图成对处理内容(。
bash在"流"上更简单地运行,而不是在数据本身上进行。
- 从第一个开始,第一个新的新线替换为制表或空间或其他分离器
- 然后将文件"粘贴"在一起
- 然后从
*name1 John *number1 25
到*name1 *number1 John 25
cat >name.txt <<EOF
*name1
John
*name2
Paul
EOF
cat <<EOF >number.txt
*number1
25
*number2
45
EOF
paste <(<name.txt sed 'N;s/n/t/') <(<number.txt sed 'N;s/n/t/') |
awk '{print $1,$3,$2,$4}'
将输出:
*name1 *number1 John 25
*name2 *number2 Paul 45
首先,在您的示例中,您覆盖变量$number
。因此,您从第二个循环运行开始读取文件$number
时遇到问题。
粘贴的解决方案
命令paste
可以结合多个文件,并与选项-d
划线。
#!/usr/bin/env bash
name=/home/davide/name.txt
number=/home/davide/number.txt
# combine both files linb-by-line
paste $'-dn' "$name" "$number" |
while read nam
do
#after reading name to var 'nam', read number to var 'num':
read num
# print both
echo "$nam $num"
done
如果您想要选项卡或任何其他分离器,并且没有其他处理,则不需要时循环。示例
paste "$name" "$number"
paste -d: "$name" "$number"
paste -d| "$name" "$number"
$ cat tst.awk
NR==FNR {
if ( NR%2 ) {
tags[++numPairs] = $0
}
else {
vals[numPairs] = $0
}
next
}
!(NR%2) {
for (pairNr=1; pairNr<=numPairs; pairNr++) {
print prev, tags[pairNr], $0, vals[pairNr]
}
}
{ prev = $0 }
$ awk -f tst.awk number.txt name.txt
*name1 *number1 John 25
*name1 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45
在脚本中,您对文件路径和while-loop变量都使用变量name
。这会导致"模棱两可的重定向"误差。两行需要修复,例如:
name_file=/home/davide/name.txt
done < $name_file
无需在shell脚本中搜索(0(。只需再次处理文件,例如:
while read line ; do
echo "== $line =="
done < /some/file
while read line ; do
echo "--> ${line:0:1}"
done < /some/file
这比您可以seek()
的更真实的编程语言效率较低且灵活。但这是关于外壳脚本和编程之间的差异,优势和缺点。
顺便说一句:
n=$(echo $name)
...只是这样做的尴尬方式:
n=$name
当$name
包含诸如*
之类的特殊字符时,这可能会导致您的脚本的行为不可预测。而且由于$name
是从文本文件中读取的,因此这不太可能发生。(感谢查尔斯·达菲(Charles Duffy(提出这一点(