¿Usar leer como mensaje dentro de un ciclo while impulsado por lectura?

9

Tengo un caso de uso en el que necesito leer varias variables al comienzo de cada iteración y leer una entrada del usuario en el bucle.

Posibles caminos a la solución que no sé cómo explorar:

  1. Para la asignación, use otro identificador de archivo en lugar de stdin
  2. Use un forbucle en lugar de ... | while read ...... No sé cómo asignar múltiples variables dentro de un forbucle

    echo -e "1 2 3\n4 5 6" |\
    while read a b c; 
    do 
      echo "$a -> $b -> $c";
      echo "Enter a number:";
      read d ;
      echo "This number is $d" ; 
    done
Debanjan Basu
fuente

Respuestas:

9

Si entendí esto bien, creo que básicamente quieres recorrer las listas de valores, y luego readotra dentro del ciclo.

Aquí hay algunas opciones, 1 y 2 son probablemente las más sensatas.

1. Emular matrices con cadenas

Tener matrices 2D sería bueno, pero en realidad no es posible en Bash. Si sus valores no tienen espacios en blanco, una solución alternativa para aproximarse es pegar cada conjunto de tres números en una cadena y dividir las cadenas dentro del bucle:

for x in "1 2 3" "4 5 6"; do 
  read a b c <<< "$x"; 
  read -p "Enter a number: " d
  echo "$a - $b - $c - $d ";
done

Por supuesto, también podría usar algún otro separador, por ejemplo, for x in 1:2:3 ...y IFS=: read a b c <<< "$x".


2. Reemplace la tubería con otra redirección para liberar stdin

Otra posibilidad es tener la read a b clectura de otro fd y dirigir la entrada a eso (esto debería funcionar en un shell estándar):

while read a b c <&3; do
    printf "Enter a number: "
    read d
    echo "$a - $b - $c - $d ";
done 3<<EOF
1 2 3
4 5 6
EOF

Y aquí también puede usar una sustitución de proceso si desea obtener los datos de un comando: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(la sustitución de proceso es una función bash / ksh / zsh)


3. Tome la entrada del usuario de stderr en su lugar

O, al revés, usando una tubería como en su ejemplo, pero tenga la entrada readdel usuario de stderr(fd 2) en lugar de de stdindónde proviene la tubería:

echo $'1 2 3\n4 5 6' |
while read a b c; do 
    read -u 2 -p "Enter a number: " d
    echo "$a - $b - $c - $d ";
done

Leer de stderres un poco extraño, pero en realidad a menudo funciona en una sesión interactiva. (También podría abrir explícitamente /dev/tty, suponiendo que realmente desea omitir cualquier redirección, eso es lo que se lessusa para obtener la entrada del usuario incluso cuando los datos se canalizan a ella).

Aunque usarlo de stderresa manera podría no funcionar en todos los casos, y si está usando algún comando externo en lugar de read, al menos necesitaría agregar un montón de redirecciones al comando.

Además, vea ¿Por qué mi variable es local en un bucle 'mientras se lee', pero no en otro bucle aparentemente similar? por algunos problemas relacionados ... | while.


4. Corte partes de una matriz según sea necesario

Supongo que también podría aproximar una matriz 2D ish copiando secciones de una unidimensional normal:

data=(1 2 3 
      4 5 6)

n=3
for ((i=0; i < "${#data[@]}"; i += n)); do
    a=( "${data[@]:i:n}" )
    read -p "Enter a number: " d
    echo "${a[0]} - ${a[1]} - ${a[2]} - $d "
done

También puede asignar ${a[0]}etc. a a, betc. si desea nombres para las variables, pero Zsh lo haría mucho mejor .

ilkkachu
fuente
1
¡eso fue genial! ¡Y qué gran resumen de las diferentes soluciones!
Debanjan Basu
@ StéphaneChazelas, ah sí, tienes razón. Usarlo stderres un poco asqueroso, pero recuerdo que alguna utilidad lo hace. Pero todo lo que puedo encontrar ahora son los que solo uso /dev/tty. Oh bien.
ilkkachu
Tenga en cuenta que el uso <&2(así como </dev/tty) evita leer del stdin del script. Esto no va a funcionar printf '682\n739' | ./script. También tenga en cuenta que read -psolo funciona en bash.
Isaac
@Isaac, en el que tiene la lectura de stderr, también está la tubería del eco a ese whilebucle, por lo que realmente no puedes usar el stdin del script de todos modos ... read -utambién es bash, pero se puede reemplazar con redireccionamientos, y <<<en el primero tampoco es estándar, pero es un poco más difícil de solucionar.
ilkkachu
Todo podría resolverse, por favor: lea mi respuesta
Isaac
3

Solo hay uno /dev/stdin, readse leerá en cualquier lugar donde se use (por defecto).

La solución es usar algún otro descriptor de archivo en lugar de 1 ( /dev/stdin).

Desde el código equivalente (en bash) hasta lo que publicaste [1] (mira abajo)
solo agrega 0</dev/tty(por ejemplo) para leer desde el tty "real":

while read a b c
do    read -p "Enter a number: " d  0</dev/tty   # 0<&2 is also valid
      echo "$a -> $b -> $c and ++> $d"
done  <<<"$(echo -e '1 2 3\n4 5 6')"

En ejecución:

$ ./script
Enter a number: 789
1 -> 2 -> 3 and ++> 789
Enter a number: 333
4 -> 5 -> 6 and ++> 333

Otra alternativa es usar 0<&2(que puede parecer extraño, pero es válido).

Tenga en cuenta que la lectura de /dev/tty(también 0<&2) omitirá el stdin del script, esto no leerá los valores del eco:

$ echo -e "33\n44" | ./script

Otras soluciones

Lo que se necesita es redirigir una entrada a otro fd (descriptor de archivo).
Válido en ksh, bash y zsh:

while read -u 7 a b c
do    printf "Enter a number: "
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<<"$(echo -e '1 2 3\n4 5 6')"

O, con el ejecutivo:

exec 7<<<"$(echo -e '1 2 3\n4 5 6')"

while read -u 7 a b c
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Una solución que funciona en sh ( <<<no funciona):

exec 7<<-\_EOT_
1 2 3
4 5 6
_EOT_

while read a b c  <&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Pero esto es probablemente más fácil de entender:

while read a b c  0<&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<-\_EOT_
1 2 3
4 5 6
_EOT_

1 código más simple

Tu código es:

echo -e "1 2 3\n4 5 6" |\
while read a b c; 
do 
  echo "$a -> $b -> $c";
  echo "Enter a number: ";
  read d ;
  echo "This number is $d" ; 
done

Un código simplificado (en bash) es:

while read a b c
do    #0</dev/tty
      read -p "Enter a number: " d ;
      echo "$a -> $b -> $c and ++> $d";
done  <<<"$(echo -e '1 2 3\n4 5 6')"

Que, si se ejecuta, imprime:

$ ./script
1 -> 2 -> 3 and ++> 4 5 6

Lo que solo muestra que la var d se está leyendo de la misma /dev/stdin.

Isaac
fuente
2

Con zsh, puedes escribirlo en su lugar:

for a b c (
  1 2 3
  4 5 6
  'more complex' $'\n\n' '*** values ***'
) {
  read 'd?Enter a number: '
  do-something-with $a $b $c $d
}

Para matrices 2D, vea también el ksh93shell:

a=(
  (1 2 3)
  (4 5 6)
  ('more complex' $'\n\n' '*** values ***')
)
for i in "${!a[@]}"; do
  read 'd?Enter a number: '
  do-something-with "${a[i][0]}" "${a[i][1]}" "${a[i][2]}" "$d"
done
Stéphane Chazelas
fuente
agradable ... Estaba buscando una respuesta basada en bash, ¡pero es bueno saberlo!
Debanjan Basu