Bash 變數擴充可以直接根據使用者輸入執行嗎?

Bash 變數擴充可以直接根據使用者輸入執行嗎?

在bash中,有沒有辦法讀取用戶輸入但仍然允許bash變數擴展?

我試圖請求使用者在程式中間輸入路徑,但由於~其他變數沒有作為內建的一部分擴展read,因此使用者必須輸入絕對路徑。

範例:當使用者輸入路徑時:

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

當使用者輸入/home/user/bin但未輸入~/bin或 時傳回 true $HOME/bin

答案1

一個天真的方法是:

eval "dirin=$dirin"

dirin=$dirin它的作用是評估as shell 程式碼的擴展。

對於dirincontains ~/foo,它實際上是在評估:

dirin=~/foo

很容易看出其限制。如果dirin包含foo bar,則變為:

dirin=foo bar

bar所以它在它的環境中運行dirin=foo(並且所有 shell 特殊字元都會遇到其他問題)。

在這裡,您需要決定允許哪些擴展(波形符、命令替換、參數擴展、進程替換、算術擴展、文件名擴展...),並且要么手動執行這些替換,要么使用evalbut逃脫除了那些允許它們的字符之外的每個字符,除了實現完整的 shell 語法解析器之外,這實際上是不可能的,除非您將其限制為例如~foo, $VAR, ${VAR}

在這裡,我將使用專門的運算符來zsh代替:bash

vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"

vared是個變數編輯器,類似於bashread -e

(e)是一個參數擴展標誌,用於在參數內容中執行擴展(參數、命令、算術,但不包括波形符)。

為了解決僅發生在字串開頭的波形符擴展,我們會這樣做:

vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
  eval "dirin=$match[1]\${(e)match[3]}"
else
  dirin=${(e)dirin}
fi

POSIXly(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

作為近似,我們也可以~${}-_.在傳遞給之前在每個字元 but 和 alnums 前面加上反斜線eval

eval "dirin=$(
  printf '%s\n' "$dirin" |
    sed 's/[^[:alnum:]~${}_.-]/\\&/g')"

(這裡進行了簡化,$dirin不能包含換行符,因為它來自read

${foo#bar}例如,如果有人輸入,這會觸發語法錯誤,但至少不會像簡單的那樣造成太大傷害eval

編輯: 和其他 POSIX shell 的一個可行解決方案bash是將波形符號和其他擴充分開,例如 in並與此處文件一起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。 }

相關內容