Можно ли выполнять расширение переменных 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's 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также в POSIX) для выполнения тильды и расширения переменной (не параметра) можно написать функцию вроде:

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код.

Редактировать: рабочим решением для bashи других оболочек POSIX было бы отделить тильду и другие расширения, такие как в zshи использовать evalс here-документом длядругие расширениячасть вроде:

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выше. }

Связанный контент