Como evito que as opções de comando quebrem outros scripts executados no meu comando?

1

Eu tenho um script de shell bash ( my_script.sh ) que executa um script ( their_script ) durante a execução. Meu script ( my_script.sh ) é assim:

THISDIR='dirname $(readlink -f $0)'
main() {
    cd $THISDIR
    source their-script
}
main "$@"

their-script é um arquivo que não devo alterar. their-script tem essas coisas:

BDIR="$1"
...
BDIR='readlink -f "$BDIR"

Tudo acima funciona muito bem. Gostaria de adicionar algumas opções, então alterei my_script.sh para ficar assim:

THISDIR='dirname $(readlink -f $0)'
check_options() {
    while [ "$1" != "" ]; do
        case $1 in
            --username )  shift
                          OPTIONS_USERNAME=$1
                          ;;
            * )           # No more options
                          ;;
        esac
        shift
    done
}

main() {
    check_options
    cd $THISDIR
    source their-script
}
main "$@"

Agora, quando executo ./my_script.sh --username example , their-script falha durante a execução de:

readlink -f --username

e cospe esta linha:

readlink: unrecognized option '--username'

Como posso impedir que os parâmetros posicionais do meu script quebrem outros scripts dessa maneira?

    
por karobar 19.01.2016 / 20:26

2 respostas

4

Como @roaima apontou, você provavelmente deseja executar o script em um processo separado. Fornecê-lo em seu código sem um entendimento completo de seu funcionamento interno pode ser bastante perigoso:

FILE_TO_REMOVE="/tmp/foobar"
source some-cool-script
rm -Rf "$FILE_TO_REMOVE"

é, obviamente, não o jeito certo de fazer as coisas, porque o script legal pode acontecer de incluir

FILE_TO_REMOVE=/

Sem mencionar que o script pode ser alterado a qualquer momento (mesmo que não seja "ativamente" mantido).

Além disso, há várias coisas que você deve considerar:

  1. processamento de parâmetros posicionais é melhor controlado pelo número de parâmetros restantes:

    while [ $# -gt 0 ]; then
    ...
    done
    

    string vazia como "" pode ser um argumento perfeitamente válido, a menos que você queira usá-la como separador. Mesmo se você fizer isso (tradicionalmente -- é usado), é sem dúvida melhor manter a condição e uso acima:

        case "$1" in
            ...
            "")
                shift
                break
                ;;
            ...
        esac
    

    que é mais fácil de manter.

  2. A variável $@ é por escopo. Isso significa que, se você decidir executar o script externo como normalmente é feito, ou seja, chamando-o em vez de fazer o sourcing em seu código, você precisará passar os argumentos:

    their-script "$@"
    

    Usar um caminho completo para o script também pode não ser uma má ideia, especialmente se o script estiver sendo executado com privilégios elevados.

  3. O acima também significa que isso realmente não verifica os argumentos da linha de comando:

    main() {
        check_options
    }
    main "$@"
    

    Você precisa chamá-lo como

        check_options "$@"
    
  4. No entanto, o acima também significa que os argumentos em main não serão modificados de forma alguma por check_options . Se você precisar filtrar algumas opções para que elas não acabem quebrando o script externo (e fazendo a pergunta que você faz), você terá duas opções:

    • coloque a análise de opções em main ou até mesmo no escopo global de seu script. É mais rápido e um pouco confuso na medida em que a limpeza do código é considerada.

    • mantém a análise de opções em uma função separada e faz algumas malabarísticas de variáveis:

      check_options {
          # parse options magic
          # what needs to be passed over is
          # kept in a separate variable
          PASS_THROUGH_OPTS=...
      }
      
      main {
          check_options "$@"
          set -- $PASS_THROUGH_OPTS
          ...
      }
      

      Observe que isso não lida com a possibilidade de que os parâmetros posicionais possam conter espaços ou outros separadores de palavras. Eu não sei de qualquer outra maneira de lidar com isso corretamente do que usando arrays (que é específico da implementação) e envolve coisas como:

      check_options {
          ...
              # parameter should be kept for further use
              x=( "${x[@]}" "$1" )
          ...
      }
      
      check_options "$@"
      set -- "${x[@]}"
      
por 20.01.2016 / 01:21
3

O comando source their-script diz ao seu shell para executar their-script diretamente no contexto do seu script. Isso significa que ele tem acesso a todas as suas variáveis e pode até mesmo alterá-las.

Se você remover a palavra source e apenas executar their-script como um comando, não será possível afetar qualquer um dos seus códigos, e seu $1 será o primeiro argumento que você fornecer, portanto, neste exemplo $1 terá o valor banana :

their-script banana
    
por 19.01.2016 / 21:32