
No bash, existe alguma maneira de ler a entrada do usuário, mas ainda permitir bash
a expansão de variáveis?
Estou tentando solicitar que o usuário insira um caminho no meio de um programa, mas como ~
outras variáveis não são expandidas como parte do read
built-in, os usuários precisam inserir um caminho absoluto.
Exemplo: quando um usuário insere um caminho em:
read -ep "input> " dirin
[[ -d "$dirin" ]]
retorna verdadeiro quando um usuário entra, /home/user/bin
mas não ~/bin
ou $HOME/bin
.
Responder1
Uma maneira ingênua seria:
eval "dirin=$dirin"
O que isso faz é avaliar a expansão do dirin=$dirin
código shell.
Com dirin
contains ~/foo
, na verdade está avaliando:
dirin=~/foo
É fácil ver as limitações. Com um dirin
contendo foo bar
, isso se torna:
dirin=foo bar
Portanto, ele está sendo executado bar
em dirin=foo
seu ambiente (e você teria outros problemas com todos os caracteres especiais do shell).
Aqui, você precisaria decidir quais expansões são permitidas (til, substituição de comando, expansão de parâmetro, substituição de processo, expansão aritmética, expansão de nome de arquivo...) e fazer essas substituições manualmente ou usar eval
masescapartodos os caracteres, exceto aqueles que os permitiriam, o que seria virtualmente impossível, a não ser pela implementação de um analisador de sintaxe de shell completo, a menos que você o limite, por exemplo ~foo
, $VAR
, , ${VAR}
.
Aqui, eu usaria zsh
em vez bash
disso um operador dedicado para isso:
vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"
vared
é oeditor de variáveis, semelhante ao bash
de read -e
.
(e)
é um sinalizador de expansão de parâmetro que realiza expansões (parâmetro, comando, aritmética, mas não til) no conteúdo do parâmetro.
Para resolver a expansão do til, que ocorre apenas no início da string, faríamos:
vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
eval "dirin=$match[1]\${(e)match[3]}"
else
dirin=${(e)dirin}
fi
POSIXly (também bash
), para realizar expansão de til e variável (não parâmetro), você poderia escrever uma função como:
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"
}
Exemplo:
$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane
Como aproximação, também poderíamos acrescentar todos os caracteres but ~${}-_.
e alnums com uma barra invertida antes de passar para eval
:
eval "dirin=$(
printf '%s\n' "$dirin" |
sed 's/[^[:alnum:]~${}_.-]/\\&/g')"
(aqui simplificado porque $dirin
não pode conter caracteres de nova linha, pois vem de read
)
Isso provocaria erros de sintaxe se alguém digitasse ${foo#bar}
, por exemplo, mas pelo menos isso não pode causar tantos danos quanto um simples eval
faria.
Editar: uma solução funcional para bash
e outros shells POSIX seria separar o til e outras expansões como em zsh
e usar eval
com um documento aqui para ooutras expansõesparte como:
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//
)"
Isso permitiria expansões de til, parâmetros, aritmética e comando como zsh
acima. }