Utilizzare read come prompt all'interno di un ciclo while guidato da read?


9

Ho un caso d'uso in cui ho bisogno di leggere più variabili all'inizio di ogni iterazione e leggere un input dell'utente nel ciclo.

Possibili percorsi per la soluzione che non so esplorare -

  1. Per l'assegnazione utilizzare un altro filehandle invece di stdin
  2. Usa un forciclo invece di ... | while read ...... Non so come assegnare più variabili all'interno di un forciclo

    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
    

Risposte:


9

Se ho capito bene, penso che tu voglia fondamentalmente passare in rassegna le liste di valori, e poi readun'altra all'interno del ciclo.

Ecco alcune opzioni, 1 e 2 sono probabilmente le più sagge.

1. Emulare le matrici con stringhe

Avere array 2D sarebbe bello, ma non proprio possibile in Bash. Se i tuoi valori non hanno spazi bianchi, una soluzione alternativa per approssimare è attaccare ogni set di tre numeri in una stringa e dividere le stringhe all'interno del ciclo:

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

Ovviamente potresti usare anche qualche altro separatore, ad esempio for x in 1:2:3 ...e IFS=: read a b c <<< "$x".


2. Sostituire il tubo con un altro reindirizzamento per liberare lo stdin

Un'altra possibilità è quella di avere la read a b clettura da un altro fd e indirizzare l'input su quello (questo dovrebbe funzionare in una shell standard):

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

E qui puoi anche usare una sostituzione di processo se vuoi ottenere i dati da un comando: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(la sostituzione di processo è una funzione bash / ksh / zsh)


3. Prendi invece l'input dell'utente da stderr

Oppure, viceversa, usando una pipe come nel tuo esempio, ma hai l'input dell'utente readda stderr(fd 2) anziché da stdindove proviene la pipe:

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

Leggere da stderrè un po 'strano, ma in realtà spesso funziona in una sessione interattiva. (Potresti anche aprire esplicitamente /dev/tty, supponendo che tu voglia effettivamente bypassare qualsiasi reindirizzamento, ecco cosa lessusa roba simile per ottenere l'input dell'utente anche quando i dati vengono reindirizzati ad esso.)

Sebbene l'utilizzo in stderrquesto modo potrebbe non funzionare in tutti i casi e se si utilizza un comando esterno anziché read, è necessario aggiungere almeno un gruppo di reindirizzamenti al comando.

Inoltre, vedi Perché la mia variabile è locale in un ciclo "while read", ma non in un altro loop apparentemente simile? per alcuni problemi riguardanti ... | while.


4. Affettare le parti di un array secondo necessità

Suppongo che potresti anche approssimare un array 2D ish copiando sezioni di un normale unidimensionale:

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

Puoi anche assegnare ${a[0]}ecc. A a, becc. Se vuoi nomi per le variabili, ma Zsh lo farebbe molto più bene .


1
È stato perfetto! E che grande panoramica delle diverse soluzioni alternative!
Debanjan Basu,

@ StéphaneChazelas, ah sì, hai ragione. Usare stderrcosì è un po 'malizioso, ma ho avuto il ricordo che qualche utilità lo fa. Ma tutto quello che posso trovare ora sono quelli che usano solo /dev/tty. Oh bene.
ilkkachu,

Si noti che l'utilizzo <&2(oltre che </dev/tty) evita di leggere dallo stdin dello script. Questo non funzionerà printf '682\n739' | ./script. Si noti inoltre che read -pfunziona solo in bash.
Isacco

@Isaac, in quello con la lettura di stderr, c'è anche la pipe dall'eco a quel whileloop, quindi non puoi davvero usare lo stdin dello script ... read -uè anche bash, ma può essere sostituito con reindirizzamenti, e <<<in anche il primo non è standard, ma è un po 'più difficile aggirare.
ilkkachu,

Tutto potrebbe essere risolto, per favore: leggi la mia risposta
Isacco

3

Ce n'è solo uno /dev/stdin, readleggerà da esso ovunque sia utilizzato (per impostazione predefinita).

La soluzione è utilizzare un altro descrittore di file invece di 1 ( /dev/stdin).

Dall'equivalente del codice (in bash) a ciò che hai pubblicato [1] (guarda sotto)
basta aggiungere 0</dev/tty(per esempio) per leggere dal tty "reale":

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')"

In esecuzione:

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

Un'altra alternativa è usare 0<&2(che potrebbe sembrare strano, ma è valido).

Si noti che la lettura da /dev/tty(anche 0<&2) ignorerà lo stdin dello script, questo non leggerà i valori dall'eco:

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

Altre soluzioni

Ciò che è necessario è reindirizzare un input verso qualche altro fd (descrittore di file).
Valido in ksh, bash e 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')"

Oppure, con exec:

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 soluzione che funziona in sh ( <<<non funziona):

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>&-

Ma questo è probabilmente più facile da capire:

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 codice più semplice

Il tuo codice è:

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 codice semplificato (in bash) è:

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')"

Che, se eseguito, stampa:

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

Il che sta solo dimostrando che il var d viene letto dallo stesso /dev/stdin.


2

Con zsh, invece, puoi scriverlo:

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
}

Per le matrici 2D, vedere anche la 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

bello ... stavo cercando una risposta bash, ma è bello sapere!
Debanjan Basu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.