A naive way would be:
eval "dirin=$dirin"
What that does is evaluate the expansion of dirin=$dirin
as shell code.
With dirin
containing ~/foo
, it's actually evaluating:
dirin=~/foo
It's easy to see the limitations. With a dirin
containing foo bar
, that becomes:
dirin=foo bar
So it's running bar
with dirin=foo
in its environment (and you'd have other problems with all the shell special characters).
Here, you'd need to decide what expansions are allowed (tilde, command substitution, parameter expansion, process substitution, arithmetic expansion, filename expansion...) and either do those substitutions by hand, or use eval
but escape every character but those that would allow them which would be virtually impossible other than by implementing a full shell syntax parser unless you limit it to for instance ~foo
, $VAR
, ${VAR}
.
Here, I'd use zsh
instead of bash
that has a dedicated operator for that:
vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"
vared
is the variable editor, similar to bash
's read -e
.
(e)
is a parameter expansion flag that performs expansions (parameter, command, arithmetic but not tilde) in the content of the parameter.
To address tilde expansion, which only takes place at the beginning of the string, we'd do:
vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
eval "dirin=$match[1]\${(e)match[3]}"
else
dirin=${(e)dirin}
fi
POSIXly (so bash
ly as well), to perform tilde and variable (not parameter) expansion, you could write a function like:
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"
}
Example:
$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane
As an approximation, we could also prepend every character but ~${}-_.
and alnums with backslash before passing to eval
:
eval "dirin=$(
printf '%s\n' "$dirin" |
sed 's/[^[:alnum:]~${}_.-]/\\&/g')"
(here simplified on the ground that $dirin
can't contain newline characters as it comes from read
)
That would trigger syntax errors if one entered ${foo#bar}
for instance but at least that can't do much harm as a simple eval
would.
Edit: a working solution for bash
and other POSIX shells would be to separate the tilde and other expansions like in zsh
and use eval
with a here-document for the other expansions part like:
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//
)"
That would allow tilde, parameter, arithmetic and command expansions like in zsh
above.
}
eval
.