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

Posted

技术标签:

【中文标题】我刚刚分配了一个变量,但 echo $variable 显示了其他内容【英文标题】:I just assigned a variable, but echo $variable shows something else 【发布时间】:2015-06-05 09:04:54 【问题描述】:

以下是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

【问题讨论】:

感谢您这样做。我经常遇到换行符。所以var=$(cat file) 很好,但是echo "$var" 是必需的。 顺便说一句,这也是 BashPitfalls #14:mywiki.wooledge.org/BashPitfalls#echo_.24foo 另见***.com/questions/10067266/… 另见***.com/questions/2414150/… 【参考方案1】:

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

echo "$var"

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


为什么?

当变量未引用时,它将:

    Undergo field splitting,其中值在空格上拆分为多个单词(默认情况下):

    之前:/* Foobar is free software */

    之后:/*Foobarisfreesoftware*/

    这些单词中的每一个都将经过pathname expansion,其中模式被扩展为匹配的文件:

    之前:/*

    之后:/bin/boot/dev/etc/home、...

    最后,所有参数都传递给echo,echo将它们写出separated by single spaces,给出

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

    而不是变量的值。

当变量被引用时,它将:

    用它的值代替。 没有第 2 步。

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

【讨论】:

它并不总是有效。我可以举个例子:paste.ubuntu.com/p/8RjR6CS668 是的,$(..) 去除尾随换行符。您可以使用var=$(cat file; printf x); var="$var%x" 来解决它。【参考方案2】:

您可能想知道为什么会这样。连同the great explanation by that other guy,在Unix & Linux中找到Gilles写的Why does my shell script choke on whitespace or other special characters?的引用:

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

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

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

请注意,结果是一个字符串列表。有两个上下文 shell 语法:列表上下文和字符串上下文。字段拆分和 文件名生成只发生在列表上下文中,但大多数情况下 时间。双引号分隔字符串上下文:整个 双引号字符串是单个字符串,不能被拆分。 (例外: "$@" 展开到位置参数列表,例如"$@" 是 如果有三个位置,则相当于"$1" "$2" "$3" 参数。见What is the difference between $* and $@?)

使用$(foo) `foo` 进行命令替换也是如此。在旁注中,不要使用`foo`:它的引用规则是 怪异且不可移植,并且所有现代 shell 都支持 $(foo) 除了具有直观的引用规则之外,绝对等价。

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

请参阅When is double-quoting necessary? 了解更多详情 可以省略引号的情况。

除非您的意思是让所有这些繁琐的事情发生,否则请记住 始终在变量和命令替换周围使用双引号。做 注意:省略引号不仅会导致错误,还会导致 security holes.

【讨论】:

【参考方案3】:

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

【讨论】:

是的,我们需要一个好的去重!我同意这符合问题标题,但我认为它不会在这里获得应有的知名度。一个新问题怎么样?“为什么我的-e/-n/反斜杠没有出现?”我们可以从这里酌情添加链接。 您的意思是同时使用-n @PesaThe,不,我的意思是-eecho 的标准在其第一个参数为 -n 时没有指定输出,这使得任何/所有可能的输出在这种情况下都是合法的; -e 没有这样的规定。 哦...我看不懂。让我们责怪我的英语。谢谢你的解释。【参考方案4】:

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

echo "$var"

它会正确读取您的值。

【讨论】:

root@ubuntu:/home/qgb# var_a=100 echo $var_a【参考方案5】:

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的值为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 ~]$ 

【讨论】:

您还必须set -f 以防止通配符 @thatotherguy,对于您的第一个路径扩展示例真的有必要吗?将IFS 设置为null,echo $var 将扩展为echo '/* Foobar is free software */',并且不会在单引号字符串中执行路径扩展。 是的。如果您 mkdir "/this thing called Foobar is free software etc/" 您会看到它仍在扩展。对于[a-z] 的例子来说显然更实用。 我明白了,这对于 [a-z] 示例来说是有意义的。 root@ubuntu:/home/qgb# var=32321 echo $var root@ubuntu:/home/qgb# var=3231; echo $var 3231【参考方案6】:

answer from ks1322 帮助我在使用docker-compose exec 时发现问题:

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

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "$testb"
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 "$testb"
1b

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

但这会将 all 空格替换为换行符。引用会保留现有的换行符和空格。 没错,是的。我想这取决于变量中的内容。我实际上使用tr 从文本文件创建数组。 通过不正确引用变量来制造问题,然后使用笨拙的额外过程解决它不是好的编程。 @Alek,...呃,什么?从文本文件正确/正确地创建数组不需要tr——您可以通过设置 IFS 来指定所需的任何分隔符。例如:IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0') 一直工作到 bash 3.2(广泛流通的最旧版本),如果您的 cat 失败,则正确地将退出状态设置为 false。如果您想使用制表符而不是换行符,只需将 $'\n' 替换为 $'\t' @Alek, ...如果您正在执行类似 arrayname=( $( cat file | tr '\n' ' ' ) ) 的操作,那么这会在多个层面上出现问题:它会覆盖您的结果(因此 * 会变成当前目录),并且没有tr(或cat,就此而言,它也能正常工作;一个人可以只使用arrayname=$( $(<file) ),它会以同样的方式被破坏,但效率较低)。

以上是关于我刚刚分配了一个变量,但 echo $variable 显示了其他内容的主要内容,如果未能解决你的问题,请参考以下文章

如何将函数调用中的 echo 语句分配给变量?

如何将前一个表达式的结果分配给变量?

Tomcat环境变量配置命令行报错:The JRE_HOME environment variable is not defined correctl This environment variab

Bash变量分配中找不到命令错误

无法在Bash中使用变量分配另一个变量

tomcat闪退无法启动 the catalina_home environment variable is not defined correctly this environment variab