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

tha*_*guy 87 bash shell sh quoting

以下是一系列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 ...
Run Code Online (Sandbox Code Playgroud)

方括号

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

$ var=[a-z]
$ echo $var
c
Run Code Online (Sandbox Code Playgroud)

换行(换行符)

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

$ cat file
foo
bar
baz

$ var=$(cat file)
$ echo $var
foo bar baz
Run Code Online (Sandbox Code Playgroud)

多个空间

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

$ var="       title     |    count"
$ echo $var
title | count
Run Code Online (Sandbox Code Playgroud)

标签

我期望两个制表符分隔值,但我得到两个空格分隔值!

$ var=$'key\tvalue'
$ echo $var
key value
Run Code Online (Sandbox Code Playgroud)

tha*_*guy 108

在上述所有情况中,变量都已正确设置,但未正确读取!正确的方法是在引用使用双引号:

echo "$var"
Run Code Online (Sandbox Code Playgroud)

这给出了给出的所有示例中的期望值.总是引用变量引用!


为什么?

当变量未加引号时,它将:

  1. 进行字段拆分,其中值在空格上拆分为多个单词(默认情况下):

    之前: /* Foobar is free software */

    后:/*,Foobar,is,free,software,*/

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

    之前: /*

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

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

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
    
    Run Code Online (Sandbox Code Playgroud)

    而不是变量的值.

引用变量时,它将:

  1. 取代它的价值.
  2. 没有第2步.

这就是为什么你应该总是引用所有变量引用,除非你特别需要单词拆分和路径名扩展.像shellcheck这样的工具可以提供帮助,并会在上述所有情况下警告缺少引号.

  • 是的,`$(..)` 会去除尾随换行符。您可以使用 `var=$(cat file; printf x); var="${var%x}"` 来解决这个问题。 (2认同)

fed*_*qui 14

您可能想知道为什么会这样.再加上其他人的精彩解释,找到为什么我的shell脚本会在空格或其他特殊字符上窒息?GillesUnix和Linux中编写:

我为什么要写"$foo"?没有报价会怎么样?

$foo并不意味着"获取变量的值foo".这意味着更复杂的事情:

  • 首先,取变量的值.
  • 字段拆分:将该值视为以空格分隔的字段列表,并构建结果列表.例如,如果该变量包含foo * bar ?然后此步骤的结果为第3元素的列表foo,*,bar.
  • 文件名生成:将每个字段视为glob,即作为通配符模式,并将其替换为与此模式匹配的文件名列表.如果模式与任何文件都不匹配,则保持不变.在我们的示例中,这导致列表包含foo,后面是当前目录中的文件列表,最后是 bar.如果当前目录是空的,结果是foo,*, bar.

请注意,结果是字符串列表.shell语法中有两种上下文:list context和string context.字段拆分和文件名生成仅发生在列表上下文中,但这是大部分时间.双引号分隔字符串上下文:整个双引号字符串是单个字符串,不能拆分.(例外: "$@"扩展到位置参数列表,例如"$@"相当于"$1" "$2" "$3"有三个位置参数.请参阅$*和$ @之间的区别?)

命令用$(foo)或用命令替换也是如此 `foo`.请注意,不要使用`foo`:它的引用规则很奇怪且不可移植,$(foo)除了具有直观的引用规则外,所有现代shell都支持绝对等效.

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

什么时候需要双引号?有关可以省略引号的情况的更多详细信息.

除非你的意思是发生所有这些乱七八糟的事情,否则请记住在变量和命令替换周围总是使用双引号.请注意:省略引号不仅会导致错误,还会导致 安全漏洞.


van*_*hou 9

用户双引号以获取确切值。像这样:

echo "${var}"
Run Code Online (Sandbox Code Playgroud)

它会正确读取您的值。


Cha*_*ffy 5

除了其他问题造成无法引用,-n并且-e可以通过消耗echo作为参数。(根据POSIX规范echo,只有前者是合法的,但是几种常见的实现方式也违反了该规范并消耗-e)。

为了避免这种情况,使用printf而不是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
Run Code Online (Sandbox Code Playgroud)

但是,使用时,正确的引用并不一定能节省您的时间echo

$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed
Run Code Online (Sandbox Code Playgroud)

......而这节省你printf

$ vars="-n"
$ printf '%s\n' "$vars"
-n
Run Code Online (Sandbox Code Playgroud)