我刚刚分配了一个变量,但 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 */
之后:/*
、Foobar
、is
、free
、software
、*/
这些单词中的每一个都将经过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,不,我的意思是-e
。 echo
的标准在其第一个参数为 -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 显示了其他内容的主要内容,如果未能解决你的问题,请参考以下文章
Tomcat环境变量配置命令行报错:The JRE_HOME environment variable is not defined correctl This environment variab
tomcat闪退无法启动 the catalina_home environment variable is not defined correctly this environment variab