bash 我刚刚分配了一个变量,但 echo $variable 显示了其他内容
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29378566/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
I just assigned a variable, but echo $variable shows something else
提问by that other guy
Here are a series of cases where echo $var
can show a different value than what was just assigned. This happens regardless of whether the assigned value was "double quoted", 'single quoted' or unquoted.
下面是一系列echo $var
可以显示与刚刚分配的值不同的值的情况。无论分配的值是“双引号”、“单引号”还是不带引号,都会发生这种情况。
How do I get the shell to set my variable correctly?
如何让 shell 正确设置我的变量?
Asterisks
星号
The expected output is /* Foobar is free software */
, but instead I get a list of filenames:
预期的输出是/* Foobar is free software */
,但我得到了一个文件名列表:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Square brackets
方括号
The expected value is [a-z]
, but sometimes I get a single letter instead!
预期值为[a-z]
,但有时我会得到一个字母!
$ var=[a-z]
$ echo $var
c
Line feeds (newlines)
换行符(换行符)
The expected value is a a list of separate lines, but instead all the values are on one line!
预期值是一个单独行的列表,但所有值都在一行上!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Multiple spaces
多个空格
I expected a carefully aligned table header, but instead multiple spaces either disappear or are collapsed into one!
我希望有一个仔细对齐的表头,但多个空格要么消失要么折叠成一个!
$ var=" title | count"
$ echo $var
title | count
Tabs
标签
I expected two tab separated values, but instead I get two space separated values!
我希望有两个制表符分隔值,但我得到了两个空格分隔值!
$ var=$'key\tvalue'
$ echo $var
key value
回答by that other guy
In all of the cases above, the variable is correctly set, but not correctly read! The right way is to use double quotes when referencing:
在上述所有情况下,变量设置正确,但读取不正确!正确的方法是在引用时使用双引号:
echo "$var"
This gives the expected value in all the examples given. Always quote variable references!
这给出了所有给定示例中的预期值。始终引用变量引用!
Why?
为什么?
When a variable is unquoted, it will:
当一个变量没有被引用时,它将:
Undergo field splittingwhere the value is split into multiple words on whitespace (by default):
Before:
/* Foobar is free software */
After:
/*
,Foobar
,is
,free
,software
,*/
Each of these words will undergo pathname expansion, where patterns are expanded into matching files:
Before:
/*
After:
/bin
,/boot
,/dev
,/etc
,/home
, ...Finally, all the arguments are passed to echo, which writes them out separated by single spaces, giving
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
instead of the variable's value.
进行字段拆分,其中将值拆分为空格上的多个单词(默认情况下):
前:
/* Foobar is free software */
之后:
/*
,Foobar
,is
,free
,software
,*/
这些单词中的每一个都将进行路径名扩展,其中模式被扩展为匹配的文件:
前:
/*
之后:
/bin
,/boot
,/dev
,/etc
,/home
, ...最后,所有的参数都传递给 echo,它把它们写出来,用单个空格分隔,给出
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
而不是变量的值。
When the variable is quotedit will:
当变量被引用时,它将:
- Be substituted for its value.
- There is no step 2.
- 取而代之的是它的价值。
- 没有第 2 步。
This is why you should always quote all variable references, unless you specifically require word splitting and pathname expansion. Tools like shellcheckare there to help, and will warn about missing quotes in all the cases above.
这就是为什么您应该始终引用所有变量引用的原因,除非您特别需要分词和路径名扩展。像shellcheck这样的工具可以提供帮助,并且会在上述所有情况下警告缺少引号。
回答by fedorqui 'SO stop harming'
You may want to know why this is happening. Together with the great explanation by that other guy, find a reference of Why does my shell script choke on whitespace or other special characters?written by Gillesin Unix & Linux:
您可能想知道为什么会发生这种情况。与其他人的精彩解释一起,找到为什么我的 shell 脚本在空格或其他特殊字符上窒息?吉尔斯在Unix 和 Linux 中写的:
Why do I need to write
"$foo"
? What happens without the quotes?
$foo
does not mean “take the value of the variablefoo
”. It means something much more complex:
- First, take the value of the variable.
- Field splitting: treat that value as a whitespace-separated list of fields, and build the resulting list. For example, if the variable contains
foo * bar ?
then the result of this step is the 3-element listfoo
,*
,bar
.- Filename generation: treat each field as a glob, i.e. as a wildcard pattern, and replace it by the list of file names that match this pattern. If the pattern doesn't match any files, it is left unmodified. In our example, this results in the list containing
foo
, following by the list of files in the current directory, and finallybar
. If the current directory is empty, the result isfoo
,*
,bar
.Note that the result is a list of strings. There are two contexts in shell syntax: list context and string context. Field splitting and filename generation only happen in list context, but that's most of the time. Double quotes delimit a string context: the whole double-quoted string is a single string, not to be split. (Exception:
"$@"
to expand to the list of positional parameters, e.g."$@"
is equivalent to"$1" "$2" "$3"
if there are three positional parameters. See What is the difference between $* and $@?)The same happens to command substitution with
$(foo)
or with`foo`
. On a side note, don't use`foo`
: its quoting rules are weird and non-portable, and all modern shells support$(foo)
which is absolutely equivalent except for having intuitive quoting rules.The output of arithmetic substitution also undergoes the same expansions, but that isn't normally a concern as it only contains non-expandable characters (assuming
IFS
doesn't contain digits or-
).See When is double-quoting necessary?for more details about the cases when you can leave out the quotes.
Unless you mean for all this rigmarole to happen, just remember to always use double quotes around variable and command substitutions. Do take care: leaving out the quotes can lead not just to errors but to security holes.
为什么我需要写
"$foo"
?没有引号会发生什么?
$foo
并不意味着“取变量的值foo
”。这意味着更复杂的事情:
- 首先,获取变量的值。
- 字段拆分:将该值视为以空格分隔的字段列表,并构建结果列表。例如,如果变量包含,
foo * bar ?
则此步骤的结果是 3 元素列表foo
,*
,bar
。- 文件名生成:将每个字段视为一个 glob,即作为通配符模式,并将其替换为与此模式匹配的文件名列表。如果模式与任何文件都不匹配,则不会对其进行修改。在我们的示例中,这会导致列表包含
foo
,然后是当前目录中的文件列表,最后是bar
. 如果当前目录为空,则结果为foo
,*
,bar
。请注意,结果是一个字符串列表。shell 语法中有两种上下文:列表上下文和字符串上下文。字段拆分和文件名生成仅发生在列表上下文中,但大多数情况下都是如此。双引号分隔字符串上下文:整个双引号字符串是单个字符串,不可拆分。(例外:
"$@"
扩展到位置参数的列表,例如"$@"
相当于"$1" "$2" "$3"
如果有三个位置参数。请参阅$* 和 $@ 之间的区别是什么?)使用
$(foo)
或进行命令替换也会发生同样的情况`foo`
。附带说明一下,不要使用`foo`
:它的引用规则很奇怪且不可移植,并且所有现代 shell 都支持$(foo)
绝对等效,除了具有直观的引用规则。算术替换的输出也经历了相同的扩展,但这通常不是问题,因为它只包含不可扩展的字符(假设
IFS
不包含数字或-
)。请参阅何时需要双引号?有关可以省略引号的情况的更多详细信息。
除非你的意思是所有这些繁琐的事情发生,请记住始终在变量和命令替换周围使用双引号。请注意:省略引号不仅会导致错误,还会导致 安全漏洞。
回答by vanishedzhou
user double quote to get the exact value. like this:
用户双引号以获得确切的值。像这样:
echo "${var}"
and it will read your value correctly.
它会正确读取您的值。
回答by Charles Duffy
In addition to other issues caused by failing to quote, -n
and -e
can be consumed by echo
as arguments. (Only the former is legal per the POSIX spec for echo
, but several common implementations violate the spec and consume -e
as well).
除了其他问题造成无法引用,-n
并且-e
可以通过消耗echo
作为参数。(根据 POSIX 规范echo
,只有前者是合法的,但一些常见的实现违反了规范并且-e
也会消费)。
To avoid this, use printf
instead of echo
when details matter.
为避免这种情况,请在细节重要时使用printf
而不是echo
。
Thus:
因此:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
However, correct quoting won't always save you when using echo
:
但是,在使用时,正确的引用并不总是能拯救您echo
:
$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed
...whereas it willsave you with printf
:
......而这会节省你printf
:
$ vars="-n"
$ printf '%s\n' "$vars"
-n
回答by A.L
The answer from ks1322helped me to identify the issue while using docker-compose exec
:
来自 ks1322的答案帮助我在使用时确定了问题docker-compose exec
:
If you omit the -T
flag, docker-compose exec
add a special character that break output, we see b
instead of 1b
:
如果省略该-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$
With -T
flag, docker-compose exec
works as expected:
使用-T
标志,docker-compose exec
按预期工作:
$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b
回答by ks1322
echo $var
output highly depends on the value of IFS
variable. By default it contains space, tab, and newline characters:
echo $var
输出高度依赖于IFS
变量的值。默认情况下,它包含空格、制表符和换行符:
[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$
This means that when shell is doing field splitting (or word splitting) it uses all these characters as word separators. This is what happens when referencing a variable without double quotes to echo it ($var
) and thus expected output is altered.
这意味着当 shell 进行字段拆分(或单词拆分)时,它使用所有这些字符作为单词分隔符。当引用一个没有双引号的变量来回显它 ( $var
)时会发生这种情况,因此预期的输出会改变。
One way to prevent word splitting (besides using double quotes) is to set IFS
to null. See http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05:
防止分词(除了使用双引号)的一种方法是设置IFS
为 null。见http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05:
If the value of IFS is null, no field splitting shall be performed.
如果IFS 的值为空,则不进行字段拆分。
Setting to null means setting to empty value:
设置为 null 表示设置为空值:
IFS=
Test:
测试:
[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 ~]$
回答by Alek
Additional to putting the variable in quotation, one could also translate the output of the variable using tr
and converting spaces to newlines.
除了将变量放在引号中外,还可以使用tr
空格将变量的输出转换为换行符。
$ echo $var | tr " " "\n"
foo
bar
baz
Although this is a little more convoluted, it does add more diversity with the output as you can substitute any character as the separator between array variables.
虽然这有点复杂,但它确实增加了输出的多样性,因为您可以替换任何字符作为数组变量之间的分隔符。