Como escrever um script que funcione com ou sem argumentos?

13

Eu tenho um script bash que é algo como isto:

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

Então, o que eu quero poder fazer não é apenas executá-lo com ./script --testor ./script -t, mas também sem argumentos (apenas ./script), mas aparentemente se eu fizer isso com o código atual, a saída é apenas:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

Então, como programo para que a execução sem argumentos funcione elsesem gerar o erro? O que estou fazendo de errado?

Peter Cordes
fonte
1
Você precisa de colchetes duplos nos cheques. [[]]
Terrance
3
Veja Bash pitfall # 4
steeldriver
@ Terrrance, você não precisa deles, embora deva usá-los se tiver como alvo o bash.
Ruslan

Respostas:

1

A "maneira correta" de usar opções longas em scripts de shell é através do utilitário getopt GNU . Também há getopts que são embutidos no bash , mas permitem apenas opções curtas como -t. Alguns exemplos de getoptuso podem ser encontrados aqui .

Aqui está um script que demonstra como eu abordaria sua pergunta. A explicação da maioria das etapas é adicionada como comentários no próprio script.

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

Com algumas simplificações e comentários removidos, isso pode ser condensado em:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac
Sergiy Kolodyazhnyy
fonte
16

Várias formas; os dois mais óbvios são:

  • Coloque $1aspas duplas:if [ "$1" = "--test" ]
  • Verifique o número de argumentos usando $ #

Melhor ainda, use getopts.

JayEye
fonte
2
+1 para usar getopts. Esse é o melhor caminho a percorrer.
Seth
3
Outro idioma comum é [ "x$1" = "x--test" ]defender novamente argumentos da linha de comandos que são operadores válidos para o [comando. (Esta é outra razão pela qual [[ ]]é o preferido: é parte da sintaxe shell, e não apenas um comando integrado.)
Peter Cordes
7

Você precisa citar suas variáveis ​​dentro da condição if. Substituir:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

com:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

Então vai funcionar:


Not ~ ./test.sh Não está testando.

Sempre cite sempre suas variáveis!

Seth
fonte
As aspas simples são essenciais ou pode-se usar aspas duplas?
A resposta precisa depende de qual shell você está usando, mas da sua pergunta, suponho que você esteja usando um descendente de Bourne-shell. A principal diferença entre aspas simples e duplas é que a expansão variável ocorre entre aspas duplas, mas não entre aspas simples: $ FOO = bar $ echo "$ FOO" bar $ echo '$ FOO' $ FOO (hmm ... pode ' código t formato nos comentários ...)
JayEye
@ParanoidPanda Você não precisa usar aspas simples, não, mas é uma boa prática usá-las em literais de strings. Dessa forma, você não ficará surpreso mais tarde se seus dados de string tiverem um símbolo que deseja expandir.
Seth
@ParanoidPanda O que @JayEye disse: Com aspas simples, não há foo=bar echo '$foo'impressões de expansão variável $foona saída, mas sim foo=bar echo "$foo"impressões bar.
EKons
4

Você está usando o bash. Bash tem uma ótima alternativa para [: [[. Com [[, você não precisa se preocupar com a cotação e terá muito mais operadores do que [, incluindo suporte adequado para ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Ou você pode usar expressões regulares:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Obviamente, citações apropriadas sempre devem ser feitas, mas não há razão para se manter [ao usar o bash.

muru
fonte
3

Para testar se argumentos estão presentes (em geral, e executar as ações adequadamente) Isso deve funcionar:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi
Jacob Vlijm
fonte
Por que não [ -z "$1" ]?
EKons
@ ΈρικΚωνσταντόπουλος Nesse caso, claro, normalmente, porém em scripts, eu chamo a variável uma única vez, refiro-a nos procedimentos, que geralmente é mais de uma vez. Decidiu manter o protocolo na amostra.
Jacob Vlijm
Nesta amostra, você parece usar $check uma vez, antes de qualquer shifts, portanto, seria melhor usá-lo $1.
EKons
@ ΈρικΚωνσταντόπουλος É claro que não, como mencionado, é uma amostra , para ser usado em scripts. Nos scripts, é uma prática ruim fazer repetidamente o mesmo repetidamente.
Jacob Vlijm
Entendo o que você quer dizer, mas é melhor prática não usar shebangs#! ao fornecer amostras (você deve descrever o shell fora do código).
EKons