我有一个用例,我需要在每次迭代开始时读入多个变量,并将用户的输入读入循环。
我不知道如何探索可能的解决方案——
- 对于分配,使用另一个文件句柄而不是标准输入
使用
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 ,但是b
Zsh 会做得更好。
答案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 数组,另请参见ksh93
shell:
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