¿Cómo escribir un guión que funcione con o sin argumentos?

13

Tengo un script bash que dice algo como esto:

#!/bin/bash

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

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

fi

Entonces, lo que quiero poder hacer no es solo ejecutarlo con ./script --testo ./script -t, sino también sin argumentos (solo ./script), sino que aparentemente si hago eso con el código actual, la salida es solo:

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

Entonces, ¿cómo lo programo para que ejecutarlo sin argumentos lo haga elsesin arrojar el error? ¿Qué estoy haciendo mal?

Peter Cordes
fuente
1
Necesita corchetes dobles alrededor de sus cheques. [[]]
Terrance
3
Ver Bash trampa # 4
steeldriver
@Terrance no los necesita , aunque debe usarlos si apunta a bash.
Ruslan

Respuestas:

1

La "forma correcta" de usar opciones largas en el script de shell es a través de la utilidad getopt GNU . También hay getopts que es un bash incorporado , pero solo permite opciones cortas como -t. Algunos ejemplos de getoptuso se pueden encontrar aquí .

Aquí hay un script que demuestra cómo abordaría su pregunta. La explicación de la mayoría de los pasos se agrega como comentarios dentro del 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"

Con algunas simplificaciones y comentarios eliminados, esto se puede condensar en:

#!/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
fuente
16

Varias formas; Los dos más obvios son:

  • Poner $1entre comillas dobles:if [ "$1" = "--test" ]
  • Verifique el número de argumentos usando $ #

Mejor aún, usa getopts.

JayEye
fuente
2
+1 por usar getopts. Ese es el mejor camino a seguir.
Septiembre
3
Otro idioma común es [ "x$1" = "x--test" ]defender nuevamente los argumentos de la línea de comandos que son operadores válidos para el [comando. (Esta es otra razón por la que [[ ]]se prefiere: es parte de la sintaxis de shell, no solo un comando incorporado.)
Peter Cordes
7

Debe citar sus variables dentro de la condición if. Reemplazar:

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

con:

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

Entonces funcionará:

➜ ~ ./test.sh
No prueba.

¡Siempre siempre comillas dobles tus variables!

Seth
fuente
¿Son esenciales las comillas simples o se pueden usar comillas dobles?
La respuesta precisa depende de qué shell está utilizando, pero a partir de su pregunta, supongo que está utilizando un descendiente de Bourne-shell. La principal diferencia entre comillas simples y dobles es que la expansión variable ocurre dentro de las comillas dobles, pero no dentro de las comillas simples: $ FOO = bar $ echo "$ FOO" bar $ echo '$ FOO' $ FOO (hmm .... can ' t formato de código en los comentarios ...)
JayEye
@ParanoidPanda No tiene que usar comillas simples, no, pero es una buena práctica usarlas en literales de cadena. De esa manera, no se sorprenderá más adelante si sus datos de cadena tienen un símbolo bash que desea expandir.
Septiembre
@ParanoidPanda Lo que dijo @JayEye: con comillas simples no hay foo=bar echo '$foo'impresiones de expansión variable $fooen la salida, sino que se foo=bar echo "$foo"imprime en su barlugar.
EKons
4

Estás usando bash. Bash tiene una gran alternativa a [: [[. Con esto [[, no tiene que preocuparse por las cotizaciones, y obtiene muchos más operadores que [, incluido el soporte adecuado para ||:

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

O puede usar expresiones regulares:

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

Por supuesto, siempre se debe hacer una cita adecuada, pero no hay razón para seguir [cuando se usa bash.

muru
fuente
3

Para probar si hay argumentos presentes (en general, y hacer las acciones en consecuencia) Esto debería funcionar:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi
Jacob Vlijm
fuente
¿Por qué no [ -z "$1" ]?
EKons
@ ΈρικΚωνσταντόπουλος En este caso, seguro, normalmente, sin embargo, en los scripts, llamo a la variable una sola vez, consulte en los procedimientos, que generalmente es más de una vez. Decidió mantener el protocolo en la muestra.
Jacob Vlijm
En esta muestra, parece usar $checkuna vez, antes de cualquier shifts, por lo que sería mejor usarla $1.
EKons
@ ΈρικΚωνσταντόπουλος Por supuesto que no, como se ha mencionado, es una muestra , que se utilizará en las secuencias de comandos. En los scripts, es una mala práctica hacer lo mismo repetidamente una y otra vez.
Jacob Vlijm
Entiendo lo que quiere decir, pero es una mejor práctica no usar shebangs #!cuando proporcione muestras (debe describir el shell fuera del código).
EKons