在带有 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
。如果要扩展$VAR
s 或${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}
,保持所有其他$VAR
s 不变)。
如果您想允许所有形式的单词扩展(但请注意,这会使其成为命令注入漏洞),您可以这样做:
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"
(并记住打电话read
和IFS=
不打电话-r
几乎从来都不是你想要的)。
做了:
#! /bin/sh -
var=${1?}
ls -l -- "$var"
然后调用者可以按照他们认为合适的方式执行your-script ~/dir
或your-script "$HOME/dir"
或your-script '$$$weird***/dir'
或甚至操作。your-script $'/dir\nwith\nnewline\ncharacters'
^词扩展在这方面指的是参数扩展,算术展开和命令替换。这不包括文件名生成(又名通配或者路径名扩展),波形符扩展也不大括号扩展(本身不是标准sh
功能)。此处使用此处文档可确保'
和"
s 保持不变,但请注意,仍然存在反斜杠处理。