env
我正在尝试获取shell 变量中的输出并打印它。
#!/bin/sh
ENV_BEFORE=$(env)
printf $ENV_BEFORE
结果,env
输出中的单个变量被打印。
当使用echo
而不是printf
打印所有输出时,但没有换行符。
我在这里缺少什么?
答案1
问题是您没有引用该$ENV
变量。正如中所解释的man bash
:
将字符括在双引号中会保留引号内所有字符的字面值,但 $、`、\ 以及启用历史扩展时的 ! 除外。字符 $ 和 ` 在双引号内保留其特殊含义。 仅当反斜杠后跟以下字符之一时,反斜杠才保留其特殊含义:$、`、"、\ 或。
因此,将序列括\n
在双引号中可以保留其含义。这就是为什么,当没有引用时,\n
它只是一个正常的n
:
$ printf \n
n$
而当引用时:
$ printf "\n"
$
bash 中未加引号的变量会调用 split+glob 运算符。这意味着该变量在空格(或特殊变量$IFS
设置的任何内容)上分割,并且每个结果单词都用作 glob(它将扩展以匹配任何匹配的文件名)。您的问题在于其中的“拆分”部分。
为了说明这一点,我们采用一个更简单的多行变量:
$ var=$(printf "foo\nbar\n")
现在,使用 shell 的set -x
调试功能,您可以准确地看到发生了什么:
$ echo $var
+ echo foo bar
foo bar
$ echo "$var"
+ echo 'foo
bar'
foo
bar
正如您在上面看到的,echo $var
(未加引号)服从$var
split+glob,因此它会产生两个单独的字符串,foo
和bar
。换行符被 split+glob 吃掉了。当变量被引用时,它不会受到 split+glob 的影响,换行符被保留,并且因为它被引用,所以也被正确解释并打印出来。
下一个问题是printf
不喜欢echo
。它不只是打印您提供的任何内容,它还需要一个格式字符串。例如printf "color:%s" "green"
将打印,color:green
因为%s
将被替换为green
。
它还忽略任何不适合给定格式字符串的输入。因此,如果您运行printf foo bar
,printf
将视为foo
其格式字符串以及bar
应使用其格式化的变量。由于没有%s
或等价物可以被替换bar
,bar
因此被忽略并foo
单独打印:
$ printf $var
+ printf foo bar
foo
这就是你跑步时发生的事情printf $ENV_BEFORE
。因为变量没有被引用,所以分割的 glob 有效地用空格替换了换行符,并且printf
只打印了它看到的第一个“单词”。
要正确执行此操作,请使用格式字符串,并始终引用变量:
printf '%s\n' "$ENV_BEFORE"
答案2
printf
( 与echo
) 默认情况下不打印换行符,您必须明确告诉它:
printf "${ENV_BEFORE}\n"
或更好:
printf '%s\n' "$ENV_BEFORE"
后@don_crissti在评论中正确指出,在我再次阅读问题后,我认为您的情况与上面不同。
就您而言,问题是您没有引用变量$ENV_BEFORE
。
如果不引用变量,以下是printf
和的行为:echo
printf
:没有引号和任何格式说明符,它只会打印第一个单词
使用引号并且没有任何格式说明符,它会考虑扩展中具有
%
格式说明符的任何内容,并且可能会阻塞如果没有引号并带有格式说明符,它将受到分词和路径名扩展的影响,因此会导致意外结果,因为任何值
IFS
都会成为输出中的空格使用引号和格式说明符,您将获得所需的结果,即在扩展上不会执行分词和路径名扩展。
echo
:如果没有引号,变量扩展将受到分词和路径名扩展的影响,因此您将无法获得所需的输出
使用
quote
变量扩展将不会受到分词和路径名扩展的影响,因此您将获得所需的输出