Bash:从多个文件重定向输入

作者:Mitch Frazier

最近我需要创建一个处理两个输入文件的脚本。我说的处理是指脚本需要从一个文件获取一行,然后从第二个文件获取一行,然后对它们进行一些操作。听起来很简单,但除非您了解 bash 的一些扩展重定向功能,否则并不那么容易。

为了这个例子,我们假设我们要实现一个简单的paste命令作为 bash 脚本。该paste命令从每个输入文件读取一行,然后将它们粘贴在一起,并将组合结果作为单行写入 stdout。我们的示例版本只对两个输入文件执行此操作。此外,它不会进行任何错误检查,并且会假定文件包含相同数量的行。

我们的输入文件,file1file2

  $ cat file1
  f1 1
  f1 2
  f1 3
  f1 4
  $ cat file2
  f2 1
  f2 2
  f2 3
  f2 4

您的第一个想法可能是这样的

#!/bin/bash

while read f1 <$1
do
    read f2 <$2
    echo $f1 $f2
done
如果您运行这个,您会发现它不太能完成任务
  $ sh paste-bad.sh file1 file2
  f1 1 f2 1
  f1 1 f2 1
  f1 1 f2 1
  f1 1 f2 1
  f1 1 f2 1
  f1 1 f2 1
  f1 1 f2 1
  ...
  Ctrl-C
这是因为此处的每个重定向都是重新开始的:它会重新打开文件并读取第一行,然后您会得到一个无限循环。

您的下一个想法可能是逐个读取文件,然后获取缓冲的数据并在之后将它们粘贴在一起

#!/bin/bash

i=0
while read line
do
    f1[$i]="$line"
    let i++
done <$1

i=0
while read line
do
    f2[$i]="$line"
    let i++
done <$2

i=0
while [[ "${f1[$i]}" ]]
do
    echo ${f1[$i]} ${f2[$i]}
    let i++
done
这行得通
  $ sh paste-ok.sh file1 file2
  f1 1 f2 1
  f1 2 f2 2
  f1 3 f2 3
  f1 4 f2 4
但是,如果您尝试做一些比粘贴行更复杂的事情,那么这种方法可能不可行,而且无论如何都很麻烦。

另一个解决方案是使用一些更高级的重定向

#!/bin/bash

while read f1 <&7
do
    read f2 <&8
    echo $f1 $f2
done \
    7<$1 \
    8<$2

在这个版本中,在循环结束时,我们使用 bash 输入重定向的完整通用形式来指定多个输入重定向[n]<word。如果没有指定前导 *[n]*,则默认为 0,这是正常的 stdin 重定向。但是,通过在重定向前面指定一个小整数,我们可以将多个输入文件重定向到命令,在本例中,命令是 *while* 循环

  ...
  done \
        7<$1 \
        8<$2
这会导致 "while" 循环执行,其中文件描述符 7 打开以读取第一个输入文件,文件描述符 8 打开以读取第二个输入文件。通常,您应该使用大于 2 的数字,因为 0-2 用于 stdin、stdout 和 stderr。

为了让read命令工作,我们需要使用 bash 重定向的另一种形式,在本例中,我们使用 bash 复制文件描述符的能力(如 C 库函数dup2())。文件描述符复制允许两个文件描述符引用同一个打开的文件。由于read通常从 stdin 读取,而不是文件描述符 7 或 8,我们需要一种在 stdin 上复制文件描述符 7(或 8)的方法,bash 的文件描述符复制正是这样做的

  while read f1 <&7
  ...
      read f2 <&8
  ...
请注意read还包括一个-u选项,用于指定要从中读取的文件描述符(如果您喜欢)。

Bash 包含类似形式的输出文件重定向。有关更多信息,请参阅 bash 手册页。

Mitch Frazier 是 Emerson Electric Co. 的嵌入式系统程序员。自 2000 年代初以来,Mitch 一直是Linux Journal 的贡献者和朋友。

加载 Disqus 评论