在由 read 驱动的 while 循环中使用 read 作为提示?

在由 read 驱动的 while 循环中使用 read 作为提示?

我有一个用例,我需要在每次迭代开始时读入多个变量,并将用户的输入读入循环。

我不知道如何探索可能的解决方案——

  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
    

答案1

如果我做对了,我想你基本上想循环遍历值列表,然后read在循环内循环另一个。

这里有几个选项,1 和 2 可能是最明智的。

1. 用字符串模拟数组

拥有 2D 数组固然很好,但在 Bash 中却不太可能。如果您的值没有空格,一种近似的解决方法是将每组三个数字粘贴到一个字符串中,然后在循环内拆分字符串:

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. 将管道替换为另一个重定向到免费标准输入

另一种可能性是从另一个 fd 读取read a b c并将输入定向到该 fd(这应该在标准 shell 中工作):

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 获取用户输入

或者,相反,使用像示例中那样的管道,但让用户输入read来自stderr(fd 2) 而不是stdin管道的来源:

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有点奇怪,但实际上经常在交互式会话中工作。 (您也可以显式地 open /dev/tty,假设您实际上想绕过任何重定向,这就是类似的东西less用来获取用户的输入,即使数据通过管道传输到它。)

stderr尽管这样使用可能并不在所有情况下都有效,并且如果您使用一些外部命令而不是read,则至少需要向该命令添加一堆重定向。

另请参阅为什么我的变量在一个“while read”循环中是本地变量,但在另一个看似相似的循环中却不是?对于一些有关 的问题... | while


4. 根据需要对数组的各个部分进行切片

我想你也可以通过复制常规一维数组的切片来近似二维数组:

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

如果您想要变量的名称,您也可以将${a[0]}etc. 分配给a, etc ,但是bZsh 会做得更好

答案2

只有一个/dev/stdin,read将从使用它的任何地方读取它(默认情况下)。

解决方案是使用其他文件描述符代替 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(also 0<&2) 将绕过脚本的标准输入,这不会从 echo 读取值:

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

其他解决方案

需要的是将一个输入重定向到其他某个 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

这只是表明 var d 是从同一个/dev/stdin.

答案3

使用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 数组,另请参见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

相关内容