Verwenden Sie read als Eingabeaufforderung in einer while-Schleife, die von read gesteuert wird.


9

Ich habe einen Anwendungsfall, in dem ich zu Beginn jeder Iteration mehrere Variablen einlesen und eine Eingabe des Benutzers in die Schleife einlesen muss.

Mögliche Wege zur Lösung, die ich nicht erforschen kann -

  1. Verwenden Sie für die Zuweisung ein anderes Dateihandle anstelle von stdin
  2. Verwenden Sie eine forSchleife anstelle von ... | while read ...... Ich weiß nicht, wie mehrere Variablen innerhalb einer forSchleife zugewiesen werden sollen

    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

Antworten:


9

Wenn ich das richtig verstanden habe, denke ich, dass Sie im Grunde genommen eine Liste von Werten und dann eine readandere innerhalb der Schleife durchlaufen möchten .

Hier sind einige Optionen, 1 und 2 sind wahrscheinlich die vernünftigsten.

1. Emulieren Sie Arrays mit Strings

2D-Arrays zu haben wäre schön, aber in Bash nicht wirklich möglich. Wenn Ihre Werte kein Leerzeichen haben, besteht eine Problemumgehung darin, jeden Satz von drei Zahlen in eine Zeichenfolge zu kleben und die Zeichenfolgen innerhalb der Schleife aufzuteilen:

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

Natürlich können Sie auch ein anderes Trennzeichen verwenden, z . B. for x in 1:2:3 ...und IFS=: read a b c <<< "$x".


2. Ersetzen Sie das Rohr durch eine andere Umleitung, um stdin freizugeben

Eine andere Möglichkeit besteht darin, das read a b cLesen von einem anderen fd zu haben und die Eingabe darauf zu lenken (dies sollte in einer Standard-Shell funktionieren):

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

Und hier können Sie auch eine Prozessersetzung verwenden, wenn Sie die Daten von einem Befehl abrufen möchten: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(Die Prozessersetzung ist eine bash / ksh / zsh-Funktion.)


3. Nehmen Sie stattdessen Benutzereingaben von stderr entgegen

Oder umgekehrt, indem Sie eine Pipe wie in Ihrem Beispiel verwenden, aber den Benutzer readvon stderr(fd 2) eingeben lassen, anstatt stdinwoher die Pipe stammt:

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

Das Lesen von stderrist etwas seltsam, funktioniert aber häufig in einer interaktiven Sitzung. (Sie können auch explizit öffnen /dev/tty, vorausgesetzt, Sie möchten Umleitungen tatsächlich umgehen. Dies wird lessverwendet, um die Benutzereingaben auch dann abzurufen, wenn die Daten an sie weitergeleitet werden.)

Obwohl die Verwendung stderrdieser Funktion möglicherweise nicht in allen Fällen funktioniert und Sie stattdessen einen externen Befehl verwenden read, müssen Sie dem Befehl zumindest eine Reihe von Umleitungen hinzufügen.

Siehe auch Warum befindet sich meine Variable lokal in einer 'while read'-Schleife, aber nicht in einer anderen scheinbar ähnlichen Schleife? für einige Fragen in Bezug auf ... | while.


4. Schneiden Sie Teile eines Arrays nach Bedarf in Scheiben

Ich nehme an, Sie könnten ein 2D-ish-Array auch approximieren, indem Sie Slices eines regulären eindimensionalen Arrays kopieren:

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

Sie können auch ${a[0]}usw. usw. zuweisen a, bwenn Sie Namen für die Variablen möchten, aber Zsh würde das viel besser tun .


1
das war großartig! Und was für ein großartiger Überblick über die verschiedenen Problemumgehungen!
Debanjan Basu

@ StéphaneChazelas, ah ja, du hast recht. So stderretwas zu benutzen ist ein bisschen schwierig, aber ich hatte die Erinnerung, dass ein Dienstprogramm es tut. Aber alles, was ich jetzt finden kann, sind solche, die nur verwenden /dev/tty. Naja.
Ilkkachu

Beachten Sie, dass Sie mit <&2(sowie </dev/tty) vermeiden, aus dem Standard des Skripts zu lesen. Dies wird nicht funktionieren printf '682\n739' | ./script. Beachten Sie auch, dass read -pnur in Bash arbeiten.
Isaac

@Isaac, in dem mit dem Lesen von stderr gibt es auch die Pipe vom Echo zu dieser whileSchleife, so dass Sie das stdin des Skripts sowieso nicht wirklich verwenden können ... read -uist ebenfalls bash, kann aber durch Weiterleitungen ersetzt werden und <<<in Der erste ist ebenfalls nicht Standard, aber das ist etwas schwieriger zu umgehen.
Ilkkachu

Alles könnte gelöst werden, bitte: Lesen Sie meine Antwort
Isaac

3

Es gibt nur einen /dev/stdin, der readüberall dort liest, wo er verwendet wird (standardmäßig).

Die Lösung besteht darin, anstelle von 1 ( /dev/stdin) einen anderen Dateideskriptor zu verwenden .

Vom Code-Äquivalent (in Bash) zu dem, was Sie gepostet haben [1] (siehe unten),
fügen Sie einfach 0</dev/tty(zum Beispiel) hinzu, um aus dem "echten" tty zu lesen:

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

Bei der Ausführung:

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

Eine andere Alternative ist die Verwendung 0<&2(was seltsam erscheinen mag, aber gültig ist).

Beachten Sie, dass das Lesen von /dev/tty(auch 0<&2) den Standard des Skripts umgeht. Dadurch werden die Werte aus dem Echo nicht gelesen:

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

Andere Lösungen

Was benötigt wird, ist, eine Eingabe auf eine andere fd (Dateideskriptor) umzuleiten.
Gültig in ksh, bash und 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')"

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

Eine Lösung, die in sh <<<funktioniert ( funktioniert nicht):

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

Aber das ist wahrscheinlich leichter zu verstehen:

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 Einfacherer Code

Ihr Code lautet:

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

Ein vereinfachter Code (in Bash) lautet:

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

Welche, wenn ausgeführt, druckt:

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

Das zeigt nur, dass die Variable d von derselben gelesen wird /dev/stdin.


2

Mit zshkönnen Sie es stattdessen schreiben:

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
}

Für 2D-Arrays siehe auch die 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

nett ... Ich suchte nach einer bashbasierten Antwort, aber das ist schön zu wissen!
Debanjan Basu
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.