Multivariable para bucles


12

¿Hay alguna manera de especificar múltiples variables (no solo enteros) en forbucles bash? Es posible que tenga 2 archivos que contienen texto arbitrario con el que necesitaría trabajar.

Lo que funcionalmente necesito es algo como esto:

for i in $(cat file1) and j in $(cat file2); do command $i $j; done

¿Algunas ideas?

Respuestas:


11

Primero, no lea las líneas con for , ya que existen varios problemas inevitables con la lectura de líneas mediante la división de palabras.

Suponiendo archivos de igual longitud, o si solo desea hacer un bucle hasta que se lean los dos archivos más cortos, es posible una solución simple.

while read -r x && read -r y <&3; do
    ...
done <file1 3<file2

Preparar una solución más general es difícil debido a que readdevuelve falso y varias otras razones. Este ejemplo puede leer un número arbitrario de secuencias y regresar después de la entrada más corta o más larga.

#!/usr/bin/env bash

# Open the given files and assign the resulting FDs to arrName.
# openFDs arrname file1 [file2 ...]
openFDs() {
    local x y i arr=$1

    [[ -v $arr ]] || return 1
    shift

    for x; do
        { exec {y}<"$x"; } 2>/dev/null || return 1
        printf -v "${arr}[i++]" %d "$y"
    done
}

# closeFDs FD1 [FD2 ...]
closeFDs() {
    local x
    for x; do
        exec {x}<&-
    done
}

# Read one line from each of the given FDs and assign the output to arrName.
# If the first argument is -l, returns false only when all FDs reach EOF.
# readN [ -l ] arrName FD1 [FD2 ...]
readN() {
    if [[ $1 == -l ]]; then
        local longest
        shift
    else
        local longest=
    fi

    local i x y status arr=$1
    [[ -v $arr ]] || return 1
    shift

    for x; do
        if IFS= read -ru "$x" "${arr}[i]" || { unset -v "${arr}[i]"; [[ ${longest+_} ]] && return 1; }; then
            status=0
        fi
        ((i++))
    done
    return ${status:-1}
}

# readLines file1 [file2 ...]
readLines() {
    local -a fds lines
    trap 'closeFDs "${fds[@]}"' RETURN
    openFDs fds "$@" || return 1

    while readN -l lines "${fds[@]}"; do
        printf '%-1s ' "${lines[@]}"
        echo
    done
}

{
    readLines /dev/fd/{3..6} || { echo 'error occured' >&2; exit 1; }
} <<<$'a\nb\nc\nd' 3<&0 <<<$'1\n2\n3\n4\n5' 4<&0 <<<$'x\ny\nz' 5<&0 <<<$'7\n8\n9\n10\n11\n12' 6<&0

# vim: set fenc=utf-8 ff=unix ts=4 sts=4 sw=4 ft=sh nowrap et:

Entonces, dependiendo de si se readNobtiene -l, la salida es

a 1 x 7
b 2 y 8
c 3 z 9
d 4 10
5 11
12

o

a 1 x 7
b 2 y 8
c 3 z 9

Tener que leer múltiples transmisiones en un bucle sin guardar todo en múltiples matrices no es tan común. Si solo quieres leer matrices, deberías echarle un vistazo mapfile.


||no funciona para mi Procesa fichero1 continuación fichero2 , pero sí mantener los archivos sincronizados con la &&que sale del tiempo de bucle a primera EF . - GNU bash 4.1.5
Peter.O

No tuve un momento libre para probar, sí, probablemente querría ambos elementos por iteración. El ||operador de "lista" está en cortocircuito como en la mayoría de los idiomas. Seguramente esto ha sido respondido mil veces antes ...
ormaaj

El punto no es la frecuencia con que frecuencia se ha mencionado algo antes. Más bien, es que el código que está mostrando actualmente en su respuesta no funciona . Según su " probablemente desee ambos elementos ...", parece que aún no lo ha probado.
Peter.O

@ Peter.O lo sé. Ha sido arreglado.
ormaaj

2

Hacer hecho; hecho: necesita dos fors y dos dones:

for i in $(< file1); do for j in $(< file2); do echo $i $j;done ; done

El archivo2, por supuesto, se procesa para cada palabra en el archivo1 una vez.

Usar <en lugar de cat debería ser una ganancia de rendimiento sin complicaciones en la mayoría de los casos. No hay subproceso involucrado. (No estoy seguro acerca de la última oración).

Sin comprimir:

for i in $(< file1)
do
  for j in $(< file2)
  do
    echo $i $j
  done
done

Si no le gusta leer palabras, sino líneas, y preservar el espacio en blanco, use while y lea:

while read line; do while read second; do echo "${line}${second}"; done <file2 ; done <file1

Si desea agregar 2 archivos y no anidarlos:

cat file1 file2 | while read line; do echo "${line}"; done

Si desea comprimir 2 archivos, use pegar:

paste file1 file2

1
Dices que no hay ningún subproceso invocado. ¿No es el () un subshell? No quiero ser pendante, pero no estoy seguro de lo que está sucediendo ahora. Por ejemplo, sé que cuando tengo un script y no quiero que contamine el shell actual con cd's, por ejemplo, lo hago dentro de un (). También en ejecución (suspensión 4) y, por ejemplo, seguido de una ps me muestra que el proceso aún se está ejecutando. ¿Puedes por favor elaborar?
Silverrocker

Bueno, lo siento, estoy actuando sin fundamento aquí. Pensé que lo escuché así, pero tal vez sea solo en <comparación con cat. Y no estoy seguro de cómo probarlo y cuándo será semánticamente importante. Si se invocan tuberías, las variables en los subprocesos no aparecen en el proceso principal, pero aquí no tenemos variables. Golpeo la oración crítica, hasta que alguien pueda aclararla.
Usuario desconocido

0

whiletiene una sintaxis interesante. Puede poner varios comandos antes del do ... whileciclo, y el caso en cuestión puede necesitar hacer malabares con esta función, dependiendo de sus requisitos particulares de: ¿lee al final del archivo más largo o solo al final del más corto?

Por ejemplo, read || readsimplemente no funciona (según los requisitos de la pregunta), porque cuando se lee el primer archivo true, se omite la lectura del segundo archivo hasta que se haya leído el primer archivo de principio a fin ... Luego, porque el estado aún así true, el ciclo while continúa y lee el segundo archivo de principio a fin.

read && readleerá los archivos simultáneamente (sincrónicamente) si solo desea leer hasta el archivo más corto. Sin embargo, si desea leer ambos archivos eof, debe trabajar con los while'srequisitos de sintaxis, es decir. por el comando inmediatamente antes del do whileciclo que produce un código de retorno distinto de cero para salir del ciclo while.

Aquí hay un ejemplo de cómo leer ambos archivos en eof

while IFS= read -r line3 <&3 || ((eof3=1))
      IFS= read -r line4 <&4 || ((eof4=1))
      !((eof3 & eof4))
do
    echo "$line3, $line4"
done 3<file3 4<file4

(es posible que desee probar eof3 y eof4 antes de la lectura, pero la idea general está ahí, especialmente en la condición final verdadero / falso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.