单行 shell 赋值,但仅当变量未设置时(尊重 null 或空值)?

单行 shell 赋值,但仅当变量未设置时(尊重 null 或空值)?

APP_ENV“prod”DATABASE_DISABLE_MIGRATIONS为空时;在任何其他情况下,它应该是true 除非 DATABASE_DISABLE_MIGRATIONS为空或 null。换句话说,其价值必须得到尊重并且只有当它是时才应进行自动分配未设置

原来的DATABASE_DISABLE_MIGRATIONS APP_ENV 新的DATABASE_DISABLE_MIGRATIONS
空或空 任何 保留原有价值
非零长度字符串 任何 保留原有价值
未设置 “产品” 空的
未设置 空或不是“prod” “真的”

我正在开始学习 shell (POSIX),并且我已经使用以下代码成功完成了此任务:

#!/bin/sh

if [ "${DATABASE_DISABLE_MIGRATIONS+set}" != set ]; then
    : ${DATABASE_DISABLE_MIGRATIONS:=$( [ "$APP_ENV" = "prod" ] || echo "true" )}
fi

# Just for debug (not meant to be in the single-line assignment of course)
if [ -z "$DATABASE_DISABLE_MIGRATIONS" ]; then
    echo "Empty value, migrations ENABLED"
else
    echo "Not empty value, migrations DISABLED"
fi

可以将上面的代码写成单行语句吗?

当考虑未设置、空或空字符串时,很容易制作单行语句,我经常最终使用:

: ${FOO:=$( [ "$APP_ENV" = "prod" ] || echo "true" )}

变量在未设置、null 或空且不是“prod”时FOO获取值。trueFOOAPP_ENV

也许我缺少类似的“捷径”?

在此输入图像描述

答案1

使用 2 个变量的 case 语句可能就是您所追求的:

case "${DATABASE_DISABLE_MIGRATIONS}:${APP}" in
    ?*:* | "":prod) # not empty, or prod
                    echo "Not empty value, migrations DISABLED"
                    ;;
    *) echo "Empty value, migrations ENABLED"
       ;;
esac

虽然这并没有区分“空”和“未设置”。如果变量值之一包含冒号,也可能会被欺骗。


基于

原来的DATABASE_DISABLE_MIGRATIONS APP_ENV 新的DATABASE_DISABLE_MIGRATIONS
空或空 任何 保留原有价值
非零长度字符串 任何 保留原有价值
未设置 “产品” 空的
未设置 空或不是“prod” “真的”

不将自己限制在 POSIX 上,并且因为“未设置”是调整变量,我会:

if [[ ! -v DATABASE_DISABLE_MIGRATIONS ]]; then
    if [[ $APP == prod ]]; then 
        DATABASE_DISABLE_MIGRATIONS=""
    else
        DATABASE_DISABLE_MIGRATIONS=true
    fi
fi

这显然不能满足您的单行愿望,但我认为这是传达您的意图的最易读的方式。

答案2

命令:

[ -z "${DATABASE_DISABLE_MIGRATIONS+x}" ] && [ "$APP_ENV" != "prod" ] && DATABASE_DISABLE_MIGRATIONS=true

解释:

A评论指出将使用此信息检查变量。
[ -z "$DATABASE_DISABLE_MIGRATIONS" ]
此信息应该是问题的一部分。

"$DATABASE_DISABLE_MIGRATIONS"除非使用set -u(或),否则对于未设置的变量或空字符串,扩展的结果是相同的set -o nounset,因此可以简化要求。不要用显式的空值替换未设置的变量,以防万一"prod"您可以将其保留为未设置,并且您将获得相同的测试结果。

所以算法可以简化为

  • 如果( DATABASE_DISABLE_MIGRATIONS 未设置且 APP_ENV 不是“prod”)将 DATABASE_DISABLE_MIGRATIONS 设置为“true”
  • (否则什么也不做)

当然,您可以将其写成多行,这将使代码更清晰,但这不是一行,也不会比您在问题中的解决方案短多少。

if [ -z "${DATABASE_DISABLE_MIGRATIONS+x}" ] && [ "$APP_ENV" != "prod" ]
then
    DATABASE_DISABLE_MIGRATIONS=true
fi

答案3

那么,如果变量未设置,那么您想将其设置为默认值,否则保留原始值? (这里的默认值取决于另一个变量,但我们不要让它分散注意力。)

也许最好这样做,然后:

# don't disable migrations in production by default
if [ "$APP_ENV" = prod ]; then
    default_DATABASE_DISABLE_MIGRATIONS=
else
    default_DATABASE_DISABLE_MIGRATIONS=true
fi
# set default value if unset
if [ "${DATABASE_DISABLE_MIGRATIONS+set}" != set ]; then
    DATABASE_DISABLE_MIGRATIONS=$default_DATABASE_DISABLE_MIGRATIONS
fi

或者更短一点:

# don't disable migrations in production by default
default_DATABASE_DISABLE_MIGRATIONS=true
if [ "$APP_ENV" = prod ]; then
    default_DATABASE_DISABLE_MIGRATIONS=
fi
# set default value if unset
: "${DATABASE_DISABLE_MIGRATIONS=$default_DATABASE_DISABLE_MIGRATIONS}"

基于相关值的代码高尔夫似乎会适得其反,因为如果您需要对其他值使用类似的逻辑,则需要修改整个代码。最好制定一个可以应用于其他类似案例的直接解决方案。尽管最后一行几乎是您在问题标题中所要求的,但仅当变量未设置时才进行单行 shell 分配。 ("${var:=default}"它也适用于一组但为空的值。)

我还要指出,如果最终变量没有被否定,读者可能会更清楚。您提到使用 和 测试变量[ -z "$DATABASE_DISABLE_MIGRATIONS" ],基本上读起来像“如果不禁用迁移”。这与“如果启用迁移”相同,如果使用变量,则更容易编写$DATABASE_ENABLE_MIGRATIONS

答案4

可能:

case ${DATABASE_DISABLE_MIGRATIONS++}:$APP_ENV in
  (:prod) DATABASE_DISABLE_MIGRATIONS=    ;;
  (:*   ) DATABASE_DISABLE_MIGRATIONS=true;;
esac

就像任何代码一样,您可以将其放在一行上,只需将各行连接在一起(甚至压缩得有点像

case ${DATABASE_DISABLE_MIGRATIONS++}:$APP_ENV in(:prod)DATABASE_DISABLE_MIGRATIONS=;;(:*)DATABASE_DISABLE_MIGRATIONS=true;esac

)但我没有看到好处。

执行$(... echo ...)意味着(ksh93 中除外)分叉一个额外的进程,运行echo命令并通过管道发送和接收该命令,这意味着数十万甚至更多的 CPU 指令,而那些简单的 shell 分配和模式匹配只会有数百个,所以它我觉得有点傻。

相关内容