Gunakan baca sebagai prompt di dalam loop sementara didorong oleh baca?


9

Saya memiliki kasus penggunaan di mana saya perlu membaca dalam beberapa variabel pada awal setiap iterasi dan membaca input dari pengguna ke dalam loop.

Kemungkinan jalur menuju solusi yang saya tidak tahu cara menjelajah -

  1. Untuk penugasan, gunakan filehandle lain, bukan stdin
  2. Gunakan forloop alih-alih ... | while read ...... Saya tidak tahu cara menetapkan beberapa variabel di dalam forloop

    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
    

Jawaban:


9

Jika saya benar ini, saya pikir Anda pada dasarnya ingin mengulang daftar nilai, dan kemudian yang readlain dalam loop.

Berikut beberapa opsi, 1 dan 2 mungkin yang paling ramah.

1. Emulasi array dengan string

Memiliki array 2D akan menyenangkan, tetapi tidak benar-benar mungkin di Bash. Jika nilai Anda tidak memiliki spasi putih, satu solusi untuk perkiraan yaitu menempel setiap set dari tiga angka ke dalam string, dan membagi string di dalam loop:

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

Tentu saja Anda bisa menggunakan pemisah lain juga, misalnya for x in 1:2:3 ...dan IFS=: read a b c <<< "$x".


2. Ganti pipa dengan pengalihan lain untuk membebaskan stdin

Kemungkinan lain adalah memiliki read a b cpembacaan dari fd lain dan mengarahkan input ke sana (ini harus bekerja dalam shell standar):

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

Dan di sini Anda juga dapat menggunakan substitusi proses jika Anda ingin mendapatkan data dari perintah: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(substitusi proses adalah fitur bash / ksh / zsh)


3. Ambil input pengguna dari stderr sebagai gantinya

Atau, sebaliknya, menggunakan pipa seperti pada contoh Anda, tetapi minta input pengguna readdari stderr(fd 2) alih-alih dari stdinmana pipa itu berasal:

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

Membaca dari stderragak aneh, tetapi sebenarnya sering bekerja dalam sesi interaktif. (Anda juga dapat membuka secara eksplisit /dev/tty, dengan asumsi Anda ingin benar-benar memintas pengalihan, itulah hal-hal seperti apa yang lessdigunakan untuk mendapatkan input pengguna bahkan ketika data disalurkan ke sana.)

Meskipun menggunakan stderrseperti itu mungkin tidak berfungsi dalam semua kasus, dan jika Anda menggunakan beberapa perintah eksternal read, Anda setidaknya perlu menambahkan banyak pengalihan ke perintah.

Juga, lihat Mengapa variabel saya lokal dalam satu loop 'sambil membaca', tetapi tidak dalam loop lain yang tampaknya serupa? untuk beberapa masalah tentang ... | while.


4. Iris bagian array sesuai kebutuhan

Saya kira Anda juga bisa memperkirakan array 2D-ish dengan menyalin irisan satu dimensi biasa:

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

Anda juga dapat menetapkan ${a[0]}dll. a, bDll jika Anda ingin nama untuk variabel, tetapi Zsh akan melakukannya dengan lebih baik .


1
tadi sangat menyenangkan! Dan betapa luar biasanya gambaran dari berbagai solusi yang berbeda!
Debanjan Basu

@ StéphaneChazelas, ah ya, Anda benar. Menggunakan stderrseperti itu agak sedikit menjengkelkan, tetapi saya memiliki ingatan bahwa beberapa utilitas melakukannya. Tapi yang bisa saya temukan sekarang adalah yang hanya digunakan /dev/tty. Baiklah.
ilkkachu

Perhatikan bahwa menggunakan <&2(dan juga </dev/tty) menghindari membaca dari stdin skrip. Ini tidak akan berfungsi printf '682\n739' | ./script. Perhatikan juga bahwa read -phanya berfungsi di bash.
Isaac

@ Isaac, dalam satu dengan read dari stderr, ada juga pipa dari echo ke whileloop itu, jadi Anda tidak bisa benar-benar menggunakan skrip stdin ... read -ujuga bash, tetapi dapat diganti dengan pengalihan, dan <<<di yang pertama juga tidak standar, tapi itu agak sulit untuk diselesaikan.
ilkkachu

Semua bisa diselesaikan, tolong: baca jawabanku
Isaac

3

Hanya ada satu /dev/stdin, readakan membacanya dari mana saja di mana ia digunakan (secara default).

Solusinya adalah menggunakan beberapa deskriptor file lain, bukan 1 ( /dev/stdin).

Dari kode setara (dalam bash) ke apa yang Anda posting [1] (lihat di bawah)
cukup tambahkan 0</dev/tty(misalnya) untuk membaca dari 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')"

Pada eksekusi:

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

Alternatif lain adalah menggunakan 0<&2(yang mungkin tampak aneh, tetapi valid).

Perhatikan bahwa read from /dev/tty(juga 0<&2) akan memotong stdin dari skrip, ini tidak akan membaca nilai dari echo:

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

Solusi lain

Yang diperlukan adalah mengarahkan satu input ke beberapa fd lainnya (deskriptor file).
Berlaku dalam ksh, bash, dan 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')"

Atau, dengan 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>&-

Solusi yang bekerja di sh ( <<<tidak bekerja):

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

Tapi ini mungkin lebih mudah dimengerti:

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 kode sederhana

Kode Anda adalah:

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

Kode yang disederhanakan (dalam bash) adalah:

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

Yang, jika dijalankan, mencetak:

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

Yang hanya menunjukkan bahwa var d sedang dibaca dari yang sama /dev/stdin.


2

Dengan zsh, Anda dapat menulisnya sebagai gantinya:

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
}

Untuk array 2D, lihat juga 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

bagus ... Saya mencari jawaban berdasarkan bash, tapi ini bagus untuk diketahui!
Debanjan Basu
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.