bash como remover opções de parâmetros após o processamento

8

Lembro de ter visto em algum lugar um script bash usando case e shift para percorrer a lista de parâmetros posicionais, analisar argumentos e opções com argumentos quando eles são encontrados e removê-los após a análise para deixar apenas o argumentos nus, que são posteriormente processados pelo resto do script.

Por exemplo, ao analisar a linha de comando de cp -R file1 -t /mybackup file2 -f , ele percorre os parâmetros primeiro, reconhece que o usuário solicitou a descida aos diretórios por -R , especificou o destino por -t /mybackup e forçou a cópia por -f e remova-os da lista de parâmetros, deixando o programa para processar file1 file2 como os argumentos restantes.

Mas parece que não consigo lembrar / descobrir o script que vi sempre. Eu só gostaria de poder fazer isso. Tenho pesquisado vários sites e anexado uma lista de páginas relevantes que examinei.

Uma pergunta neste site perguntou especificamente sobre "opções independentes de pedido", mas tanto a resposta única quanto a resposta da pergunta para a qual foi descartada não considera casos como o acima, em que as opções são misturadas com argumentos normais, que eu presume-se que a razão para a pessoa mencionar especificamente as opções independentes da ordem .

Como bash do getopts parece parar no primeiro argumento não-opcional, ele não parece ser suficiente como uma solução. É por isso que a página do Wooledge BashFAQ (veja abaixo) explica como reorganizar os argumentos. Mas eu gostaria de evitar a criação de múltiplos arrays caso a lista de argumentos seja longa.

Como shift não suporta o popping de argumentos individuais no meio da lista de parâmetros, não sei ao certo o que é uma maneira direta de implementar o que estou perguntando.

Gostaria de saber se alguém tem alguma solução para remover argumentos do meio da lista de parâmetros sem criar uma matriz totalmente nova.

Páginas que eu já vi:

por jamadagni 18.09.2014 / 13:26

2 respostas

9

POSIXly, a análise das opções deve parar em -- ou no primeiro argumento que não seja de opção (ou não-opção-argumento), o que ocorrer primeiro. Então, em

cp -R file1 -t /mybackup file2 -f

que está em file1 , por isso cp deve recursivamente copiar todos os file1 , -t , /mybackup e file2 para o diretório -f .

GNU getopt(3) no entanto (aquele GNU cp usa as opções de análise (e aqui você está usando o GNU cp desde que você está usando a opção -t específica do GNU)), a menos que o $POSIXLY_CORRECT variável de ambiente é definida, aceita opções após os argumentos. Então, é realmente equivalente à análise de estilo de opção POSIX:

cp -R -t /mybackup -f -- file1 file2

O shell getopts embutido, mesmo no shell GNU ( bash ), somente manipula o estilo POSIX. Também não suporta opções longas ou opções com argumentos opcionais.

Se você quiser analisar as opções da mesma maneira que o GNU cp , você precisará usar a API GNU getopt(3) . Para isso, se no Linux, você pode usar o utilitário getopt aprimorado de util-linux ( essa versão aprimorada do getopt O comando também foi portado para alguns outros Unices, como o FreeBSD ).

Esse getopt reorganizará as opções de maneira canônica, permitindo que você analise simplesmente com um loop while/case .

$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
 -R -f -t '/mybackup' -- 'file1' 'file2'

Você costuma usá-lo como:

parsed_options=$(
  getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
  case $1 in
    (-[Rf]) shift;;
    (-t) shift 2;;
    (--) shift; break;;
    (*) exit 1 # should never be reached.
  esac
done
echo "Now, the arguments are $*"

Observe também que getopt analisará as opções da mesma maneira que o GNU cp . Em particular, ele suporta as opções longas (e as digita abreviadamente) e respeita as variáveis de ambiente $POSIXLY_CORRECT (que, quando definidas, desabilitam o suporte para opções após argumentos) da mesma maneira que o GNU cp faz.

Observe que o uso do gdb e a impressão dos argumentos que o getopt_long() recebe podem ajudar a criar os parâmetros para getopt(1) :

(gdb) bt
#0  getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
    name = 0x4171fb "archive",
    has_arg = 0,
    flag = 0x0,
    val = 97
  }, {
    name = 0x417203 "attributes-only",
[...]

Então você pode usar getopt como:

getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"

Lembre-se de que a lista de opções suportadas do GNU cp pode mudar de uma versão para a próxima e que getopt não poderá verificar se você passa um valor legal para a opção --sparse , por exemplo. / p>     

por 18.09.2014 / 14:34
1

Portanto, sempre que getopts processar um argumento, ele não espera que defina a variável de shell $OPTIND para o próximo número na lista de argumentos que deve processar e retorna diferente de 0. Se $OPTIND estiver definido como um valor de 1, getopts é especificado em POSIX para aceitar uma nova lista de argumentos. Portanto, isso apenas assiste getopts return, salva incrementos de um contador mais $OPTIND para qualquer retorno com falha, desloca os argumentos com falha e redefine $OPTIND a cada tentativa falhada. Você pode usá-lo como opts "$@" - embora você queira personalizar o loop case ou salvá-lo em uma variável e alterar essa seção para eval $case .

opts() while getopts :Rt:f opt || {
             until command shift "$OPTIND" || return 0
                   args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
                   [ -n "${1#"${1#-}"}" ]
             do OPTIND=1; done 2>/dev/null; continue
};     do case "$opt"  in
             R) Rflag=1      ;;
             t) tflag=1      ;
                targ=$OPTARG ;;
             f) fflag=1      ;;
       esac; done

Durante a execução, ele define $args para cada argumento que getopts não manipulou ... então ...

set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\n "$args"
printf %s\n "$@"         
printf %s\n "$Rflag" "$tflag" "$fflag" "$targ"

OUTPUT

 "${2}" "${5}"
file1
file2
1
1
1
/mybackup

Isso funciona em bash , dash , zsh , ksh93 , mksh ... bem, parei de tentar nesse ponto. Em cada shell, também obteve $[Rtf]flag e $targ . O ponto é que todos os números para os argumentos que getopts não queriam processar permaneciam.

Alterar o estilo das opções também não fez diferença. Funcionou como -Rf -t/mybackup ou -R -f -t /mybackup . Funcionou no meio da lista, no final da lista ou no topo da lista ...

Ainda assim, a melhor maneira é apenas colocar -- para o fim das opções na sua lista arg e então shift "$(($OPTIND-1))" no final de uma execução de processamento getopts . Dessa forma, você remove todos os parâmetros processados e mantém o final da lista de argumentos.

Uma coisa que eu gosto de fazer é traduzir longas opções para curtas - e eu faço isso de uma maneira muito similar, e é por isso que essa resposta veio facilmente - antes eu corro getopts .

i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1"                   in
--Recursive) set -- "$@" "-R"  ;;
--file)      set -- "$@" "-f"  ;;
--target)    set -- "$@" "-t"  ;;
*)           set -- "$@" "$1"  ;;
esac; shift; done
    
por 18.09.2014 / 14:03