For 循环行——如何只为一个 `for` 语句设置 IFS?

ary*_*din 5 bash

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

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

lines='John Smith
James Johnson'
Run Code Online (Sandbox Code Playgroud)

而且我只想在用户通过提示询问时循环回显姓名或姓氏的行,因此我全局更改 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
Run Code Online (Sandbox Code Playgroud)

这有效,但这种方法对我来说并不明智:

  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"
Run Code Online (Sandbox Code Playgroud)

但这不是一个选项,因为read -p提示会被连续输入流破坏

一个解决方案是 IFS 只为这样的一个for语句设置:

IFS='
' for line in $lines; do
    ...
done
Run Code Online (Sandbox Code Playgroud)

但 bash 不允许这样做。

ilk*_*chu 9

您可以先使用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
Run Code Online (Sandbox Code Playgroud)

如果lines来自某个命令的输出,您可以类似地mapfile -t lines < <(somecommand)直接使用。那里的进程替换有点像管道,但避免了在子壳中运行的管道部件的问题。请注意,您lines缺少最后一行末尾的换行符,但<<<添加了一个。mapfile不介意它是否丢失,但如果您在 末尾确实有换行符lines,您将获得一个空数组条目,用于额外的条目。使用进程替换可以绕过这个问题。


这里,

while IFS=... read line ; do
    ...
    read -p "Do you want to echo name? surname otherwise "
done <<< "$lines"
Run Code Online (Sandbox Code Playgroud)

两者read确实从同一个输入读取,但我认为(没有测试)你可以通过重定向到循环使用另一个文件描述符来解决这个问题,例如:

while IFS=... read -u 3 line ; do
    ...
    read -p "Do you want to echo name? surname otherwise "
done 3<<< "$lines"
Run Code Online (Sandbox Code Playgroud)

或者read <&3代替read -u,我不知道这是否重要。


ter*_*don 6

使用实际列表(数组)而不是摆弄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
Run Code Online (Sandbox Code Playgroud)

当然,这假设您只有一个名字和一个姓氏,这对于真实的人来说是不正确的,但对于您的数据可能是正确的。无论如何,您的原始代码做出了相同的假设,所以我猜这对您来说不是问题。

您也可以通过在子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.
Run Code Online (Sandbox Code Playgroud)