这个例子中,bash的IFS是如何工作的?

3

我正在阅读有关bash内部变量的内容,并遇到了这个IFS示例:

output_args_one_per_line()
{
  for arg
  do
    echo "[$arg]"
  done #  ^    ^   Embed within brackets, for your viewing pleasure.
}

案例1

IFS=" "
var=" a  b c   "

output_args_one_per_line $var
#OUTPUT
# [a]
# [b]
# [c]

CASE2

IFS=:
var=":a::b:c:::"               # Same pattern as above
CASE2   
                        # but substituting ":" for " "  ...
output_args_one_per_line $var
# []
# [a]
# []
# [b]
# [c]
# []
# []

现在,根据我的理解,如果 IFS 的值是默认的 \t\n,那么前导和尾随空格将被删除。因此,在 case1 中,bash 将 var 视为 a b c,因此输出结果。对于 case2,我认为 bash 将 var 视为 |a||b|c|||,并将 | 视为 空格。我使用以下方式进行了检查:
Noob@Noob:~/tmp$ IFS=$':' FOO=$":a::b:c:::"; echo $FOO $'x'
 a  b c   x

因此,我对第二种情况的预期输出是:
# []
# [a]
# []
# []
# [b]
# [c]
# []
# []
# []

那么,有人能在内部解释一下bash如何处理第二种情况中的var,以及我对此理解哪里出了问题吗。


1
OT: 你的 output_args_one_per_line 函数可以被 printf "[%s]\n" $var 替换。 - Mark Reed
1
查看在展开 var 变量时使用 bash -x 命令分割的情况。请参考我回答中的编辑。 - c00kiemon5ter
4个回答

3
你的说法(已编辑为使用“:”而不是“|”):
针对我的case2,我认为bash将var视为:a::b:c::: 在这里把“:”视为空格。
是错误的。IFS使bash将“:”视为单词分隔符,而不是空白符。不要因为空格是默认的单词分隔符而混淆两者。

你说得对,我刚才说错了,实际上我的意思是bash将a:b转换为a b,使用空格作为分隔符。 - RanRag

1

:是分隔符。a:b被分成ab,中间没有任何内容。在您期望的行为中,您会如何编码以下内容?

[a]
[]
[b]

唯一奇怪的是末尾没有三个空字符串。这可能是因为未带引号的隐式空参数(由于没有值而导致的参数扩展)被删除了。

如果我理解得正确,那么a::b被视为a(space)(empty word)b,这将导致a[]b。您能否详细说明一下“奇怪的事情”,因为我是一个bash新手,没有例子我很难理解官方文档。 - RanRag
你需要从开头到下一个遇到的 IFS 进行分割。然后从那个点到下一个遇到的 IFS,以此类推。如果字符串中出现像 :: 这样的内容,则会返回 [],也就是空字符串,因为在第一个 : 和下一个 : 之间没有任何内容,但是仍然进行了分割。 - c00kiemon5ter
@Noob - 在您的期望输出中,您声称a::bab之间应该有两个空字符串,而实际上只需要一个。 - Mark Reed
@c00kiemon5ter:谢谢,解释得非常有帮助。 - RanRag
1
去除尾随的空参数(但不是前导参数)来自POSIX标准的措辞。据报道,在委员会上进行了激烈的辩论,这是许多关心这种事情的人不喜欢的决定。但至少在各种shell(ksh,{d,}ash,zsh等)之间保持一致。这只是你必须考虑到的那些怪癖之一。 - Mark Reed

1
在第二个案例中,您有“:a::b:c:::”IFS=:,因此您正在将字符串拆分为每个遇到的
因此,从字符串开头到第一个,您有“:,这是空的,因此[]
从第一个到下一个,您有:a:,这是a,因此[a]
从那里到下一个,您有::,这是空的,因此[]
从那里到下一个,您有:b:,这是b,因此[b]
从那里到下一个,您有:c:,这是c,因此[c]

从那里到下一个:你有::
这是什么都没有,因此[]

从那里到下一个:你有::"
这是什么都没有,因此[]

所以你得到了那个结果..


正如 @Mark Reed 在评论中提到的,您可以使用 printf
bash -x 一起使用,您将得到:

$ bash -x
$ IFS=':'; var=':a::b:c:::'; printf "[%s]\n" $var
[12]+ IFS=:
[12]+ var=:a::b:c:::
[12]+ printf '[%s]\n' '' a '' b c '' ''     # notice this
[]
[a]
[]
[b]
[c]
[]
[]

1

原因非常简单:

IFS=" "
var=" a  b c   "

output_args_one_per_line $var

这意味着使用以下参数调用output_args_one_per_line函数:

output_args_one_per_line  a  b c   

在解析命令行时,BASH会删除额外的空格,因此实际调用将使用

output_args_one_per_line a b c

即将多个空格合并为一个,而 a 前的空格将成为命令和第一个参数之间的空格。

这意味着空格将在应用 IFS 之前消失。这也意味着你不能写

IFS=:
output_args_one_per_line:$var

命令后必须有一个空格,而不是单词分隔符。

您可以使用set -x运行脚本以查看跟踪输出(即BASH如何扩展行)。

在第二种情况下,单词分隔符不是空格字符,因此命令和第一个参数之间必要的空格未与参数合并,该行变为

output_args_one_per_line :a::b:c:::

唯一奇怪的是,在 c 之后输出应该是三个空参数,但这可能是因为空尾参数被删除了(就像 BASH 删除参数后的空格一样)。这里还有另一个奇怪的输出:
IFS=:
var=":a::b:c::: "   # Blank after C
> output_args_one_per_line $var
[]
[a]
[]
[b]
[c]
[]
[]
[ ]

所以如果var在最后一个冒号后面包含任何内容,我们就会得到缺失的参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接