如何使用 while 循环读取两个输入文件

如何使用 while 循环读取两个输入文件

我想知道是否有任何方法可以在嵌套 while 循环中一次读取两个输入文件。例如,假设我有两个文件FileAFileB.

文件A:

[jaypal:~/Temp] cat filea
this is File A line1
this is File A line2
this is File A line3

文件B:

[jaypal:~/Temp] cat fileb
this is File B line1
this is File B line2
this is File B line3

当前示例脚本:

[jaypal:~/Temp] cat read.sh 
#!/bin/bash
while read lineA
    do echo $lineA 
    while read lineB
        do echo $lineB 
        done < fileb
done < filea

执行:

[jaypal:~/Temp] ./read.sh 
this is File A line1
this is File B line1
this is File B line2
this is File B line3
this is File A line2
this is File B line1
this is File B line2
this is File B line3
this is File A line3
this is File B line1
this is File B line2
this is File B line3

问题和期望的输出:

对于 FileA 中的每一行,这将完全循环 FileB。我尝试使用 continue、break、exit,但它们都不是为了实现我正在寻找的输出。我希望脚本仅读取文件 A 中的一行,然后读取文件 B 中的一行,然后退出循环并继续读取文件 A 的第二行和文件 B 的第二行。类似于以下脚本的内容 -

[jaypal:~/Temp] cat read1.sh 
#!/bin/bash
count=1
while read lineA
    do echo $lineA 
        lineB=`sed -n "$count"p fileb`
        echo $lineB
        count=`expr $count + 1`
done < filea

[jaypal:~/Temp] ./read1.sh 
this is File A line1
this is File B line1
this is File A line2
this is File B line2
this is File A line3
this is File B line3

这可以用 while 循环来实现吗?

答案1

如果您可以保证某些字符永远不会出现在第一个文件中,那么您可以使用粘贴。

例如,您确信这@永远不会发生:

paste -d@ file1 file2 | while IFS="@" read -r f1 f2
do
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done

请注意,只要保证该字符不会出现在第一个文件中就足够了。这是因为填充最后一个变量时read会忽略。IFS因此,即使@发生在第二个文件中,它也不会被分割。

使用一些 bash 功能来获得可以说是更干净的代码的示例,并使用默认分隔符选项卡进行粘贴:

while IFS=$'\t' read -r f1 f2
do
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done < <(paste file1 file2)

使用的 Bash 功能:ANSI C 字符串$'\t') 和流程替代<(...)) 到避免子 shell 中的 while 循环问题

如果您不能确定任何字符都不会出现在两个文件中,那么您可以使用两个文件描述符

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done 3<file1 4<file2

没有经过太多测试。可能会在空行处中断。

文件描述符编号 0、1 和 2 已分别用于 stdin、stdout 和 stderr。 3 及以上的文件描述符(通常)是免费的。 bash 手册警告不要使用大于 9 的文件描述符,因为它们是“内部使用的”。

请注意,打开的文件描述符被继承给 shell 函数和外部程序。继承打开文件描述符的函数和程序可以读取(和写入)文件描述符。在调用函数或外部程序之前,您应该注意关闭所有不需要的文件描述符。

这是与上面相同的程序,其中实际工作(打印)与元工作(并行从两个文件中逐行读取)分开。

work() {
  printf 'f1: %s\n' "$1"
  printf 'f2: %s\n' "$2"
}

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  work "$f1" "$f2"
done 3<file1 4<file2

现在我们假装我们无法控制工作代码,并且该代码无论出于何种原因都会尝试从文件描述符 3 中读取。

unknowncode() {
  printf 'f1: %s\n' "$1"
  printf 'f2: %s\n' "$2"
  read -r yoink <&3 && printf 'yoink: %s\n' "$yoink"
}

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  unknowncode "$f1" "$f2"
done 3<file1 4<file2

这是一个示例输出。请注意,第一个文件中的第二行是从循环中“窃取”的。

f1: file1 line1
f2: file2 line1
yoink: file1 line2
f1: file1 line3
f2: file2 line2

以下是在调用外部代码(或任何与此相关的代码)之前应如何关闭文件描述符的方法。

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  # this will close fd3 and fd4 before executing anycode
  anycode "$f1" "$f2" 3<&- 4<&-
  # note that fd3 and fd4 are still open in the loop
done 3<file1 4<file2

答案2

在不同的地方打开这两个文件文件描述符。将内置的输入重定向read到您想要连接的文件的描述符。在 bash/ksh/zsh 中,您可以编写read -u 3而不是read <&3.

while IFS= read -r lineA && IFS= read -r lineB <&3; do
  echo "$lineA"; echo "$lineB"
done <fileA 3<fileB

当最短的文件被处理完后,这个片段就会停止。看将两个文件读入 IFS while 循环——在这种情况下有没有办法获得零差异结果?如果您想继续处理直到两个文件结束。

也可以看看什么时候会使用额外的文件描述符?有关文件描述符的更多信息,以及为什么如此频繁地使用“while IFS= read”,而不是“IFS=;”在阅读时..`?的解释IFS= read -r

答案3

尝试以下命令:

paste -d '\n' inp1.txt inp2.txt > outfile.txt

答案4

或者,我想您可以使用 bash 的 mapfile 命令将文件放入数组变量中,将文件的每一行绑定到 array[line_of_file_index] 中。但是,我不确定它是否仅适用于 Bash3 更高版本或 Bash4。

http://wiki.bash-hackers.org/commands/builtin/mapfile

相关内容