Cómo leer desde dos archivos de entrada usando el bucle while

27

Quería saber si hay alguna forma de leer dos archivos de entrada en un bucle anidado mientras una línea a la vez. Por ejemplo, digamos que tengo dos archivos FileAy FileB.

Presentar un:

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

Archivo B:

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

Script de muestra actual:

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

Ejecución:

[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 y salida deseada:

Esto recorre completamente FileB para cada línea en FileA. Intenté continuar, romper, salir, pero ninguno de ellos está destinado a lograr el resultado que estoy buscando. Me gustaría que el script lea solo una línea del Archivo A y luego una línea del Archivo B y salga del bucle y continúe con la segunda línea del Archivo A y la segunda línea del Archivo B. Algo similar al siguiente 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

¿Es esto posible lograr con el ciclo while?

jaypal singh
fuente
Una gran solución de @codaddict está aquí: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Respuestas:

32

Si sabe con certeza que algún carácter nunca aparecerá en el primer archivo, entonces puede usar pegar.

Ejemplo de pegar usando la pestaña delimitador predeterminado:

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

Ejemplo de pegar usando @:

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

Tenga en cuenta que es suficiente si se garantiza que el carácter no aparecerá en el primer archivo. Esto se debe a que readse ignorará IFSal completar la última variable. Entonces, incluso si @ocurre en el segundo archivo, no se dividirá.

Ejemplo de pegar usando algunas características de bash para un código posiblemente más limpio:

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

Características de Bash utilizadas: cadena ansi c ( $'\t') y sustitución de proceso ( <(...)) para evitar el ciclo while en un problema de subshell .

Si no puede estar seguro de que algún carácter nunca aparecerá en ambos archivos, puede usar descriptores de archivo .

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

No probado mucho. Podría romperse en líneas vacías.

Los descriptores de archivo número 0, 1 y 2 ya se utilizan para stdin, stdout y stderr, respectivamente. Los descriptores de archivos de 3 en adelante son (generalmente) gratuitos. El manual de bash advierte sobre el uso de descriptores de archivo superiores a 9, porque se "usan internamente".

Tenga en cuenta que los descriptores de archivos abiertos se heredan de las funciones de shell y los programas externos. Las funciones y los programas que heredan un descriptor de archivo abierto pueden leer (y escribir) en el descriptor de archivo. Debe tener cuidado de cerrar todos los descriptores de archivos que no son necesarios antes de llamar a una función o programa externo.

Aquí está el mismo programa que el anterior con el trabajo real (la impresión) separado del meta trabajo (lectura línea por línea de dos archivos en 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

Ahora pretendemos que no tenemos control sobre el código de trabajo y ese código, por cualquier razón, intenta leer del descriptor de archivo 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

Aquí hay un ejemplo de salida. Tenga en cuenta que la segunda línea del primer archivo es "robada" del bucle.

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

A continuación, le indicamos cómo debe cerrar los descriptores de archivo antes de llamar a un código externo (o cualquier código para el caso).

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
fuente
17

Abra los dos archivos en diferentes descriptores de archivo . Redirija la entrada del readincorporado al descriptor al que está conectado el archivo que desea. En bash / ksh / zsh, puedes escribir en read -u 3lugar de read <&3.

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

Este fragmento se detiene cuando se procesa el archivo más corto. Consulte Lectura de dos archivos en un bucle IFS while: ¿hay alguna forma de obtener un resultado de diferencia cero en este caso? si desea seguir procesando hasta el final de ambos archivos.

Consulte también ¿ Cuándo utilizaría un descriptor de archivo adicional? para obtener información adicional sobre los descriptores de archivos y por qué se usa `while IFS = read` tan a menudo, en lugar de` IFS =; mientras lee..`? para una explicación de IFS= read -r.

Gilles 'SO- deja de ser malvado'
fuente
Gracias @Gilles por los enlaces adicionales en el descriptor de archivo.
jaypal singh
@Gilles tal vez te entendí mal, pero no pude hacer que el proceso de bucle sea el archivo más largo por completo (que siempre es $ fileA en mi caso), así que lo hice en una pregunta separada, siendo: ¿hay alguna manera de escribir el bucle así Que diferencia no nota ninguna diferencia entre entrada y salida? unix.stackexchange.com/questions/26780/… lo más cerca que pude llegar fue diff solo encontrando una línea de diferencia.
ixtmixilix
3

Sé que desea un script de shell, pero es posible que desee echar un vistazo al pastecomando.

lutzky
fuente
Gracias @lutzky. pasteTambién es genial.
jaypal singh
2

Prueba el siguiente comando:

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

Alternativamente, supongo que podría sorber el archivo en una variable de matriz uniendo cada línea del archivo en la matriz [line_of_file_index] usando el comando maphile de bash. Sin embargo, no estoy seguro de si es solo para Bash3 superior o Bash4.

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

Nikhil Mulley
fuente