如何扩展读取里面的变量?

如何扩展读取里面的变量?

在带有 Bash 5.0.17(1) 的远程 CentOS 中,我是唯一通过 SSH 执行的用户read web_application_root

$HOME/www

或与:

${HOME}/www

或与:

"${HOME}"/www

或与:

"${HOME}/www"

旨在获得带有扩展(环境)变量的输出,例如MY_USER_HOME_DIRECTORY/www.

虽然ls -la $HOME/www工作正常,但ls -la $web_application_root所有示例均失败;错误示例是:

ls: cannot access '$HOME/www': No such file or directory

据我所知,read将所有上述$HOME变体视为字符串(由于错误中的单引号),因此不会扩展它。

如何扩展读取里面的变量?

答案1

传递给 时变量不会扩展read。如果要扩展$VARs 或${VAR}s whereVAR表示现有环境变量的名称(仅限于名称以 ASCII 字母或下划线开头,后跟 ASCII 数字或下划线的变量)并保留所有其他单词扩展($non_exported_shell_variable$1$#${HOME+x}, $((1 + 1)), $(cmd)...) 不变,您可以使用envsubst(来自 GNU gettext):

IFS= read -r web_application_root || exit
web_application_root=$(printf %s "$web_application_root" | envsubst)
ls -la -- "$web_application_root"

您可以将其设为一个 shell 函数,以变量名作为参数,并执行读取和环境变量扩展:

read_one_line_and_expand_envvars() {
  IFS= read -r "$1"
  ret="$?"
  command eval "$1"'=$(printf %s "${'"$1"'}" | envsubst)' && return "$ret"
}

例如用作:

printf >&2 'Please enter the root dir (${ENVVAR} expanded): '
read_one_line_and_expand_envvars web_application_root || exit
printf >&2 'The expanded version of your input is "%s"\n' "$web_application_root"

要将替换限制为一组有限的环境变量,您可以将该列表作为$VAR1$VAR2...文字参数传递给envsubst

web_application_root=$(
  printf %s "$web_application_root" |
    envsubst '$HOME$MYENVVAR'
)

(此处指示envsubst仅替换其输入中的$HOME, ${HOME},$MYENVVAR${MYENVVAR},保持所有其他$VARs 不变)。

如果您想允许所有形式的单词扩展(但请注意,这会使其成为命令注入漏洞),您可以这样做:

web_application_root=$(eval "cat << __EOF__
$web_application_root
__EOF__")

或者,作为一个以变量名作为参数的函数:

read_one_line_and_perform_shell_word_expansions() {
  IFS= read -r "$1"
  ret=$?
  command eval '
    case "${'"$1"'}" in
      (EOF) ;;
      (*)
        '"$1"'=$(command eval "cat << EOF
${'"$1"'}
EOF")
    esac' && return "$ret"
}
printf >&2 'Please enter the root dir ($var/$((...))/$(cmd) allowed): '
read_one_line_and_perform_shell_word_expansions web_application_root || exit
printf >&2 'The expanded version of your input is "%s"\n' "$web_application_root"

具有详细内联文档的相同功能:

read_one_line_and_perform_shell_word_expansions() {
  # first argument of our function is the variable name or REPLY
  # if not specified.
  varname=${1-REPLY}

  # read one line from stdin with read's unwanted default post-processing
  # (which is otherwise dependant on the current value of $IFS) disabled.
  IFS= read -r "$varname"

  # record read's exit status. If it's non zero, a full line could not be
  # read. We may still want to perform the expansions in whatever much
  # was read, and pass that exit status to the caller so they decide what
  # to do with it.
  ret=$?

  # We prefix the "eval" special builtin with "command" to make it lose
  # its "special" status (namely here, exit the script about failure,
  # something bash only does when in standard mode).
  command eval '
    # the approach we take to expand word expansions would be defeated
    # if the user entered "EOF" which is the delimiter we chose for our
    # here-document, so we need to handle it as a special case:
    case "${'"$varname"'}" in
      (EOF) ;;
      (*)
        # the idea here is to have the shell evaluate the
        # myvar=$(command eval "cat << EOF
        # ${myvar}
        # EOF")
        #
        # shell code when $1 is myvar, so that the
        #
        # cat << EOF
        # contents of $myvar with $(cmd), $ENV and all
        # EOF
        #
        # shell code be evaluated, and those $(cmd), $ENV expansions
        # performed in the process
        '"$varname"'=$(command eval "cat << EOF
${'"$varname"'}
EOF")
    esac' &&
      # unless eval itself failed, return read's exit status to the caller:
      return "$ret"
}

但你的问题听起来更像是一个 XY 问题。通过获取输入read既麻烦又不切实际。最好通过参数获取输入,然后您可以将其留给调用者的 shell 进行扩展,如下所示他们打算这样做。

代替

#! /bin/sh -
IFS= read -r var
ls -l -- "$var"

(并记住打电话readIFS=不打电话-r几乎从来都不是你想要的)。

做了:

#! /bin/sh -
var=${1?}
ls -l -- "$var"

然后调用者可以按照他们认为合适的方式执行your-script ~/diryour-script "$HOME/dir"your-script '$$$weird***/dir'或甚至操作。your-script $'/dir\nwith\nnewline\ncharacters'


^词扩展在这方面指的是参数扩展,算术展开命令替换。这不包括文件名生成(又名通配或者路径名扩展),波形符扩展也不大括号扩展(本身不是标准sh功能)。此处使用此处文档可确保'"s 保持不变,但请注意,仍然存在反斜杠处理。

相关内容