在bash中,IFS=和IFS=$'\n'有什么区别

在bash中,IFS=和IFS=$'\n'有什么区别

bash中这三个代码块有什么区别吗?

使用IFS=

#!/usr/bin/env bash
while IFS= read -r item; do
    echo "[$item]"
done </dev/stdin

使用IFS=$'\n'

#!/usr/bin/env bash
while IFS=$'\n' read -r item; do
    echo "[$item]"
done </dev/stdin

使用-d $'\n'

#!/usr/bin/env bash
while read -rd $'\n' item; do
    echo "[$item]"
done </dev/stdin

IFS如果两个值和分隔符选项之间存在差异-d,那么在什么情况下会出现差异?

根据我的测试,它们看起来都是一样的:

echo $'one two\nthree\tfour' | test-stdin 
# outputs:
# [one two]
# [three    four]

答案1

IFS=IFS=$'\n'是相同的read(假设read分隔符没有从默认值更改),因为唯一的区别是行内的换行符是否分隔单词,但换行符永远不会出现在行内。

readread -d $'\n'是相同的,因为$'\n'(换行符) 是默认分隔符。

IFS=并对IFS=$'\n'字段分割产生影响:IFS=完全关闭字段分割,而IFS=$'\n'在换行符上分割。

IFS=$'\n'
echo $(echo a; echo b)
# prints "a b" on a single line since $'a\nb' is split at 
# the newline and therefore echo receives two arguments "a" and "b"
IFS=
echo $(echo a; echo b)
# prints "a" and "b" on separate lines $'a\nb' is passed 
# as a single argument to echo

答案2

结合上述优秀资源@贾尔斯的 回答,@choroba的 评论,以及另一个问题的答案。我整理了以下代码示例来说明差异:


IFS(又名内部字段分隔符)指定内联分隔符(接受多个字符,顺序无关)。它默认为IFS=$' \t\n'.仅当read给定多个可变目标时它才相关。

read-d参数指定行分隔符(仅接受第一个字符)。它默认为-d $'\n'.

像这样,

# IFS=, -d $'\n', with tab separated fields, across two lines
echo $'a\tb\tc\nz\tx\ty' | while IFS= read -rd $'\t' a b c; do echo "[$a] [$b] [$c]"; done
# [a] [] []
# [b] [] []
# [c
# z] [] []
# [x] [] []

# IFS=tab, with tab separated fields, across two lines
echo $'a\tb\tc\nz\tx\ty' | while IFS=$'\t' read -r a b c; do echo "[$a] [$b] [$c]"; done
# [a] [b] [c]
# [z] [x] [y]

# IFS=tab, with tab separated fields, across two lines, with only a single variable target
echo $'a\tb\tc\nz\tx\ty' | while IFS=$'\t' read -r a; do echo "[$a]"; done
# [a    b   c]
# [z    x   y]

# IFS=tab, with space and tab separated fields, across two lines
echo $'a b\tc\nz\tx y' | while IFS=$'\t' read -r a b c; do echo "[$a] [$b] [$c]"; done
# [a b] [c] []
# [z] [x y] []

# IFS=tab+space, with space and tab separated fields, across two lines
echo $'a b\tc\nz\tx y' | while IFS=$'\t ' read -r a b c; do echo "[$a] [$b] [$c]"; done
# [a] [b] [c]
# [z] [x] [y]

# IFS=newline, -d '', with space and tab separated fields, across two lines
echo $'a b\tc\nz\tx y' | while IFS=$'\n' read -rd '' a b c; do echo "[$a] [$b] [$c]"; done
# outputs nothing, as no delimiter means no lines for inline splitting

# IFS=newline, -d '', with space and tab separated fields, across two lines, with trailing null character
printf 'a b\tc\nz\tx y\0' | while IFS=$'\n' read -rd '' a b c; do echo "[$a] [$b] [$c]"; done
# outputs a single line, with two newline separated fields:
# [a b  c] [z   x y] []

# IFS=newline, -d $'\0', with space and tab separated fields, across two lines, with trailing null character
printf 'a b\tc\nz\tx y\0' | while IFS=$'\n' read -rd $'\0' a b c; do echo "[$a] [$b] [$c]"; done
# outputs a single line, with two newline separated fields:
# [a b  c] [z   x y] []

像这样,

  • IFS将“字段”拆分为“行”,它是一个“内联”拆分器
  • -d分割“线”,它是一个“线”分割器
  • 自定义IFS来自定义分隔“字段”的内容
  • 自定义-d来自定义分隔“线”的内容

一个有价值的用例-d是按特定顺序单独读取每个字段:

echo $'a b\tc\nz\tx y' | {
    read -rd ' ' a
    echo "a=[$a]"
    read -rd $'\t' b
    echo "b=[$b]"
    read -rd $'\n' c
    echo "c=[$c]"
    read -rd $'\t' z
    echo "z=[$z]"
    read -rd $' ' x
    echo "x=[$x]"
    read -rd $'\n' y
    echo "y=[$y]"
}
# a=[a]
# b=[b]
# c=[c]
# z=[z]
# x=[x]
# y=[y]

像这样,

  • IFSread仅当您的调用接受多个变量目标时才需要定义。
  • 如果您的read调用仅接受单个变量参数,IFS则会被丢弃,这意味着IFS=在这种情况下仅提供修饰功能。

@贾尔斯的 回答涵盖IFS的上下文之外read

这样的用例可以是从包含两个文件的目录中选择一个文件名,一个文件内有空格,另一个文件内没有空格:

cd "$(mktemp -d)" || exit 1
touch 'before-space after-space.txt'
touch 'no-space.txt'

# using arrays
# results in correct fields for selection
mapfile -t list < <(ls -1)
select node in "${list[@]}"; do
    echo "via mapfile, [$node]"
    break
done
echo
# outputs:
# 1) before-space after-space.txt
# 2) no-space.txt
# #? 1
# via mapfile, [before-space after-space.txt]

# using word splitting with default `IFS`
# results in mangled fields for selection
select node in $(ls -1); do
    echo "IFS=default [$node]"
    break
done
echo
# outputs:
# 1) before-space
# 2) after-space.txt
# 3) no-space.txt
# #? 1
# IFS=default [before-space]

# using word splitting with `IFS=$'\n'`
# results in the correct fields for selection
IFS=$'\n'
select node in $(ls -1); do
    echo "IFS=newline [$node]"
    break
done
echo
# outputs:
# 1) before-space after-space.txt
# 2) no-space.txt
# #? 1
# IFS=newline [before-space after-space.txt]

# using word splitting with `IFS=`
# results in a jumbled field for selection
IFS=
select node in $(ls -1); do
    echo "IFS= [$node]"
    break
done
echo
# outputs:
# 1) before-space after-space.txt
# no-space.txt
# #? 1
# IFS= [before-space after-space.txt
# no-space.txt]

相关内容