readによって駆動されるwhileループ内でプロンプトとしてreadを使用しますか?


9

各反復の開始時に複数の変数を読み取り、ユーザーからの入力をループに読み込む必要があるユースケースがあります。

私が探索する方法がわからない解決策への可能な道-

  1. 割り当てには、標準入力の代わりに別のファイルハンドルを使用します
  2. for代わりにループを使用する... | while read ...... forループ内に複数の変数を割り当てる方法がわかりません

    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
    

回答:


9

これが正しければ、基本的に値のリストをループし、次にreadループ内で別のリストをループする必要があると思います。

ここにいくつかのオプションがあります。1と2はおそらく最も健全です。

1.文字列で配列をエミュレートする

2D配列があると便利ですが、Bashでは実際には不可能です。値に空白がない場合は、3つの数値の各セットを文字列に貼り付け、ループ内で文字列を分割することで、これを概算する1つの回避策があります。

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

もちろん、for x in 1:2:3 ...やなどの 他のセパレータも使用できますIFS=: read a b c <<< "$x"


2. stdinを解放するためにパイプを別のリダイレクトに置き換えます

別の可能性はread a b c、別のfdから読み取りを行い、それに入力を送ることです(これは標準シェルで動作するはずです)。

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

また、コマンドからデータを取得したい場合は、ここでプロセス置換を使用することもできますwhile read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(プロセス置換はbash / ksh / zsh機能です)。


3.代わりにstderrからユーザー入力を受け取る

または、逆に、例のようにパイプを使用しますが、パイプがどこから来るかではなく、(fd 2)readからのユーザー入力を使用します。stderrstdin

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

からの読み取りstderrは少し奇妙ですが、実際には多くの場合、インタラクティブセッションで機能します。(/dev/ttyリダイレクトを実際にバイパスしたい場合は、明示的に開くこともできますless。これは、データがパイプされている場合でも、ユーザーの入力を取得するために使用するものです。)

そのstderrように使用してもすべての場合に機能するわけではありません。また、の代わりに外部コマンドを使用しているread場合は、少なくとも一連のリダイレクトをコマンドに追加する必要があります。

また、なぜ、ある変数が1つの「while read」ループでローカルであるのに、別の一見似たループではないのかを参照してください。に関するいくつかの問題について... | while


4.必要に応じて配列の一部をスライスします

通常の1次元配列のスライスをコピーして、2次元配列を近似することもできます。

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

変数の名前が必要な場合は、etcを${a[0]}等に割り当てることもできますが、Zshはそれをより適切に行いますab


1
すごく良かった!そして、さまざまな回避策の素晴らしい概要です!
デバンジャンバス

@StéphaneChazelas、ああ、そうだね。stderrそのように使用するのは少し厄介ですが、私はいくつかのユーティリティがそれをするという思い出がありました。しかし、今見つけることができるのは、単に使用するものだけ/dev/ttyです。しかたがない。
ilkkachu 2018

スクリプトの標準入力からの読み取りは避けて<&2(と同様に</dev/tty)使用することに注意してください。これは機能しませんprintf '682\n739' | ./script。またread -p、bash でのみ機能することにも注意してください。
アイザック

@Isaacは、標準エラー出力からの読み出しとのいずれかで、そこにもそれにエコーからのパイプだwhileあなたは本当に...とにかくスクリプトの標準入力を使用することはできませんので、ループread -uもbashのですが、リダイレクトに置き換えることができ、かつ<<<中最初のものも非標準ですが、これを回避するのは少し難しいです。
ilkkachu 2018

すべて解決できます。どうか、私の答えを読ん
Isaac

3

は1つだけあり/dev/stdinread使用されている場所であればどこからでも読み取ります(デフォルト)。

解決策は、1(/dev/stdin)の代わりに他のファイル記述子を使用することです。

同等のコード(bashの場合)から投稿したもの[1](以下を参照)
に追加して0</dev/tty(たとえば)、「実際の」ttyから読み取るだけです。

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

実行時:

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

他の選択肢は使用することです0<&2(奇妙に見えるかもしれませんが、有効です)。

/dev/tty(また0<&2)からの読み取りはスクリプトの標準入力をバイパスし、これはエコーから値を読み取らないことに注意してください:

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

その他の解決策

1つの入力を他のfd(ファイル記述子)にリダイレクトする必要があります。
ksh、bash、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')"

または、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>&-

shで動作するソリューション(<<<動作しません):

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

しかし、これはおそらく理解しやすいでしょう:

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よりシンプルなコード

あなたのコードは:

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

簡略化されたコード(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')"

実行すると、次のように表示されます。

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

これは、変数dが同じものから読み取られていることを示してい/dev/stdinます。


2

ではzsh、代わりにそれを書くことができます:

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
}

2D配列については、ksh93シェルも参照してください。

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

いいですね... bashベースの答えを探していましたが、これは知っておくと便利です。
デバンジャンバス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.