这是我想要实现的行为示例:
假设我有一个行列表,每行包含空格分隔的值:
lines='John Smith
James Johnson'
我只想在用户按提示询问时循环回显姓名或姓氏,因此我全局更改 IFS:
oIFS=$IFS
IFS='
'
for line in $lines; do
IFS=$oIFS
read name surname <<< $line
read -p "Do you want to echo name? surname otherwise "
if [[ $REPLY == "y" ]]; then
echo $name
else
echo $surname
fi
done
这是可行的,但这种方法对我来说看起来并不明智:
- 我们更改了 IFS 并且可能忘记恢复它
- 我们在循环的每次迭代中恢复 IFS
我发现while IFS=...
可以像这样在这里使用:
while IFS='
' read line ; do
echo $line
read name surname <<< $line
read -p "Do you want to echo name? surname otherwise "
if [[ $REPLY == "y" ]]; then
echo $name
else
echo $surname
fi
done <<< "$lines"
但这不是一个选项,因为read -p
提示会被连续的输入流损坏
一种解决方案是仅针对一个for
语句设置 IFS,如下所示:
IFS='
' for line in $lines; do
...
done
但 bash 不允许这样做。
答案1
mapfile
您可以首先使用/将输入行读取到数组readarray
:
lines='John Smith
James Johnson'
mapfile -t lines <<< "$lines"
for line in "${lines[@]}"; do
read name surname <<< "$line"
echo "name: $name surname: $surname"
done
如果lines
来自某个命令的输出,您可以类似地mapfile -t lines < <(somecommand)
直接使用。那里的进程替换有点像管道,但避免了在子 shell 中运行的管道部分出现的问题。请注意,您lines
缺少最后一行末尾的换行符,但<<<
添加了一个。mapfile
不介意它是否丢失,但如果您在 的末尾确实有换行符lines
,那么您将得到一个空数组条目来存放额外的条目。使用进程替换可以绕过该问题。
这里,
while IFS=... read line ; do
...
read -p "Do you want to echo name? surname otherwise "
done <<< "$lines"
两个read
确实从相同的输入读取,但我认为(没有测试)您可以通过使用另一个文件描述符重定向到循环来解决这个问题,例如:
while IFS=... read -u 3 line ; do
...
read -p "Do you want to echo name? surname otherwise "
done 3<<< "$lines"
或者read <&3
代替read -u
,我不知道这是否重要。
答案2
使用实际的列表(数组)而不是摆弄IFS
:
#!/bin/bash
lines=( 'John Smith' 'James Johnson' )
for line in "${lines[@]}"; do
names=($line)
echo "$line"
read -p "Do you want to echo name? surname otherwise "
if [[ $REPLY == "y" ]]; then
echo "${names[0]}"
else
echo "${names[1]}"
fi
done
当然,这假设您只有一个名字和一个姓氏,这对于真实的人来说不是这样,但对于您的数据来说可能是这样。无论如何,您的原始代码做出了相同的假设,所以我猜这对您来说不是问题。
您还可以按照您的想法通过在子 shell 中运行循环来完成此操作,因此对 的任何更改IFS
都只会影响子 shell:
#!/bin/bash
lines='John Smith
James Johnson'
(
oIFS=$IFS
IFS=$'\n'
for line in $lines; do
echo "$line"
IFS=$oIFS
read name surname <<< "$line"
read -p "Do you want to echo name? surname otherwise "
if [[ $REPLY == "y" ]]; then
echo "$name"
else
echo "$surname"
fi
done
)
### The IFS hasn't been changed here outside the subshell.