多行双引号字符串会触发其通过管道传输到的后续单引号命令的历史扩展

Multi-line, double quoted string triggers history expansion on subsequent single-quoted commands it gets piped to

我使用的是 GNU bash,版本 4.3.11。

假设我想在文件上打印独特的行。我正在使用这种方法,它适用于文件:

$ cat a
9
10
9
11
$ awk '!seen[[=10=]]++' a
9
10
11

但是,如果我从 stdin 获取输入,在多行中使用双引号并通过管道传输到 awk,它会失败:

$ echo "9
> 10
> 9
> 11" | awk '!seen[[=11=]]++'
bash: !seen[[=11=]]++': event not found

即bash试图扩展命令seen,这当然不知道,因为它是一个变量名。但这不应该发生,因为命令放在单引号内。

echo在单引号、多行输入中效果很好:

$ echo '9
> 10
> 9
> 11' | awk '!seen[[=12=]]++'
9
10
11

有趣的是,它也适用于双引号的单行输入:

$ printf "9\n10\n9\n11" | awk '!seen[[=13=]]++'
9
10
11

我想知道为什么 Bash 如果它发生在多行输入之后尝试扩展历史记录,即使命令本身使用单引号。

其他注意事项:

中间有一个管道也不能解决问题:

$ echo "9
> 10
> 9
> 11" | cat - | awk '!seen[[=14=]]++'
bash: !seen[[=14=]]++': event not found

并设置 set +H turns history off,因此它运行良好,因为它不会尝试扩展任何内容:

$ set +H
$ echo "9
> 10
> 9
> 11" | awk '!seen[[=15=]]++'
9
10
11

我仔细阅读了 rici 在 how to address error “bash: !d': event not found” in Bash command substitution 上的规范回答,发现了很多可能的原因,但 none 符合这种行为。

这不是错误。

在 bash-bugs 邮件列表中询问后,我得到了以下答案:

History expansion is explicitly line-oriented.

It doesn't know about shell state, especially shell quoting state, that spans lines.

It does know about vaguely shell-like quoting that's common across a large set of Unix utilities -- since the history and readline libraries are used outside the shell -- and that a double quote introduces a quoted string in which single quotes are not significant and don't inhibit history expansion.