
Gibt es in Bash eine Möglichkeit, Benutzereingaben einzulesen und trotzdem bash
die Variablenerweiterung zuzulassen?
Ich versuche, den Benutzer mitten in einem Programm aufzufordern, einen Pfad einzugeben, aber da ~
und andere Variablen nicht als Teil der read
integrierten Funktion erweitert werden, müssen Benutzer einen absoluten Pfad eingeben.
Beispiel: Wenn ein Benutzer einen Pfad eingibt in:
read -ep "input> " dirin
[[ -d "$dirin" ]]
Gibt „true“ zurück, wenn ein Benutzer /home/user/bin
aber nicht ~/bin
oder eingibt $HOME/bin
.
Antwort1
Ein naiver Weg wäre:
eval "dirin=$dirin"
Dadurch wird die Erweiterung dirin=$dirin
als Shellcode ausgewertet.
Mit dirin
„containing“ ~/foo
wird tatsächlich Folgendes ausgewertet:
dirin=~/foo
Die Einschränkungen sind leicht zu erkennen. Mit einem dirin
enthaltenen foo bar
wird daraus:
dirin=foo bar
Es läuft also bar
in dirin=foo
seiner Umgebung (und Sie hätten andere Probleme mit all den Sonderzeichen der Shell).
Hier müssen Sie entscheiden, welche Erweiterungen erlaubt sind (Tilde, Befehlsersetzung, Parametererweiterung, Prozessersetzung, arithmetische Erweiterung, Dateinamenerweiterung...) und diese Ersetzungen entweder manuell vornehmen oder verwenden, eval
aberFluchtalle Zeichen außer denen, die sie zulassen, was praktisch unmöglich wäre, außer durch die Implementierung eines vollständigen Shell-Syntaxparsers, es sei denn, Sie beschränken es beispielsweise auf ~foo
, $VAR
, ${VAR}
.
Hier würde ich zsh
statt bash
„hat“ einen dedizierten Operator dafür verwenden:
vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"
vared
ist derVariableneditor, ähnlich bash
wie read -e
.
(e)
ist ein Parametererweiterungsflag, das Erweiterungen (Parameter, Befehl, Arithmetik, aber keine Tilde) im Inhalt des Parameters durchführt.
Um die Tilde-Erweiterung zu berücksichtigen, die nur am Anfang der Zeichenfolge stattfindet, würden wir Folgendes tun:
vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
eval "dirin=$match[1]\${(e)match[3]}"
else
dirin=${(e)dirin}
fi
Um POSIX-mäßig (also bash
auch mäßig) eine Tilde- und Variablen- (nicht Parameter-)Erweiterung durchzuführen, könnten Sie eine Funktion wie die folgende schreiben:
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"
}
Beispiel:
$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane
Als Annäherung könnten wir auch jedem Zeichen außer ~${}-_.
und jedem Zahlenwert einen Backslash voranstellen, bevor wir an übergeben eval
:
eval "dirin=$(
printf '%s\n' "$dirin" |
sed 's/[^[:alnum:]~${}_.-]/\\&/g')"
(hier vereinfacht, da es $dirin
keine Zeilenumbruchzeichen enthalten darf, wie es von kommt read
)
Das würde Syntaxfehler auslösen, wenn man ${foo#bar}
beispielsweise Folgendes eingibt, aber das kann zumindest nicht so viel Schaden anrichten wie ein einfaches eval
.
Bearbeiten: eine funktionierende Lösung für bash
und andere POSIX-Shells wäre, die Tilde und andere Erweiterungen wie in zu trennen zsh
und eval
mit einem Here-Dokument für dieandere ErweiterungenTeil wie:
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//
)"
Das würde Tilde-, Parameter-, Arithmetik- und Befehlserweiterungen wie zsh
oben ermöglichen. }