在bash中,有没有办法读取用户输入但仍然允许bash
变量扩展?
我试图请求用户在程序中间输入路径,但由于~
其他变量没有作为内置的一部分扩展read
,因此用户必须输入绝对路径。
示例:当用户输入路径时:
read -ep "input> " dirin
[[ -d "$dirin" ]]
当用户输入/home/user/bin
但未输入~/bin
或 时返回 true $HOME/bin
。
答案1
一个天真的方法是:
eval "dirin=$dirin"
dirin=$dirin
它的作用是评估as shell 代码的扩展。
对于dirin
contains ~/foo
,它实际上是在评估:
dirin=~/foo
很容易看出其局限性。如果dirin
包含foo bar
,则变为:
dirin=foo bar
bar
所以它在它的环境中运行dirin=foo
(并且所有 shell 特殊字符都会遇到其他问题)。
在这里,您需要决定允许哪些扩展(波形符、命令替换、参数扩展、进程替换、算术扩展、文件名扩展...),并且要么手动执行这些替换,要么使用eval
but逃脱除了那些允许它们的字符之外的每个字符,除了实现完整的 shell 语法解析器之外,这实际上是不可能的,除非您将其限制为例如~foo
, $VAR
, ${VAR}
。
在这里,我会使用专门的运算符来zsh
代替:bash
vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"
vared
是个变量编辑器,类似于bash
的read -e
。
(e)
是一个参数扩展标志,用于在参数内容中执行扩展(参数、命令、算术,但不包括波形符)。
为了解决仅发生在字符串开头的波形符扩展,我们会这样做:
vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
eval "dirin=$match[1]\${(e)match[3]}"
else
dirin=${(e)dirin}
fi
POSIXly(bash
也如此),要执行波形符和变量(而不是参数)扩展,您可以编写如下函数:
expand_var() {
eval "_ev_var=\${$1}"
_ev_outvar=
_ev_v=${_ev_var%%/*}
case $_ev_v in
(?*[![:alnum:]._-]*) ;;
("~"*)
eval "_ev_outvar=$_ev_v"; _ev_var=${_ev_var#"$_ev_v"}
esac
while :; do
case $_ev_var in
(*'$'*)
_ev_outvar=$_ev_outvar${_ev_var%%"$"*}
_ev_var=${_ev_var#*"$"}
case $_ev_var in
('{'*'}'*)
_ev_v=${_ev_var%%\}*}
_ev_v=${_ev_v#"{"}
case $_ev_v in
"" | [![:alpha:]_]* | *[![:alnum:]_]*) _ev_outvar=$_ev_outvar\$ ;;
(*) eval "_ev_outvar=\$_ev_outvar\${$_ev_v}"; _ev_var=${_ev_var#*\}};;
esac;;
([[:alpha:]_]*)
_ev_v=${_ev_var%%[![:alnum:]_]*}
eval "_ev_outvar=\$_ev_outvar\$$_ev_v"
_ev_var=${_ev_var#"$_ev_v"};;
(*)
_ev_outvar=$_ev_outvar\$
esac;;
(*)
_ev_outvar=$_ev_outvar$_ev_var
break
esac
done
eval "$1=\$_ev_outvar"
}
例子:
$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane
作为近似,我们还可以~${}-_.
在传递给之前在每个字符 but 和 alnums 前面加上反斜杠eval
:
eval "dirin=$(
printf '%s\n' "$dirin" |
sed 's/[^[:alnum:]~${}_.-]/\\&/g')"
(这里进行了简化,$dirin
不能包含换行符,因为它来自read
)
${foo#bar}
例如,如果有人输入,这会触发语法错误,但至少不会像简单的那样造成太大伤害eval
。
编辑: 和其他 POSIX shell 的一个可行解决方案bash
是将波形符和其他扩展分开,例如 in并与此处文档一起zsh
使用eval
其他扩展部分如:
expand_var() {
eval "_ev_var=\${$1}"
_ev_outvar=
_ev_v=${_ev_var%%/*}
case $_ev_v in
(?*[![:alnum:]._-]*) ;;
("~"*)
eval "_ev_outvar=$_ev_v"; _ev_var=${_ev_var#"$_ev_v"}
esac
eval "$1=\$_ev_outvar\$(cat << //unlikely//
$_ev_var
//unlikely//
)"
这将允许波浪号、参数、算术和命令扩展,如上面所示zsh
。 }