当将IFS设置为按换行符拆分时,为什么需要包含退格键?

83

我很好奇为什么在设置IFS以按换行符分割时需要使用退格键,就像这样:

IFS=$(echo -en "\n\b")

为什么我不能只使用这个(它不起作用)?

IFS=$(echo -en "\n")

我使用的是保存文本为Unix行结尾符的Linux系统。我已将我的带有换行符的文件转换为十六进制,并且它只使用“0a”作为换行符。

我进行了很多搜索,虽然有很多页面记录了换行符后跟退格键的解决方案,但我没有找到任何解释为什么需要使用退格键。

-David。


请问有人能告诉我在哪里可以找到这样的代码吗?谢谢。 - spbnick
@spbnick 当你想要迭代文件时,例如在 for file in $(ls) ; do echo "$f" ; done 中,你需要重置IFS。如果不将IFS设置为仅换行符,则for循环将逐个输出每个文件名中的每个以空格分隔的块,而不是整个文件名。 - bleistift2
谢谢@bleistift2,我明白为什么有人想要更改IFS,也理解代码的工作原理(我的答案是这里最受欢迎的)。我只是好奇在IFS中使用退格字符的特定用途来自何处,历史上是谁想出来的。因此,我想问问人们在哪里看到过这种代码。 - spbnick
@spbnick 真是一个好问题,我也很想知道答案。这确实是一种令人费解和误导的方式。而且是没有意义的!如果你想将 shell 变量 X 设置为 "a",你只需要说 X="a" 就行了;你不必说 X=$(echo "a")。更何况,在这里我们实际上正在设置 IFS 为某些会额外拆分退格符的东西,如果输入恰好包含它!如果问题是“为什么?”,那么你的回答是正确的,但如果问题是“设置 IFS 为换行符的好方法是什么?”,我必须赞扬 moddie 的答案。 - Steve Summit
5个回答

165

正如bash手册关于命令替换所述:

Bash通过执行命令并使用命令的标准输出来扩展命令替换,并删除任何尾随的换行符,从而执行扩展。

因此,通过添加\b,可以防止\n被删除。

更清晰的方法是使用$''引用,像这样:

IFS=$'\n'

1
$'...' 旨在被包含在 POSIX 中。[http://mywiki.wooledge.org/Bashism] [http://austingroupbugs.net/view.php?id=249] - go2null
2
虽然这些应该在某个时候包含在Posix中,但目前它们并没有被包含进去,在许多链接到posix shell而不是bash的/bin/sh中无法使用。 - John Eikenberry
1
但是当我想使用换行符和读取命令来拆分数组时,只有第一个项目被添加到hostArray中。但是主机有4个项目。host=$(mysql -S $socket $catalog --skip-column-names -e "select host from mysql.user); echo "Host: $host"; IFS=$'\n';read -ra hostArray <<< "$host"; echo "HostArray:$hostArray" #This prints only 1 item. But there are 4 items; 这里出了什么问题? - Guna

64

我刚想起最简单的方法。在debian wheezy上使用bash测试过。

IFS="
"

别开玩笑 :)


3
但这不是一个简单的命令 - 特别是如果你需要恢复旧的 $IFS,例如:https://dev59.com/5XM_5IYBdhLWcg3wXx5N#C5ycEYcBWogLw_1bi7tb - juanmirocks
1
老兄,你让我开心坏了!不是开玩笑,而且还能在 POSIX 上工作。 - Aleksey Balenko
@juanmirocks 为什么这个解决方案会让恢复旧的 IFS 更加困难呢? - Steve Summit
@SteveSummit 我现在还没有测试过它。但我认为你将不得不很好地引用 "oldIFS",以避免任何陷阱。 - juanmirocks

14

由于使用了echo和命令替换,这是一种黑客技巧。

prompt> x=$(echo -en "\n")
prompt> echo ${#x}
0
prompt> x=$(echo -en "\n\b")
prompt> echo ${#x}
2

$() 会去掉末尾的空行,\b 防止 \n 成为末尾的空行,同时它不太可能在任何文本中出现。使用 IFS=$'\n' 可以更好地将 IFS 设置为按换行符进行拆分。


2
这表明\n\b格式也将退格符作为IFS分隔符,因此在某些情况下我认为它是有问题的。 - Tom Hale

10
由于在命令替换 $(...) 中去除尾随的 \n,因此将 \b 字符作为换行符 \n 的后缀添加。因此,\b 用作 \n 的后缀,这样 \n 就不再是尾随的了,因此它从命令替换中返回。
副作用是,IFS 也将包含 \b 字符作为分隔符,而不仅仅是 \n,而我们真正关心的只有 \n。
如果您希望字符串中可能会出现 \b(为什么不呢?),那么可以使用:
IFS="$(printf '\nx')" && IFS="${IFS%x}";

该函数返回带有字符 "x" 后缀的字符串,同时去除了字符 "x"。

现在 IFS 只包含字符 "\n"。

IFS="$(printf '\nx')" && IFS="${IFS%x}";
echo ${#IFS}; # 1

在使用 \b 时不会出现任何问题,测试:

#!/bin/sh

sentence=$(printf "Foo\nBar\tBaz Maz\bTaz");
IFS="$(printf '\nx')" && IFS="${IFS%x}";

for entry in $sentence
do
    printf "Entry: ${entry}.\n";
done

给出两行(因为有一个\n):
Entry: Foo.
Entry: Bar      Baz Maz Taz.

如预期所料。

IFS="$(printf '\nx')" && IFS="${IFS%x}"; 使用:

IFS="
"

两种写法会得到同样的结果,但这两行代码不能有缩进。如果你在双引号之间意外地输入了空格、制表符或其他任何空白字符,那么你将不仅有\n字符,还有一些"额外奖励"。

这个错误很难发现,除非你使用编辑器中的“显示所有字符”选项。


1
在符合POSIX标准的脚本中,使用printf而不是echo是强制性的,并且使用IFS=$(printf '\nx'); IFS=${IFS%?}而不是IFS="<literal newline>"更加清晰,我个人认为。 - Fravadona
我的代码保留了第二个变量中的换行符,难道它不应该将其去除吗? - JPM
@JPM 你具体指的是哪个例子? - Jimmix

-1

只是按下回车键而不进行任何赋值也可以工作。虽然看起来像是有人犯了错误,难以理解。

IFS=
#This is a line

4
这个命令不是将 $IFS 设置为空字符串吗? - Marius Gedminas
好的,你需要在“=”符号后按下回车才能使其工作。我尝试过它是有效的。@moddie的答案看起来更清晰。 - taiyebur
2
这是不正确的。它在功能上等同于 IFS="",而这不是所要求的。IFS= 创建一个零长度字符串,而 IFS=$'\n' 则创建了一个长度为1个字符的字符串。您可以使用 echo ${#IFS} 进行验证。 - Rich

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