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

I just assigned a variable, but echo $variable shows something else

提问人:that other guy 提问时间:4/1/2015 最后编辑:that other guy 更新时间:4/24/2022 访问量:365244

问:

以下是一系列情况,其中可以显示与刚刚分配的值不同的值。无论分配的值是“双引号”、“单引号”还是未加引号,都会发生这种情况。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
bash shell sh 引用

评论

4赞 userABC123 4/1/2015
谢谢你这样做。我经常遇到换行。所以很好,但需要。var=$(cat file)echo "$var"
3赞 Charles Duffy 5/21/2015
顺便说一句,这也是 BashPitfalls #14:mywiki.wooledge.org/BashPitfalls#echo_.24foo
0赞 tripleee 7/9/2015
另请参阅 stackoverflow.com/questions/10067266/...
0赞 tripleee 12/19/2015
另请参阅 stackoverflow.com/questions/2414150/...

答:

198赞 that other guy 4/1/2015 #1

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

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. 没有步骤 2。

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

评论

0赞 recolic 5/5/2019
它并不总是有效。我可以举个例子:paste.ubuntu.com/p/8RjR6CS668
2赞 that other guy 6/7/2019
是的,剥离尾随换行符。您可以使用它来解决它。$(..)var=$(cat file; printf x); var="${var%x}"
-1赞 Alek 5/21/2015 #2

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

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

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

评论

3赞 user000001 5/21/2015
但这将所有空格替换为换行符。引用将保留现有的换行符和空格。
0赞 Alek 5/21/2015
没错,是的。我想这取决于变量中的内容。我实际上使用相反的方式从文本文件创建数组。tr
3赞 tripleee 2/1/2016
通过不正确地引用变量然后用一个笨拙的额外进程来解决它来制造问题,这不是好的编程。
0赞 Charles Duffy 1/24/2017
@Alek,......呃,什么?无需从文本文件正确/正确地创建数组 - 您可以通过设置 IFS 来指定所需的任何分隔符。例如:一直到 bash 3.2(广泛流传的最古老的版本)工作,如果失败,则正确地将退出状态设置为 false。如果你想要,比如说,用制表符代替换行符,你只需将 替换为 .trIFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')cat$'\n'$'\t'
1赞 Charles Duffy 1/24/2017
@Alek,......如果你正在做类似的事情,那么它在多个层面上就被破坏了:它正在对结果进行通配(所以 a 会变成当前目录中的文件列表),如果没有 tr(或者 ,就此而言,它可以很好地工作;人们可以直接使用,它会以同样的方式被破坏,但效率较低)。arrayname=( $( cat file | tr '\n' ' ' ) )*catarrayname=$( $(<file) )
26赞 fedorqui 7/9/2015 #3

您可能想知道为什么会发生这种情况。连同那个人的精彩解释,找到一个参考资料 为什么我的 shell 脚本会窒息在空格或其他特殊字符上?GillesUnix 和 Linux 中编写:

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

$foo并不意味着“取变量的值”。这意味着 更复杂的东西:foo

  • 首先,取变量的值。
  • 字段拆分:将该值视为以空格分隔的字段列表,并生成结果列表。例如,如果变量 包含,则此步骤的结果是 3 元素 列表。foo * bar ​foo*bar
  • 文件名生成:将每个字段视为一个 glob,即通配符模式,并将其替换为与此匹配的文件名列表 模式。如果模式与任何文件都不匹配,则保留该模式 未修改。在我们的示例中,这会导致包含 的列表, 接下来是当前目录中的文件列表,最后是 .如果当前目录为空,则结果为 、 、 。foobarfoo*bar

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

使用或使用 的命令替换也会发生同样的情况。顺便说一句,不要使用 :它的引用规则是 奇怪且不可移植,所有现代 shell 都支持 除了有直观的报价规则外,是绝对等价的。$(foo) `foo``foo`$(foo)

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

请参阅何时需要双引号? 您可以省略引号的情况。

除非你的意思是让所有这些僵化发生,否则请记住 始终在变量和命令替换两边使用双引号。做 注意:省略引号不仅会导致错误,还会导致安全性 孔

3赞 ks1322 11/15/2015 #4

echo $var输出高度依赖于变量的值。默认情况下,它包含空格、制表符和换行符:IFS

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

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

防止单词拆分的一种方法(除了使用双引号)是设置为 null。查看 http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05IFS

如果IFS为null,则不进行字段拆分。

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

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 ~]$ 

评论

3赞 that other guy 11/16/2015
您还必须防止通配set -f
0赞 ks1322 11/16/2015
@thatotherguy,对于具有路径扩展的第一个示例,真的有必要吗?设置为 null 时,将扩展为 并且路径扩展不在单引号字符串内执行。IFSecho $varecho '/* Foobar is free software */'
1赞 that other guy 11/17/2015
是的。如果你,你会看到它仍然在扩展。对于这个例子来说,这显然更实用。mkdir "/this thing called Foobar is free software etc/"[a-z]
0赞 ks1322 11/17/2015
我明白了,例如这是有道理的。[a-z]
0赞 CS QGB 6/11/2021
root@ubuntu:/home/qgb# var=32321 echo $var root@ubuntu:/home/qgb# var=3231; echo $var 3231
13赞 vanishedzhou 8/3/2018 #5

用户双引号可获取确切值。喜欢这个:

echo "${var}"

它将正确读取您的值。

评论

0赞 CS QGB 6/11/2021
root@ubuntu:/home/qgb# var_a=100 echo ${var_a}
13赞 Charles Duffy 9/22/2018 #6

除了引用失败导致的其他问题外,还可以作为参数使用。(根据 POSIX 规范,只有前者是合法的,但一些常见的实现也违反了规范并消耗)。-n-eechoecho-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

但是,在使用以下方法时,正确的引用并不总是能挽救您:echo

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

...而它将为您节省:printf

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

评论

0赞 that other guy 9/22/2018
是的,我们需要一个好的 dedup!我同意这符合问题标题,但我认为它不会在这里获得应有的知名度。一个新问题怎么样“为什么我的 //反斜杠没有出现?我们可以根据需要从这里添加链接。-e-n
0赞 PesaThe 1/7/2019
你的意思是也消耗 -n 吗?
1赞 Charles Duffy 1/7/2019
@PesaThe,不,我的意思是.当 for 的第一个参数是 时,标准不指定输出,使得任何/所有可能的输出在这种情况下都是合法的;没有这样的规定。-eecho-n-e
2赞 A.L 11/13/2019 #7

ks1322 的答案帮助我在使用时识别了问题:docker-compose exec

如果省略该标志,请添加一个特殊字符来破坏输出,我们看到而不是:-Tdocker-compose execb1b

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

使用标志,按预期工作:-Tdocker-compose exec

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