使用未设置的变量有什么危害吗?

使用未设置的变量有什么危害吗?

假设我有以下代码:

# Check if the color prompt is enabled and supported on this system
if [ -n "$force_color_prompt" ] && [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
    GREEN="\033[1;32m"
    DIM="\033[2m"
    RESET="\033[00m"
fi

echo -e "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

如果启用颜色支持,它将回显出漂亮的彩色线条。如果未启用颜色支持,${GREEN}则不会设置类似的值,并且文本将以通常的白色和黑色背景打印出来。

该代码依赖于这样一个事实:未设置的变量将简单地计算为空字符串(在我的测试中,它们确实如此)。这是否会导致某些系统出现错误或问题,或者是否会导致所有不存在的变量总是评估为空字符串?我有什么理由不应该依赖这个机制吗?

答案1

$FOO当扩展为or (等效)时,不存在的变量将始终计算为空字符串${FOO},并且依赖于它没有什么坏处,除了在一种特殊情况下:

set -u如果有人在您尝试使用该变量之前在当前 shell 中调用过,则他们已启用此设置:

              -u 执行参数时将未设置的变量视为错误
                      进一步扩展。如果尝试在未设置的情况下进行扩展
                      变量,shell 打印一条错误消息,如果不是
                      交互,以非零状态退出。

这意味着,如果您正在编写一个函数,该函数被设计为源到其他人控制的脚本中,那么您可能需要对使用未设置的变量持偏执态度 - 否则,如果他们set -u在调用您的函数之前使用了他们的脚本第一次尝试扩展未设置的变量时会退出并显示错误消息。

如果您正在编写自己的脚本,那么依靠未设置的变量扩展到空字符串并没有什么坏处。

编辑- 另外,只是一个想法 - 既然您使整个事情以 terminfo 颜色功能是否可用于您的终端为条件,为什么不实际使用 terminfo 来生成序列,而不是硬编码 vt100 值?就像是:

if [ -n "$force_color_prompt" ] && type tput &>/dev/null; then
    GREEN="$(tput setaf 2)$(tput bold)"
    DIM="$(tput dim)"
    RESET="$(tput sgr0)"
fi

这可能会为您带来一些跨其他终端的可移植性(尽管,不可否认,不使用您显示的代码的终端数量很少并且正在减少)。它还可能会失去一些可移植性,因为某些功能可能在某些平台上不存在,具体取决于 terminfo 定义的正确程度。 YMMV。

答案2

POSIX 兼容的 shell 脚本语言最独特的功能之一是参数扩展。它可以通过多种方式使用来完成通常不与变量值关联的任务。在 shell 中,变量不仅仅只是一个值,它还可以是一个可操作的项目。它有潜力进行自我测试。这是明确的 - 不需要设置 shell 选项。

例如,您的代码可能如下所示:

N= ERR='error encountered - exiting' 
: ${force_color_prompt?"$ERR"}
/usr/bin/tput setaf >/dev/null 2>&1 || ${N:?"$ERR"}
: "${GREEN:=\033[1;32m}" "${DIM:=\033[2m}" "${RESET:=\033[00m}"
printf %b\\n \
    "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

$N变量被显式设置为空字符串,因此当以${N:?}参数扩展的形式对其进行求值时,其父 shell 会自动退出,并且后面的语句?也会进行扩展求值,其结果输出在 上stderr。这同样适用$force_color_prompt- 如果未设置,则脚本会因错误退出并自动输出$ERRstderr- 。

如果和 当前未设置或设置为空字符串,$GREEN $RESET则将设置为您定义的值。这使您能够将它们的值作为环境变量传递到脚本中。例如,如果上面的代码片段位于名为的脚本中,我这样称呼它:$DIM''greenworld.sh

GREEN="$(tput setaf 2)$(tput bold)" greenworld.sh

然后$GREEN不会在脚本的内容中重置,而是继承我为其设置的显式值。这使得 shell 脚本变得灵活。

正如 godlygeek 所建议的那样,以这种方式使用tput是我的建议之一。

在 shell 中,未设置的变量有时与设置的变量一样有用。这是一个不同的例子:

set -- * 
while ${1+:} false ; do
    #do stuff until all positionals are shifted away
shift ; done

在该示例中,只要定义了第一个参数,它就会扩展到 shell 的内置:null,从而使以下false调用成为无操作。但是,一旦所有位置参数都被shift删除,${1}就不会以这种方式扩展,并且false会被调用,并且while循环结束。你可以对此做无数的变化。

答案3

在普通使用中,即当您只是扩展变量时,未设置的变量在所有 Bourne/POSIX 风格的 shell 中被视为空。例外情况是当set -o unsetakaset -u生效时,在这种情况下,如果您尝试访问未设置变量的值,shell 将引发错误(请参阅神极客的回答)。

有多种方法可以测试变量是否未设置或为空;例如,如果已设置(即使它为空),则构造${foo-bar}会扩展为值;如果未设置,则构造将扩展为值;(带有额外的冒号)将空变量视为未设置。其他类似的扩展结构、、的行为类似。除其他差异外,和的输出中还会出现一个空变量(如果导出的话)。如果您愿意,您只会遇到这些差异。foobarfoo
${foo:-bar}${foo+bar}${foo?bar}${foo=bar}setexport

然而,还有一个不同的理由总是初始化变量:你怎么知道变量确实没有设置?调用者可能为了自己的目的定义了类似的变量。如果调用者是同一脚本的其他部分,那么您在函数中的使用将覆盖调用者的,除非您将变量声明为本地变量(这仅在 ksh/bash/zsh 中可能),因此变量名称冲突是一个问题反正。但也有可能该变量存在于环境中,因为其他人选择了与您相同的变量名称。有一种约定,对 shell 变量使用小写名称,对环境变量使用大写名称,但它并不能解决所有冲突,也没有得到普遍遵循。

$ 导出 DIM=1 SUM=42
$ 重击
哦,绿色的世界,1现在不要抛弃我......

相关内容