6

我试图for在 shell 中使用空格来迭代文件名。我读了一个可以使用的stackoverflow问题IFS=$'\n'而且似乎工作正常。然后,我在评论中阅读了一个答案,以使其 posix 与 bash 以外的 shell 兼容,可以使用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 以换行符拆分时,为什么需要包含退格键?- 堆栈溢出

做那里讨论的事情有什么危险IFS=$(echo -en '\n\b')吗?因为这增加了\bIFS (我检查过)。可以\b在文件名中出现吗?我不希望我的代码错误地拆分文件名。

另外,您对如何在for循环后正确处理 IFS 恢复有任何建议吗?我应该将它存储在临时变量中,还是在子shell中运行 IFS 和 for 语句?(以及如何做到这一点?)谢谢

4

3 回答 3

6

更新- 将我的伪评论更改为真实答案。

我认为这个答案应该解释你所看到的行为。特别是命令替换运算符$()和反引号将从命令输出中去除尾随换行符。但是,第二个示例中的直接分配不会执行任何命令替换,因此可以按预期工作。

所以我不敢说我​​认为你提到的赞成评论是不正确的。


我认为恢复 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
$ 
于 2013-10-05T00:22:21.977 回答
5

由于换行符被命令替换剥离,正如@DigitalTrauma 正确写的那样,您应该使用不同的、符合 POSIX 的方式。

IFS='
'

写一个换行符并将其分配给它。简单有效。

如果您不喜欢跨越两行的作业,您可以使用printf替代方案。

IFS="$(printf '\n')"
于 2014-10-02T08:32:00.550 回答
1
IFS=$(printf '\n+'); IFS=${IFS%?}

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

于 2021-04-09T18:14:23.380 回答