Lê múltiplas linhas de arquivos de texto no bash

5

Quando eu sou script de shell, a maioria do que estou fazendo é envolver a E / S de outros módulos em python, matlab, etc. Para fazer isso, eu costumo usar arquivos de texto ou algo dessa natureza com a entrada / caminhos de saída. Eu sei ler uma linha de um arquivo que posso usar,

for file in $(cat $1);
do
    code using $file
done

mas e se eu quisesse fazer algo usando linhas equivalentes de ambos os arquivos? algo parecido com o Java equivalente:

while((line1 = file1.readLine()) != null) {
    line2 = file2.readLine();
    //do something with both lines...
}

Qual é o método padrão para fazer isso no bash?

    
por Eric 15.06.2015 / 00:14

2 respostas

5
exec 3<file1
exec 4<file2
while read line1 <&3 && read line2 <&4
do
        echo "line1=$line1 and line2=$line2"
done
exec 3<&-
exec 4<&-

Discussão

  • Acima, o espaço em branco inicial e final é retirado das linhas de entrada. Se você deseja preservar esse espaço em branco, substitua read … por IFS= read …

  • Acima, as barras invertidas na entrada serão interpretadas como caracteres de escape. Se você não quiser isso, substitua read … por read -r …

  • read line1 <&3line1 do descritor de arquivo 3. Isso também pode ser escrito de forma equivalente como read -u3 line1 .

  • Declarações como for file in $(cat $1); têm alguns problemas que você deve saber sobre isso. O shell aplicará a expansão do nome do caminho de divisão de palavras ao conteúdo do arquivo e, a menos que você espere, pode levar a vários erros.

Alternativa

while read line1 <&3 && read line2 <&4
do
        echo "line1=$line1 and line2=$line2"
done 3<file1 4<file2
    
por 15.06.2015 / 00:59
4

Para iterar as linhas de um arquivo:

while IFS= read -r line; do
  echo "read $line"
done <input-file

Para iterar vários arquivos, abra-os em diferentes descritores de arquivo (consulte Quando você usaria um descritor de arquivo adicional? ).

while IFS= read -r line1 <&8 || IFS= read -r line2 <&9; do
  echo "read '$line1' from file 1 and '$line2' from file 2"
done 8<input-file1 9<input-file2

Usar read <&8 || read <&9 completa o arquivo mais curto com linhas vazias para corresponder ao arquivo mais longo. Para sair assim que o final de um dos arquivos for atingido, use && em vez de || . Se você quiser detectar todos os casos, verifique o código de retorno separadamente.

{
  while
    IFS= read -r line1 <&8; empty1=$?
    IFS= read -r line2 <&9; empty2=$?
    [ "$empty1" -ne 0 ] && [ "$empty2" -ne 0 ]
  do
    echo "read '$line1' from file 1 and '$line2' from file 2"
  done
  if [ "$empty1" -ne 0 ]; then
    echo "Finishing processing file 1"
    …
  fi
  if [ "$empty2" -ne 0 ]; then
    echo "Finishing processing file 2"
    …
  fi
} 8<input-file1 9<input-file2

Como alternativa, você pode juntar os dois arquivos juntos. O comando paste é conveniente para isso. Por padrão, ele separa as linhas por guias (passa -d para selecionar diferentes delimitadores) e conclui arquivos com linhas vazias. Se os arquivos não contiverem guias, isso delimitará sem ambiguidade linhas de entrada.

tab=$(printf \t)
paste input-file1 input-file2 |
while IFS=$tab read -r line1 line2; do … done

Note que os shells não são muito rápidos em processar textos. Ferramentas mais especializadas são melhores para entradas de médio a grande porte. O pré-processamento com paste é conveniente para juntar dois arquivos para qualquer pós-tratamento. Se você precisar de mais controle sobre quando as linhas são lidas, o awk pode fazer isso com seu comando getline (semelhante ao read do shell).

    
por 15.06.2015 / 02:24