并行循环浏览两个文件的行


18

我正在编写的脚本的目的是比较两个系列的文件。文件名本身存储在两个单独的文件中,每行一个路径。我的想法是有两个while read循环,每个文件名列表一个,但是如何将两个循环混在一起?

while read compareFile <&3; do     
 if [[ ! $server =~ [^[:space:]] ]] ; then  #empty line exception
    continue
 fi   
    echo "Comparing file - $compareFile"
 if diff "$compareFile" _(other file from loop?_) >/dev/null ; then
     echo Same
 else
      echo Different
 fi 
done 3</infanass/dev/admin/filestoCompare.txt

我需要能够通过两个while读取循环同时比较两个不同列表中的文件……这甚至可能吗?


您是否打算使此脚本成为挑战之王?如果不是这样,那么已经存在功能强大的工具来比较文件,例如diff
lgeorget

有点挑战”,对不起
lgeorget13年

@lgeorget OP正在使用diff
terdon

嗯,文件来自两个清单。抱歉,无用的评论……
lgeorget

请避免交叉发布
iruvar

Answers:


20

您不需要两个循环。您只需要在一个循环中读取两个文件。

while read compareFile1 <&3 && read compareFile2 <&4; do     
 if [[ ! $server =~ [^[:space:]] ]] ; then  #empty line exception
    continue
 fi   
    echo "Comparing file - $compareFile"
 if diff "$compareFile1" "$compareFile2" >/dev/null ; then
     echo Same
 else
      echo Different
 fi 
done 3</infanass/dev/admin/filestoCompare.txt 4<other_file

多数民众赞成更多更少的代码,谢谢!我如何同时处理两个循环的空行异常?
mkrouse

@mkrouse,我之前不知道您在使用该$ server变量做什么,但是但是您在一个变量上测试了空行,而在另一个变量上执行了同样的操作……
psusi 2013年

7

方法1:使用您所知道的

由于您已经知道如何循环播放一个文件,因此可以合并文件然后处理合并的文件。该命令paste逐行连接两个文件。它在两个文件的行之间放置一个选项卡,因此此解决方案假定您的文件名中没有选项卡。(您可以更改分隔符,但必须找到文件名中不存在的字符。)

paste -- "$list1.txt" "list2.txt" |
while IFS=$'\t' read -r file1 file2 rest; do
  diff -q -- "$file1" "$file2"
  case $? in
    0) status='same';;
    1) status='different';;
    *) status='ERROR';;
  esac
  echo "$status $file1 $file2"
done

如果要跳过空白行,则需要在每个文件中分别进行处理,因为这paste可能会使一个文件中的空白行与另一个文件中的非空白行匹配。您可以grep用来过滤非空白行。

paste -- <(grep '[^[:space:]]' "$list1.txt") <(grep '[^[:space:]]' "list2.txt") |
while IFS=$'\t' read -r file1 file2 rest; do
  

请注意,如果两个文件的长度不同,您将得到一个空文件$file2(无论哪个列表最先结束)。

方法2:循环两个文件

您可以在while循环的条件下将任意复杂的命令放入。如果放了,read file1 <&3 && read file2 <&4则循环将一直运行,只要两个文件都有一行要读取,即直到一个文件用完。

while read -u 3 -r file1 && read -u 4 -r file2; do
  
done 3<list1..txt 4<list2.txt

如果要跳过空白行,则要稍微复杂一点,因为必须分别在两个文件中进行跳过。一种简单的方法是将问题分为两部分:跳过一个文件中的空白行,并处理非空白行。跳过空白行的一种方法是grep如上所述进行处理。当心<重定向操作符和<(起始命令之间的必要空间。

while read -u 3 -r file1 && read -u 4 -r file2; do
  
done 3< <(grep '[^[:space:]]' "$list1.txt") 4< <(grep '[^[:space:]]' "list2.txt")

另一种方法是编写行为类似于read但跳过空行的函数。该函数可以通过read循环调用来工作。它不必一定是一个函数,但是函数是最好的方法,既可以组织代码,又可以使该代码段被调用两次。在函数中,${!#}是bash构造的一个实例,该实例的${!VARIABLE}计算结果为名称为的变量的值VARIABLE;这里的变量是特殊变量#,它包含位置参数的数量,${!#}最后一个位置参数也是。

function read_nonblank {
  while read "$@" &&
        [[ ${!#} !~ [^[:space:]] ]]
  do :; done
}
while read_nonblank -u 3 -r file1 && read_nonblank -u 4 -r file2; do
  
done 3<list1..txt 4<list2.txt

我喜欢使用read -u选项
Felipe Alvarez 2013年

1

一种方法是使用read -ra而不是read。假设其中filestoCompare.txt包含两列,每列各有一个文件名,read -ra则会同时读取两列,并将它们分配到一个数组中compareFile。然后可以访问该数组,以便每次while循环时索引0将是第一个文件,索引1将是第二个文件。

说我有这个文件:filestoCompare.txt,它包含以下内容:

file1 file2
file3 file4
file5 file6

通过该文件的命令如下:

$ while read -ra a ; do printf "%s\t%s\n" ${a[0]} ${a[1]}; done < filestoCompare.txt
file1   file2
file3   file4
file5   file6

如果这两个文件确实是单独的文件,例如:

#list1
file1
file2
file3

#list2
file4
file5
file6

可以使用以下paste命令将它们连接在一起:

$ paste list1 list2 > list1and2

这是list1and2的内容​​:

$ cat list1and2
file1   file4
file2   file5
file3   file6

但这不是输入格式:列表位于两个不同的文件中。你可以join先。
吉尔(Gilles)'所以

@Gilles-我知道这不是输入格式,我相信我甚至说过:“ ...假设filestoCompare.txt包含2列,每列中都有文件名...”。我了解您的主张,也不同意。自发布以来,OP尚未就此问题提供任何进一步的指导。
slm

@Gilles-如果我添加一个示例,说明如何使用命令paste将2个文件联接在一起,该怎么办?那会让你退票吗?
slm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.