shell - temp IFS仅作为换行符。为什么这不起作用:IFS = $(echo -e'\ n')

时间:2013-10-04 21:19:23

标签: bash shell ifs

我正在尝试在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语句? (以及如何做到这一点?)谢谢

3 个答案:

答案 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%?}

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