Como ler dois arquivos de entrada usando o loop while

24

Eu queria saber se há alguma maneira de ler de dois arquivos de entrada em um loop aninhado de uma linha por vez. Por exemplo, digamos que eu tenha dois arquivos FileA e FileB .

FileA:

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

FileB:

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

Script de amostra atual:

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

Execução:

[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

Problema e saída desejada:

Isso percorre o FileB completamente para cada linha no FileA. Eu tentei usar continuar, quebrar, sair, mas nenhum deles foi feito para alcançar a saída que estou procurando. Eu gostaria que o script para ler apenas uma linha do arquivo A e, em seguida, uma linha de FileB e sair do loop e continuar com a segunda linha do arquivo A e segunda linha do arquivo B. Algo semelhante ao seguinte script -

[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

É possível alcançar com o loop while?

    
por jaypal singh 12.12.2011 / 01:40

5 respostas

28

Se você tiver certeza de que algum caractere nunca ocorrerá no primeiro arquivo, poderá usar o comando colar.

Exemplo de colar usando a guia delimitador padrão:

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

Exemplo de colar usando @ :

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

Observe que é suficiente se o caractere tiver a garantia de não ocorrer no primeiro arquivo. Isso ocorre porque read ignorará IFS ao preencher a última variável. Portanto, mesmo que @ ocorra no segundo arquivo, ele não será dividido.

Exemplo de colar usando alguns recursos bash para um código indiscutivelmente mais limpo:

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

Recursos do Bash usados: ansi c string ( $'\t' ) e substituição de processos ( <(...) ) para evite o loop while em um problema de subshell .

Se você não pode ter certeza de que algum caractere nunca ocorrerá em ambos os arquivos, então você pode usar os descritores de arquivos .

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

Não testou muito. Pode quebrar em linhas vazias.

Os descritores de arquivo 0, 1 e 2 já são usados para stdin, stdout e stderr, respectivamente. Os descritores de arquivo de 3 e acima são (geralmente) gratuitos. O manual do bash avisa sobre o uso de descritores de arquivos maiores que 9, porque eles são "usados internamente".

Observe que os descritores de arquivos abertos são herdados para funções de shell e programas externos. Funções e programas que herdam um descritor de arquivo aberto podem ler (e gravar) o descritor de arquivo. Você deve tomar cuidado para fechar todos os descritores de arquivos que não são necessários antes de chamar uma função ou um programa externo.

Aqui está o mesmo programa acima, com o trabalho real (a impressão) separado do meta-trabalho (lendo linha por linha a partir de dois arquivos em paralelo).

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

Agora fingimos que não temos controle sobre o código de trabalho e esse código, por qualquer motivo, tenta ler a partir do descritor de arquivo 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

Aqui está um exemplo de saída. Observe que a segunda linha do primeiro arquivo é "roubada" do loop.

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

Aqui está como você deve fechar os descritores de arquivos antes de chamar o código externo (ou qualquer outro código).

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
    
por 12.12.2011 / 02:11
17

Abra os dois arquivos em diferentes descritores de arquivos . Redirecione a entrada do read embutido para o descritor ao qual o arquivo que você deseja está conectado. No bash / ksh / zsh, você pode escrever read -u 3 em vez de read <&3 .

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

Este snippet pára quando o arquivo mais curto foi processado. Veja Lendo dois arquivos em um IFS while loop - Existe uma maneira de obter um resultado zero diff neste caso? se você quiser continuar processando até o final de ambos os arquivos.

Veja também Quando você usaria um descritor de arquivo adicional? para obter informações adicionais sobre descritores de arquivo e Porque é que 'enquanto IFS = read' é usado com tanta freqüência, ao invés de 'IFS =; enquanto lê .. '? para uma explicação de IFS= read -r .

    
por 12.12.2011 / 02:13
3

Eu sei que você quer um script de shell, mas você pode querer dar uma olhada no comando paste .

    
por 14.12.2011 / 20:38
2

Experimente o comando abaixo:

paste -d '\n' inp1.txt inp2.txt > outfile.txt
    
por 24.01.2017 / 22:08
0

Como alternativa, suponho que você poderia fazer o slurp do arquivo em uma variável array, vinculando cada linha do arquivo em array [line_of_file_index] usando o comando mapfile do bash. No entanto, não tenho certeza se é apenas para Bash3 superior ou Bash4.

link

    
por 15.12.2011 / 21:08