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


9

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

我不知道如何探索的解决方案的可能路径-

  1. 对于分配,请使用另一个文件句柄而不是stdin
  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

Answers:


9

如果我做对了,我认为您基本上是要遍历值列表,然后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.用另一个重定向到自由stdin的管道替换

另一种可能性是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有点奇怪,但实际上通常可以在交互式会话中使用。(您也可以显式打开/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

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


1
那很棒!以及不同解决方法的精彩概述!
Debanjan Basu

@StéphaneChazelas,是的,你是对的。这样使用stderr有点棘手,但我记得有些实用程序会这样做。但是我现在所能找到的只是那些/dev/tty。那好吧。
ilkkachu

请注意,使用<&2和都</dev/tty避免从脚本的stdin中读取内容。这是行不通的printf '682\n739' | ./script。另请注意,read -p仅适用于bash。
艾萨克

@Isaac,在从stderr读取的那个中,还有从回显到该while循环的管道,因此您无论如何都不能真正使用脚本的stdin ... read -u也是bash,但是可以用重定向替换,并且<<<在第一个也是非标准的,但这很难解决。
ilkkachu

一切都可以解决,请:阅读我的回答
Isaac

3

只有一个/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(from 0<&2)读取将绕过脚本的stdin,这不会从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


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

很好...我一直在寻找基于bash的答案,但这很高兴!
Debanjan Basu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.