A Expansão Variável do Bash pode ser executada diretamente na entrada do usuário?

2

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

Estou tentando solicitar que o usuário insira um caminho no meio de um programa, mas como ~ e outras variáveis não são expandidas como parte do read construído, os usuários precisam inserir um caminho absoluto. / p>

Exemplo: quando um usuário digita um caminho para:

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

retorna verdadeiro quando um usuário insere /home/user/bin , mas não ~/bin ou $HOME/bin .

    
por Squaven 15.10.2016 / 07:16

1 resposta

4

Um jeito ingênuo seria:

eval "dirin=$dirin"

O que isso faz é avaliar a expansão de dirin=$dirin como código shell.

Com dirin contendo ~/foo , está realmente avaliando:

dirin=~/foo

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

dirin=foo bar

Portanto, ele está executando bar com dirin=foo em seu 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 eval , mas escape todos os caracteres, exceto aqueles que permitiriam que seriam virtualmente impossíveis a não ser implementando um analisador de sintaxe de shell completo, a menos que você o limite, por exemplo ~foo , $VAR , ${VAR} . / p>

Aqui, eu usaria zsh em vez de bash que tem um operador dedicado para isso:

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

vared é o editor de variáveis , semelhante a bash ' read -e .

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

Para resolver a expansão de 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 (assim bash ly também), para executar a expansão de til e variável (não de 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 uma aproximação, podemos também preceder todos os caracteres, mas ~${}-_. e alnums com barra invertida antes de passar para eval :

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

(aqui simplificado com base no fato de que $dirin não pode conter caracteres de nova linha, pois é proveniente de read )

Isso acionaria erros de sintaxe se alguém inserisse ${foo#bar} , por exemplo, mas pelo menos isso não prejudicasse muito como um simples eval faria.

Editar : uma solução de trabalho para bash e outros POSIX shells seria separar o til e outras expansões como em zsh e usar eval com um documento aqui para o < em> outras expansões parte 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âmetro, aritmética e comando, como em zsh acima.     }

    
por 15.10.2016 / 09:31