O que a linha 'set-$ args' faz aqui e por que ela se comporta de maneira diferente entre Zsh e Bash?

1

Na página manp para a versão de getopt que vem com o Mac OSX, é dado um exemplo que usa a construção args=$(getopt optstring $*); set -- $args . O que o set -- $args faz aqui?

Além disso, considere a função e a string de teste

f () {
    args=$(getopt o: $*)
    set -- $args
    for i; do
        echo $i
    done
}

f -o 123 xyz

No Bash 3.2 e 4.3, isso produz

-o
123
--
xyz

mas no Zsh 5.1, produz

 -o 123 -- xyz

O que causa a discrepância? É por causa do comportamento diferente de divisão de palavras do Zsh, ou alguma outra coisa? Eu tentei diferentes combinações de sinalizadores de expansão de cotação e parâmetro e não consegui descobrir o que estava acontecendo sob o capô em qualquer shell.

    
por shadowtalker 03.10.2015 / 19:05

2 respostas

1

set -- $args define os argumentos posicionais como base no conteúdo de $args . Agora você obterá o comportamento diferente entre zsh e outros shells POSIX.

Como o zsh não realiza Divisão de campos por padrão, você receberá uma string, que é o conteúdo de $args . Você deve chamar a divisão explicitamente para obter o mesmo comportamento que bash (e também outras shells POSIX):

set -- ${=args}

bash perform Field Splitting no conteúdo de $args , produz quatro sequências conforme você obteve. Você pode verificar $# para saber o número de argumentos posicionais após set -- $args .

Observe que, no caso de bash , você deve adicionar set -f para desativar a globulação também.

    
por 03.10.2015 / 19:16
2

Este é um código ruim. getopt é praticamente inutilizável¹. Use o getopts shell embutido (ele tem duas vantagens: ele pode funcionar e está disponível em todas as plataformas POSIX).

O que o args=$(getopt optstring $*); set -- $args faz é:

  • Divida cada argumento da linha de comando no espaço em branco e substitua-o pela lista de palavras delimitadas por espaço em branco.
  • Pegue cada uma das palavras resultantes e interprete-as como um padrão curinga. Se o padrão corresponder a pelo menos um arquivo, substitua o padrão pela lista de arquivos correspondentes.
  • Transmita a lista de palavras resultante para o comando getopt .
  • Armazena a saída do comando getopt na variável args . Esta é uma string que consiste nos argumentos da linha de comando unidos com espaço em branco como o separador, e reordenados para ter opções e seus argumentos, então -- , então operandos não opcionais.
  • Divida essa string no espaço em branco, interprete cada palavra como um padrão curinga e substitua o padrão pela lista de arquivos correspondentes, se houver algum.
  • Use a lista resultante como argumentos posicionais.

Em shells ao estilo Bourne / POSIX, $foo é o operador “split + glob”. Você precisa de aspas duplas para obter o valor de uma variável: "$foo" . Mas em zsh, $foo funciona de maneira diferente: ela se expande para o valor de foo , exceto quando foo está vazio. Então, em zsh, você obtém um único parâmetro posicional.

O primeiro uso do operador split + glob poderia ser evitado escrevendo getopt optstring "$@" : isso passaria os parâmetros posicionais exatamente para getopt . Mas ao analisar a saída de getopt , a divisão é inevitável (você pode desabilitar a globulação), porque getopt usa espaços para separar argumentos. O problema com getopt é que a saída é ambígua: não há como distinguir um espaço que estava em um argumento de um espaço que getopt adicionou como separador.

O jeito certo de fazer isso é com getopts . É robusto e portátil.

while getopts optstring OPTLET; do
  # We got the option -$OPTLET
  case $OPTLET in
    …
  esac
done
shift $((OPTIND-1))
# Now the positional parameters are just the non-option operands

¹ Pelo menos a versão do BSD. A versão GNU adiciona recursos que a tornam utilizável.

    
por 04.10.2015 / 01:38