Spa*_*awk 10 bash zsh quoting whitespace here-string
我创建了一个带有制表符分隔字段的文件。
echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input
Run Code Online (Sandbox Code Playgroud)
我有以下脚本命名 zsh.sh
#!/usr/bin/env zsh
while read line; do
<<<$line cut -f 2
done < "$1"
Run Code Online (Sandbox Code Playgroud)
我测试一下。
$ ./zsh.sh input
bar
bar
Run Code Online (Sandbox Code Playgroud)
这工作正常。但是,当我将第一行更改为调用时bash
,它失败了。
$ ./bash.sh input
foo bar baz
foo bar baz
Run Code Online (Sandbox Code Playgroud)
为什么这会失败并bash
与 一起使用zsh
?
env
产生相同的行为。echo
而不是使用 here-string<<<$line
也会产生相同的行为。即echo $line | cut -f 2
。awk
而不是cut
适用于两个外壳。即<<<$line awk '{print $2}'
。Sté*_*las 17
这是因为在<<< $line
,bash
之前的版本4.4没有分词,上(虽然不是通配符)$line
时没有援引那里,然后加入空格字符所产生的词(也放到了一个临时文件后跟一个换行符,并作出这样的标准输入的cut
)。
$ a=a,b,,c bash-4.3 -c 'IFS=","; sed -n l <<< $a'
a b c$
Run Code Online (Sandbox Code Playgroud)
tab
恰好在默认值中$IFS
:
$ a=$'a\tb' bash-4.3 -c 'sed -n l <<< $a'
a b$
Run Code Online (Sandbox Code Playgroud)
解决方案bash
是引用变量。
$ a=$'a\tb' bash -c 'sed -n l <<< "$a"'
a\tb$
Run Code Online (Sandbox Code Playgroud)
请注意,它是唯一执行此操作的 shell。zsh
(<<<
来自哪里,受 Byron Rakitzis 对 的实现启发rc
),ksh93
,mksh
并且yash
也支持<<<
不这样做。
当谈到阵列,mksh
,yash
并zsh
加入上的第一个字符$IFS
,bash
和ksh93
空间。
$ mksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ yash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ ksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ bash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$
Run Code Online (Sandbox Code Playgroud)
当为空时zsh
/yash
和mksh
(至少版本 R52)之间存在差异$IFS
:
$ mksh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
12$
Run Code Online (Sandbox Code Playgroud)
当您使用时,跨外壳的行为更加一致"${a[*]}"
(除了mksh
在$IFS
为空时仍然存在错误)。
在 中echo $line | ...
,这是所有类似 Bourne 的 shell 中常用的 split+glob 运算符,但是zsh
(以及与 相关的常见问题echo
)。
Mic*_*hrs 13
发生的事情是bash
用空格替换制表符。你可以通过说"$line"
来避免这个问题,或者明确地减少空格。
ter*_*don 10
问题是你没有引用$line
. 要进行调查,请更改两个脚本,以便它们简单地打印$line
:
#!/usr/bin/env bash
while read line; do
echo $line
done < "$1"
Run Code Online (Sandbox Code Playgroud)
和
#!/usr/bin/env zsh
while read line; do
echo $line
done < "$1"
Run Code Online (Sandbox Code Playgroud)
现在,比较它们的输出:
$ bash.sh input
foo bar baz
foo bar baz
$ zsh.sh input
foo bar baz
foo bar baz
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,因为您没有引用$line
,所以 bash 无法正确解释选项卡。Zsh 似乎处理得更好。现在,cut
使用\t
的默认字段分隔符。因此,由于您的bash
脚本正在吃制表符(由于 split+glob 运算符),因此cut
只能看到一个字段并相应地采取行动。你真正运行的是:
$ echo "foo bar baz" | cut -f 2
foo bar baz
Run Code Online (Sandbox Code Playgroud)
因此,为了让您的脚本在两个 shell 中都能按预期工作,请引用您的变量:
while read line; do
<<<"$line" cut -f 2
done < "$1"
Run Code Online (Sandbox Code Playgroud)
然后,两者都产生相同的输出:
$ bash.sh input
bar
bar
$ zsh.sh input
bar
bar
Run Code Online (Sandbox Code Playgroud)