我正在尝试在shell中使用for
来迭代带有空格的文件名。我在stackoverflow question中读到可以使用IFS=$'\n'
这似乎工作正常。然后我读了in a comment to one of the answers,为了使其与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 - When setting IFS to split on newlines, why is it necessary to include a backspace? - Stack Overflow
在那里讨论的内容是否有任何危险,IFS=$(echo -en '\n\b')
?因为它为IFS添加了\b
(我检查过)。 \b
可以出现在文件名中吗?我不希望我的代码错误地拆分文件名。
对于for
循环后如何正确处理IFS恢复,您还有什么建议吗?我应该将它存储在临时变量中,还是在子shell中运行IFS和for语句? (以及如何做到这一点?)谢谢
答案 0 :(得分:6)
更新 - 将我的伪评论更改为真实答案。
我认为this answer应该解释你所看到的行为。具体而言,命令替换运算符$()
和反引号将从命令输出中删除尾随换行符。但是,第二个示例中的直接赋值不执行任何命令替换,因此按预期工作。
所以我不敢说我认为你提到的赞成评论不正确。
我认为恢复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。至少这就是它在wikipedia article上所说的内容。
$ touch $'12345\b67890'
$ touch "new
> line"
$ ls --show-control-chars
123467890 new
line
$
答案 1 :(得分:5)
由于命令替换会删除换行符,正如@DigitalTrauma正确编写的那样,您应该使用不同的POSIX兼容方式。
IFS='
'
写一个换行符并将其分配给它。简单而有效。
如果您不喜欢跨越两行的作业,则可以使用printf
作为替代。
IFS="$(printf '\n')"
答案 2 :(得分:1)
IFS=$(printf '\n+'); IFS=${IFS%?}
正如其他人提到的,命令替换会删除尾随的换行符。所以,如果你想要一些“可读”的东西,那么在换行符之后添加一个字符(在本例中为'+')以防止它被删除,然后使用参数扩展来删除占位符。