我刚刚分配了一个变量,但 echo $variable 显示了其他内容

下面是一系列的例子,在这些例子中,echo $var可以显示与刚才分配的值不同的值。无论分配的值是“双引号”、“单引号”还是未引号,都会发生这种情况。

如何让 shell 正确设置变量?

星号

预期的输出是 /* Foobar is free software */,但是我得到了一个文件名列表:

$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...

方括号

期望值是 [a-z],但有时我得到一个单一的字母代替!

$ var=[a-z]
$ echo $var
c

换行(换行)

期望值是一个单独行的列表,但是所有的值都在一行上!

$ cat file
foo
bar
baz


$ var=$(cat file)
$ echo $var
foo bar baz

多个空格

我期望的是一个仔细对齐的表头,但是相反,多个空格要么消失,要么折叠成一个!

$ var="       title     |    count"
$ echo $var
title | count

标签

我以为会有两个制表符分隔的值,结果却得到了两个空格分隔的值!

$ var=$'key\tvalue'
$ echo $var
key value
328596 次浏览

在上述所有情况下,变量设置正确,但读取不正确!正确的方法是去 在引用 时使用双引号 < em > :

echo "$var"

这给出了所有示例中的期望值。始终引用变量引用!


为什么?

当一个变量为 未被引用时,它将:

  1. 执行 场分裂,其中值在空白处被分割为多个单词(默认情况下) :

    之前: /* Foobar is free software */

    之后: /*Foobarisfreesoftware*/

  2. 这些单词中的每一个都将经历 路径名扩展,在 路径名扩展中,模式被扩展为匹配的文件:

    之前: /*

    之后: /bin/boot/dev/etc/home,..。

  3. 最后,将所有参数传递给 echo,echo 将它们写出 用单个空格隔开,给出

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
    

    而不是变量的值。

当变量为 引用时:

  1. 用来代替它的价值。
  2. 没有第二步。

这就是为什么您应该使用 始终引用所有变量引用,除非您特别需要分词和路径名扩展。像 贝壳检查这样的工具可以提供帮助,并且在以上所有情况下都会警告引号丢失。

除了引用变量之外,还可以使用 tr转换变量的输出,并将空格转换为换行。

$ echo $var | tr " " "\n"
foo
bar
baz

虽然这有点复杂,但它确实增加了输出的多样性,因为您可以用任何字符替换数组变量之间的分隔符。

你可能想知道为什么会这样。与 另一个家伙的伟大解释一起,在 Unix & Linux中找到由 吉尔斯写的 为什么我的 shell 脚本在使用空格或其他特殊字符时会卡壳?的参考文献:

为什么我需要写 "$foo"? 没有引号会发生什么?

$foo并不意味着“取变量 foo的值”,它的意思是 更复杂的东西:

  • 首先,获取变量的值。
  • 字段分割: 将该值视为以空格分隔的字段列表,并构建结果列表。例如,如果变量 包含 foo * bar ​,那么这个步骤的结果是3个元素 列出 foo*bar
  • 文件名生成: 将每个字段视为一个通配符,即通配符模式,并将其替换为与之匹配的文件名列表 如果模式不匹配任何文件,则将其保留 在我们的示例中,这将导致包含 foo的列表, 接着是工作目录中的文件列表,最后是 如果工作目录是空的,结果就是, bar.

注意,结果是一个字符串列表 Shell 语法: 列表上下文和字符串上下文 文件名生成只发生在列表上下文中,但这是大多数 双引号分隔字符串上下文: 整个 双引号字符串是单个字符串,不能拆分。(例外: 扩展到位置参数列表,例如 "$@"是 如果有三个位置,则相当于 "$1" "$2" "$3" (请参阅 $* 和 $@ 有什么区别?)

同样的情况也会发生在指令替代上 注意,不要使用 `foo`: 它的引用规则是 怪异和不便携,所有现代的外壳支持 $(foo) 除了具有直观的引用规则外,是绝对等价的。

算术替换的输出也经历同样的过程 但这通常不是一个问题,因为它只包含 不可展开的字符(假设 IFS不包含数字或 -).

有关 当你可以省略引号的情况下。

除非你想让这些繁文缛节发生,记住了 总是在变量和命令替换前后使用双引号 注意: 省略引用不仅会导致错误,而且会导致 保安 洞

echo $var输出在很大程度上取决于 IFS变量的值。默认情况下,它包含空格、制表符和换行符:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$

这意味着当 shell 进行字段分割(或单词分割)时,它使用所有这些字符作为单词分隔符。当引用一个没有双引号的变量来回显它($var)时,就会发生这种情况,因此预期的输出会发生改变。

防止分词的一种方法(除了使用双引号)是将 IFS设置为 null。参见 http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05:

如果 IFS 的值为空,则不执行字段分割。

设置为空意味着设置为空 价值:

IFS=

测试:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$

用户双引号得到确切的值。如下:

echo "${var}"

它会正确读取你的值。

除了由于未能报价而引起的其他问题外,-n-e可以作为参数被 echo使用。(按照 echo的 POSIX 规范,只有前者是合法的,但是一些常见的实现违反了这个规范,并且使用了 -e)。

为了避免这种情况,当细节重要时,使用 ABC0而不是 echo

因此:

$ vars="-e -n -a"
$ echo $vars      # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a

然而,当使用 echo时,正确的引用并不总是能够节省您的时间:

$ vars="-n"
$ echo "$vars"
$ ## not even an empty line was printed

威尔printf救了你:

$ vars="-n"
$ printf '%s\n' "$vars"
-n

在使用 docker-compose exec时,答案来自 ks1322帮助我发现了这个问题:

如果省略 -T标志,docker-compose exec添加一个中断输出的特殊字符,我们将看到 b而不是 1b:

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

随着 -T标志的出现,docker-compose exec按预期工作:

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b