
bash では、ユーザー入力を読み取りながらbash
変数の拡張を許可する方法はありますか?
プログラムの途中でユーザーにパスの入力を要求しようとしていますが、~
その他の変数は組み込みの一部として展開されないためread
、ユーザーは絶対パスを入力する必要があります。
例: ユーザーが次のパスを入力すると:
read -ep "input> " dirin
[[ -d "$dirin" ]]
ユーザーが または を入力しなかった場合は true を返し/home/user/bin
ます。~/bin
$HOME/bin
答え1
素朴な方法は次のとおりです。
eval "dirin=$dirin"
dirin=$dirin
これは、シェル コードとしての展開を評価します。
dirin
を含む場合~/foo
、実際には以下が評価されます:
dirin=~/foo
制限は簡単にわかります。 をdirin
含む を使用するとfoo bar
、次のようになります。
dirin=foo bar
bar
したがって、その環境内で実行されますdirin=foo
(シェルの特殊文字すべてで他の問題が発生します)。
ここでは、どのような展開が許可されるか(チルダ、コマンド置換、パラメータ展開、プロセス置換、算術展開、ファイル名展開など)を決定し、それらの置換を手動で行うか、またはeval
butを使用する必要がある。逃げる許可される文字を除くすべての文字は、たとえば~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
POSIX に従って (同様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
近似的に、~${}-_.
と alnums 以外のすべての文字の先頭にバックスラッシュを付けてから に渡すこともできますeval
。
eval "dirin=$(
printf '%s\n' "$dirin" |
sed 's/[^[:alnum:]~${}_.-]/\\&/g')"
$dirin
(ここでは、 から派生したように改行文字を含めることができないという理由で簡略化されていますread
)
たとえば、入力すると構文エラーが発生します${foo#bar}
が、少なくとも単純な場合ほど大きな害はありませんeval
。
編集: および他のPOSIXシェルの実用的な解決策は、チルダと他の展開を分離し、ヒアドキュメントで使用することbash
です。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
。}