For 循环行——如何仅为一个“for”语句设置 IFS?

For 循环行——如何仅为一个“for”语句设置 IFS?

这是我想要实现的行为示例:

假设我有一个行列表,每行包含空格分隔的值:

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

这是可行的,但这种方法对我来说看起来并不明智:

  1. 我们更改了 IFS 并且可能忘记恢复它
  2. 我们在循环的每次迭代中恢复 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.

相关内容