Shell中,将IFS设置为仅换行符时为什么不能正常工作?以下是示例代码:IFS=$(echo -e '\n')。

7
我试图在shell中使用for循环遍历带有空格的文件名。我在一个stackoverflow问题中读到IFS=$'\n'可以使用,这似乎很有效。然后我在答案的评论中读到,为了使它在bash之外的shell中具有posix兼容性,可以使用IFS=$(echo -e '\n')
前者有效,但后者无效。该评论已获得数次赞同。我检查了输出和变量似乎不包含换行符。
#!/bin/sh

IFS=$(echo -e '\n')
echo -n "$IFS" | od -t x1
for file in `printf 'one\ntwo two\nthree'`; do
    echo "Found $file"
done

echo

IFS=$'\n'
echo -n "$IFS" | od -t x1
for file in `printf 'one\ntwo two\nthree'`; do
    echo "Found $file"
done

输出显示第一个字段缺少字段分隔符:
0000000
Found one
two two
three

但是在第二个上是正确的:
0000000 0a
0000001
Found one
Found two two
Found three

我找到了一个已解答的问题,它可能是我的问题的重复:
linux - 当设置 IFS 以按换行符拆分时,为什么需要包含退格符?- Stack Overflow

那样做有没有任何风险,IFS=$(echo -en '\n\b')?因为这会给 IFS 添加一个 \b(我已经检查过了)。文件名中是否可能出现 \b?我不想让代码错误地拆分文件名。

另外,您有没有关于如何正确处理 for 循环之后 IFS 的恢复的建议?我应该将其存储在临时变量中,还是在子 shell 中运行 IFS 和 for 语句?(如何实现?)谢谢

4个回答

7

更新 - 将我的伪注释更改为真正的答案。

我认为这个答案应该能解释你所看到的行为。具体来说,命令替换运算符$()和反引号将从命令输出中删除尾随的换行符。但是,你第二个示例中的直接赋值没有执行任何命令替换,因此按预期工作。

所以我很抱歉地说,我认为你所提到的被赞评论是不正确的。


我认为恢复IFS的最安全方法是在子shell中设置它。你需要做的就是将相关命令放在括号()中:

(
    IFS=$'\n'
    echo -n "$IFS" | od -t x1
    for file in `printf 'one\ntwo two\nthree'`; do
        echo "Found $file"
    done
)

当然,调用子shell会导致小延迟,因此如果要重复执行此操作,则需要考虑性能。


顺便提一下,文件名可能包含\b和\n。我认为它们唯一不能包含的字符就是斜杠和\0。至少这就是这篇维基百科文章所说的。

$ touch $'12345\b67890'
$ touch "new
> line"
$ ls --show-control-chars
123467890  new
line
$ 

6

由于命令替换会去除换行符,正如@DigitalTrauma所写的那样,您应该使用一种不同的、符合POSIX标准的方法。

IFS='
'

在新的一行中输出并将其分配给它。简单而有效。

如果您不喜欢跨越两行的赋值语句,可以使用 printf 作为替代方法。

IFS="$(printf '\n')"

非常感谢您提供的信息,我会牢记在心。 - user2672807
请删除您的第二个示例,因为它不够干净: IFS="$(printf '\n+')" $ echo -n "$IFS" | xxd 00000000: 0a2b <- 这也将 + 添加到 IFS 中。 - Tom Hale
@TomHale 我已经移除了不必要的 + 符号。 - Riccardo Galli
你测试过了吗?这个不起作用的原因在这里给出了。 - Tom Hale
我试过了,它可以工作。顺便说一下,我是在Ubuntu上的rxvt-unicode上进行测试的(所以我想是/bin/sh用的是dash)。 - Riccardo Galli

1
IFS=$(printf '\n+'); IFS=${IFS%?}

正如其他人所提到的,命令替换会删除尾随的换行符。因此,如果您想要一个“可读”的东西,那么在换行符后面添加一个字符(在本例中为“+”)以防止其被删除,然后使用参数扩展来删除占位符字符。

0
要保持POSIX兼容性(如果你的shebang字符串是#!/bin/sh,并且你希望优先考虑跨平台兼容性),你可以使用以下方法:
#!/bin/sh

# not very elegant (multiline string)
IFS='
'

或者

#!/bin/sh

IFS="$(printf '\n')"

如果您没有保留 POSIX 兼容性的特定要求,那么这种写法更易读:

#!/bin/bash

IFS=$'\n'

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