web-dev-qa-db-ja.com

2つのファイルの行を並列にループする

私が作成しているスクリプトの目的は、2つのシリーズのファイルを比較することです。ファイル名自体は、1行に1つのパスを持つ2つの個別のファイルに保存されます。私の考えは、2つのwhile readループ、ファイル名のリストごとに1つですが、2つのループをどのように混合できますか?

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

2つのwhile読み取りループを介して、2つの異なるリストのファイルを同時に比較できるようにする必要があります...これは可能ですか?

18
mkrouse

2つのループは必要ありません。 1つのループで2つのファイルから読み取る必要があるだけです。

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
20
psusi

方法1:知っていることを使用する

1つのファイルをループする方法をすでに知っているので、ファイルを結合して、結合されたファイルを処理することができます。コマンド paste は、2つのファイルを1行ずつ結合します。 2つのファイルからの行の間にタブを配置するため、このソリューションでは、ファイル名にタブがないと想定しています。 (区切り文字は変更できますが、ファイル名に存在しない文字を見つける必要があります。)

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
  …

2つのファイルの長さが異なる場合は、空の$file2が返されます(どのリストが最初に終了したかに関係なく)。

方法2:2つのファイルをループする

Whileループの条件に、好きなだけ複雑なコマンドを入れることができます。 read file1 <&3 && read file2 <&4を指定すると、両方のファイルに読み取る行がある限り、つまり1つのファイルがなくなるまでループが実行されます。

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

空白行をスキップする場合は、2つのファイルを個別にスキップする必要があるため、少し複雑になります。簡単な方法は、問題を2つの部分に分けることです。1つのファイルから空白行をスキップして、空白以外の行を処理します。空白行をスキップする1つの方法は、上記のように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を呼び出すことで機能します。関数である必要はありませんが、コードを整理するためと、そのコードの一部を2回呼び出す必要があるため、関数は最良のアプローチです。関数では、${!#}は、名前がVARIABLEの値である変数の値に評価されるbash構文${!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

1つのアプローチは、read -raだけではなくreadfilestoCompare.txtには2つの列があり、それぞれにファイル名、read -raは両方の列を同時に読み取り、それらを配列compareFileに割り当てます。この配列にアクセスすると、whileループを介して毎回、インデックス0が最初のファイルになり、インデックス1が2番目のファイルになります。

このファイルがあるとしましょう: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

2つのファイルが実際に次のような個別のファイルである場合:

#list1
file1
file2
file3

#list2
file4
file5
file6

これらは、次のようにpasteコマンドで結合できます。

$ paste list1 list2 > list1and2

List1and2の内容は次のとおりです。

$ cat list1and2
file1   file4
file2   file5
file3   file6
1
slm