A expansão de variável Bash pode ser executada diretamente na entrada do usuário?

A expansão de variável Bash pode ser executada diretamente na entrada do usuário?

No bash, existe alguma maneira de ler a entrada do usuário, mas ainda permitir basha expansão de variáveis?

Estou tentando solicitar que o usuário insira um caminho no meio de um programa, mas como ~outras variáveis ​​não são expandidas como parte do readbuilt-in, os usuários precisam inserir um caminho absoluto.

Exemplo: quando um usuário insere um caminho em:

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

retorna verdadeiro quando um usuário entra, /home/user/binmas não ~/binou $HOME/bin.

Responder1

Uma maneira ingênua seria:

eval "dirin=$dirin"

O que isso faz é avaliar a expansão do dirin=$dirincódigo shell.

Com dirincontains ~/foo, na verdade está avaliando:

dirin=~/foo

É fácil ver as limitações. Com um dirincontendo foo bar, isso se torna:

dirin=foo bar

Portanto, ele está sendo executado barem dirin=fooseu ambiente (e você teria outros problemas com todos os caracteres especiais do shell).

Aqui, você precisaria decidir quais expansões são permitidas (til, substituição de comando, expansão de parâmetro, substituição de processo, expansão aritmética, expansão de nome de arquivo...) e fazer essas substituições manualmente ou usar evalmasescapartodos os caracteres, exceto aqueles que os permitiriam, o que seria virtualmente impossível, a não ser pela implementação de um analisador de sintaxe de shell completo, a menos que você o limite, por exemplo ~foo, $VAR, , ${VAR}.

Aqui, eu usaria zshem vez bashdisso um operador dedicado para isso:

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

varedé oeditor de variáveis, semelhante ao bashde read -e.

(e)é um sinalizador de expansão de parâmetro que realiza expansões (parâmetro, comando, aritmética, mas não til) no conteúdo do parâmetro.

Para resolver a expansão do til, que ocorre apenas no início da string, faríamos:

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

POSIXly (também bash), para realizar expansão de til e variável (não parâmetro), você poderia escrever uma função como:

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

Exemplo:

$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane

Como aproximação, também poderíamos acrescentar todos os caracteres but ~${}-_.e alnums com uma barra invertida antes de passar para eval:

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

(aqui simplificado porque $dirinnão pode conter caracteres de nova linha, pois vem de read)

Isso provocaria erros de sintaxe se alguém digitasse ${foo#bar}, por exemplo, mas pelo menos isso não pode causar tantos danos quanto um simples evalfaria.

Editar: uma solução funcional para bashe outros shells POSIX seria separar o til e outras expansões como em zshe usar evalcom um documento aqui para ooutras expansõesparte como:

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//
)"

Isso permitiria expansões de til, parâmetros, aritmética e comando como zshacima. }

informação relacionada