当我编写 shell 脚本时,我所做的大部分工作是将其他模块的 I/O 包装在 python、matlab 等中。为此,我通常使用文本文件或具有输入/输出路径的类似性质的文件。我知道从我可以使用的一个文件中读取一行,
for file in $(cat $1);
do
code using $file
done
但是如果我想使用两个文件中的等效行执行某些操作怎么办?类似于 Java 的等效项:
while((line1 = file1.readLine()) != null) {
line2 = file2.readLine();
//do something with both lines...
}
在 bash 中执行此操作的标准方法是什么?
答案1
exec 3<file1
exec 4<file2
while read line1 <&3 && read line2 <&4
do
echo "line1=$line1 and line2=$line2"
done
exec 3<&-
exec 4<&-
讨论
在上面,从输入行中去除了前导和尾随空白。如果你想保留这个空格,请替换
read …
为IFS= read …
在上面,输入中的反斜杠将被解释为转义字符。如果您不想这样做,请替换
read …
为read -r …
read line1 <&3
line1
从文件描述符 3读取。这也可以等效地写为read -u3 line1
。诸如此类的陈述
for file in $(cat $1);
有一些您应该了解的问题。 shell 会将分词路径名扩展应用于文件的内容,除非您期望如此,否则可能会导致各种错误。
选择
while read line1 <&3 && read line2 <&4
do
echo "line1=$line1 and line2=$line2"
done 3<file1 4<file2
答案2
要迭代文件的行:
while IFS= read -r line; do
echo "read $line"
done <input-file
要迭代多个文件,请在不同的文件描述符上打开它们(请参阅什么时候会使用额外的文件描述符?)。
while IFS= read -r line1 <&8 || IFS= read -r line2 <&9; do
echo "read '$line1' from file 1 and '$line2' from file 2"
done 8<input-file1 9<input-file2
使用read <&8 || read <&9
空行完成最短的文件以匹配最长的文件。要在到达任一文件末尾时立即退出,请使用&&
而不是||
.如果要检测所有情况,请单独检查返回码。
{
while
IFS= read -r line1 <&8; empty1=$?
IFS= read -r line2 <&9; empty2=$?
[ "$empty1" -ne 0 ] && [ "$empty2" -ne 0 ]
do
echo "read '$line1' from file 1 and '$line2' from file 2"
done
if [ "$empty1" -ne 0 ]; then
echo "Finishing processing file 1"
…
fi
if [ "$empty2" -ne 0 ]; then
echo "Finishing processing file 2"
…
fi
} 8<input-file1 9<input-file2
或者,您可以将两个文件连接在一起。这paste
命令对此很方便。默认情况下,它通过制表符分隔行(传递-d
以选择不同的分隔符)并用空行完成文件。如果文件不包含制表符,则会明确界定输入行。
tab=$(printf \\t)
paste input-file1 input-file2 |
while IFS=$tab read -r line1 line2; do … done
请注意,shell 进行文本处理的速度不是很快。更专业的工具最适合中型到大型输入。预处理paste
可以方便地将两个文件压缩在一起以进行任何后处理。如果您需要更多地控制何时读取行, awk 可以使用其getline
命令(类似于 shell 的read
)来做到这一点。