ユーザー入力に基づいてBash変数拡張を直接実行できますか?

ユーザー入力に基づいてBash変数拡張を直接実行できますか?

Bashからユーザー入力を読みながらbash変数の拡張を許可する方法はありますか?

プログラムの途中でユーザーにパスを入力しようとしますが、組み込み~部分に他の変数が拡張されないため、readユーザーは絶対パスを入力する必要があります。

例: ユーザーがパスを入力する場合:

read -ep "input> " dirin
  [[ -d "$dirin" ]] 

ユーザーが入力したが/home/user/bin入力しなかった場合、~/binまたはを返します$HOME/bin

ベストアンサー1

素朴なアプローチは次のとおりです。

eval "dirin=$dirin"

dirin=$dirinその目的は、シェルコードの拡張として評価することです。

dirinContains の場合、~/foo実際に以下を評価します。

dirin=~/foo

限界が簡単にわかります。dirin含まれている場合はfoo bar次のようになります。

dirin=foo bar

barしたがって、その環境内で実行されますdirin=foo(そしてすべてのシェル特殊文字には他の問題が発生します)。

ここで許容される拡張(チルダ、コマンド置換、パラメータ拡張、プロセス置換、算術拡張、ファイル名拡張...)を決定し、これらの置換を手動で実行するか、次のように実行する必要がありますeval脱出する~fooたとえば、$VARなどに限定されない限り、許可されている文字を除くすべての文字に対してフルシェルパーサーを実装することは実際には不可能です${VAR}

ここでは代わりに特殊演算子を使用しますzshbash

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

POSIXly(bashまた、true)、チルダ、および変数(パラメータの代わりに)拡張を実行するには、次の関数を作成できます。

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

おおよその次~${}-_.に渡す前に、各文字の前にバックスラッシュを付けることもできます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。 }

おすすめ記事