Faz loop através das linhas de dois arquivos em paralelo [closed]

17

O objeto do script que estou fazendo é comparar duas séries de arquivos. Os nomes dos arquivos são armazenados em dois arquivos separados, um caminho por linha. Minha idéia é ter dois while read loops, um para cada lista de nomes de arquivos, mas como posso misturar os dois loops juntos?

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

Eu preciso poder comparar arquivos de duas listas diferentes ao mesmo tempo por meio de dois loops de leitura ... Isso é possível?

    
por mkrouse 10.07.2013 / 21:39

3 respostas

19

Você não precisa de dois loops; você só precisa ler dois arquivos em um loop.

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
    
por 11.07.2013 / 15:29
7

Método 1: use o que você sabe

Como você já sabe como fazer o loop de um arquivo, é possível combinar os arquivos e processar os arquivos combinados. O comando paste une dois arquivos linha por linha. Ele coloca uma tabulação entre as linhas provenientes dos dois arquivos, portanto, esta solução assume que não há guias nos nomes dos arquivos. (Você pode alterar o separador, mas você precisa encontrar um caractere que não esteja presente em um nome de arquivo.)

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

Se você quiser pular linhas em branco, precisará fazer isso em cada arquivo separadamente, pois paste pode corresponder a uma linha em branco de um arquivo com uma linha não em branco de outro arquivo. Você pode usar grep para filtrar as linhas não em branco.

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

Observe que, se os dois arquivos tiverem comprimentos diferentes, você receberá um $file2 vazio (independentemente de qual lista terminou primeiro).

Método 2: faça um loop sobre dois arquivos

Você pode colocar um comando tão complexo quanto desejar na condição do loop while. Se você colocar read file1 <&3 && read file2 <&4 , o loop será executado contanto que os dois arquivos tenham uma linha para ler, ou seja, até que um arquivo acabe.

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

Se você quiser pular linhas em branco, é um pouco mais complicado, porque você tem que fazer o salto nos dois arquivos de forma independente. A maneira mais fácil é dividir o problema em duas partes: pule as linhas em branco de um arquivo e processe as linhas não em branco. Um método para pular as linhas em branco é processar através de grep como acima. Observe o espaço necessário entre o operador de redirecionamento < e o <( que inicia uma substituição de comando.

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

Outro método é escrever uma função que se comporta como read , mas ignora linhas em branco. Esta função pode funcionar chamando read em um loop. Não precisa ser uma função, mas uma função é a melhor abordagem, tanto para organizar seu código quanto porque essa parte do código precisa ser chamada duas vezes. Na função, ${!#} é uma instância da construção bash ${!VARIABLE} que avalia o valor da variável cujo nome é o valor de VARIABLE ; aqui a variável é a variável especial # que contém o número do parâmetro posicional, então ${!#} é o último parâmetro posicional.

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
    
por 12.07.2013 / 00:33
1

Uma abordagem seria usar read -ra em vez de apenas read . Supondo que filestoCompare.txt continha 2 colunas com os nomes de arquivo em cada, o read -ra leria as duas colunas ao mesmo tempo e as designaria em uma matriz, compareFile . Este array poderia então ser acessado de modo que o índice 0 seria o primeiro arquivo e o índice 1 seria o segundo arquivo a cada vez através do loop while .

Exemplo

Digamos que eu tenha este arquivo: filestoCompare.txt e contenha o seguinte:

file1 file2
file3 file4
file5 file6

O comando para passar por este arquivo seria o seguinte:

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

Se os 2 arquivos forem de fato arquivos separados, como:

#list1
file1
file2
file3

#list2
file4
file5
file6

Eles podem ser unidos com o comando paste da seguinte forma:

$ paste list1 list2 > list1and2

Aqui está o conteúdo de list1and2:

$ cat list1and2
file1   file4
file2   file5
file3   file6
    
por 10.07.2013 / 21:47