Bash 変数の展開はユーザー入力で直接実行できますか?

Bash 変数の展開はユーザー入力で直接実行できますか?

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(シェルの特殊文字すべてで他の問題が発生します)。

ここでは、どのような展開が許可されるか(チルダ、コマンド置換、パラメータ展開、プロセス置換、算術展開、ファイル名展開など)を決定し、それらの置換を手動で行うか、またはevalbutを使用する必要がある。逃げる許可される文字を除くすべての文字は、たとえば~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です。zshevalその他の拡張次のような部分:

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。}

関連情報