将读取光标返回到文件的开始



我试图同时读取两个文件(名称,编号(,并获取每个可能的对的值。两个文件是这样的:

*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

什么改变了?

  • 我们在文件名中都停止使用namenumber,以及从它们中读取的值。因此,当您运行<$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(提出这一点(

相关内容

最新更新