Como ler de dois arquivos de entrada usando o loop while

27

Eu queria saber se existe alguma maneira de ler dois arquivos de entrada em um loop aninhado while, uma linha por vez. Por exemplo, digamos que eu tenho dois arquivos FileAe FileB.

Arquivo A:

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

PastaB:

[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 faz um loop sobre o FileB completamente para cada linha no FileA. Tentei usar continuar, interromper, sair, mas nenhum deles foi feito para alcançar a saída que estou procurando. Gostaria que o script lesse apenas uma linha do arquivo A e, em seguida, uma linha do arquivo B, saia do loop e continue com a segunda linha do arquivo A e a segunda linha do arquivo B. Algo semelhante ao script a seguir -

[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

Isso é possível com o loop while?

jaypal singh
fonte
Uma ótima solução por @codaddict está aqui: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Respostas:

32

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

Exemplo de colagem 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 for garantido que o caractere não ocorra no primeiro arquivo. Isso ocorre porque readserá ignorado IFSao preencher a última variável. Portanto, mesmo que @ocorra no segundo arquivo, ele não será dividido.

Exemplo de colagem usando alguns recursos do bash para um código sem dúvida 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: string ansi c ( $'\t') e substituição do processo ( <(...)) para evitar o loop while em um problema de sub-shell .

Se você não tiver certeza de que algum caractere nunca ocorrerá nos dois arquivos, use os descritores de arquivo .

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 número 0, 1 e 2 já são usados ​​para stdin, stdout e stderr, respectivamente. Os descritores de arquivos de 3 em diante são (geralmente) gratuitos. O manual do bash avisa sobre o uso de descritores de arquivo maiores que 9, porque eles são "usados ​​internamente".

Observe que os descritores de arquivo aberto 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) no descritor de arquivo. Você deve fechar todos os descritores de arquivo que não são necessários antes de chamar uma função ou programa externo.

Aqui está o mesmo programa acima, com o trabalho real (a impressão) separado do meta-trabalho (lendo linha por linha 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 o 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 arquivo 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
lesmana
fonte
17

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

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

Esse trecho para quando o arquivo mais curto é processado. Consulte Lendo dois arquivos em um loop IFS while - Existe uma maneira de obter um resultado zero diff neste caso? se você quiser continuar processando até o final dos dois arquivos.

Consulte também Quando você usaria um descritor de arquivo adicional? para obter informações adicionais sobre descritores de arquivos, e Por que `enquanto IFS = read` é usado com tanta frequência, em vez de` IFS =; enquanto lê ...? para uma explicação de IFS= read -r.

Gilles 'SO- parar de ser mau'
fonte
Obrigado @Gilles pelos links adicionais no descritor de arquivos.
Jaypal singh
@Gilles talvez eu tenha entendido mal você, mas não consegui fazer com que o processo de loop fosse o arquivo mais longo inteiramente (que é sempre $ fileA no meu caso), então fiz isso em uma pergunta separada, sendo: existe uma maneira de escrever o loop para que diff não percebe nenhuma diferença entre entrada e saída? unix.stackexchange.com/questions/26780/… o mais próximo que pude chegar foi encontrar apenas uma linha de diferença.
ixtmixilix
3

Sei que você deseja um script de shell, mas pode dar uma olhada no pastecomando.

lutzky
fonte
Obrigado @lutzky. pasteé legal também.
Jaypal singh
2

Experimente o comando abaixo:

paste -d '\n' inp1.txt inp2.txt > outfile.txt
Shree
fonte
0

Como alternativa, suponho que você possa inserir o arquivo em uma variável de matriz, amarrando cada linha do arquivo na matriz [line_of_file_index] usando o comando mapfile do bash. No entanto, não tenho certeza se é apenas para o Bash3 superior ou o Bash4.

http://wiki.bash-hackers.org/commands/builtin/mapfile

Nikhil Mulley
fonte