Validación de parámetros a un script Bash

95

Se me ocurrió uno básico para ayudar a automatizar el proceso de eliminación de varias carpetas a medida que se vuelven innecesarias.

#!/bin/bash
rm -rf ~/myfolder1/$1/anotherfolder
rm -rf ~/myfolder2/$1/yetanotherfolder
rm -rf ~/myfolder3/$1/thisisafolder

Esto se evoca así:

./myscript.sh <{id-number}>

El problema es que si te olvidas de escribir id-number (como hice en ese momento) , podría eliminar muchas cosas que realmente no quieres que se eliminen.

¿Hay alguna manera de agregar alguna forma de validación a los parámetros de la línea de comando? En mi caso, sería bueno comprobar que a) hay un parámetro, b) es numérico yc) esa carpeta existe; antes de continuar con el guión.

nickf
fuente

Respuestas:

158
#!/bin/sh
die () {
    echo >&2 "$@"
    exit 1
}

[ "$#" -eq 1 ] || die "1 argument required, $# provided"
echo $1 | grep -E -q '^[0-9]+$' || die "Numeric argument required, $1 provided"

while read dir 
do
    [ -d "$dir" ] || die "Directory $dir does not exist"
    rm -rf "$dir"
done <<EOF
~/myfolder1/$1/anotherfolder 
~/myfolder2/$1/yetanotherfolder 
~/myfolder3/$1/thisisafolder
EOF

editar : Me perdí la parte sobre verificar si los directorios existen al principio, así que agregué eso, completando el script. Además, se han abordado las cuestiones planteadas en los comentarios; se corrigió la expresión regular, se cambió de ==a eq.

Este debería ser un script portátil compatible con POSIX hasta donde yo sé; no usa bashismos, lo cual es realmente importante porque /bin/shen Ubuntu lo es en dashestos días, no bash.

Brian Campbell
fuente
recuerde establecer + e y usar '-eq' en lugar de '==' para comparaciones de enteros
armas
Lo cambió a -eq; ¿Qué te compra set + e aquí?
Brian Campbell
Encontré dos cosas en mi respuesta que es posible que también desee arreglar en la suya: primero, el SO hilighter se vuelve loco por $ # (tratándolo como un comentario). Hice "$ #" para solucionarlo. segundo, la expresión regular también coincide con "foo123bar". Lo arreglé haciendo ^ [0-9] + $. también puede solucionarlo usando la opción -x de grep
Johannes Schaub - litb
1
@ojblass Me faltaba una de las pruebas por las que estaba preguntando. Agregar eso significaba también agregar sus directorios para probar, lo que expandió significativamente el tamaño de la respuesta ya que no caben en una línea. ¿Puede sugerir una forma más compacta de probar la existencia de cada directorio?
Brian Campbell
1
Según el comentario de respuesta de @Morten Nielsen a continuación, el grep '$ [0-9] + ^' se ve realmente extraño. ¿No debería ser '^ [0-9] + $'?
martin jakubik
21

La shsolución de Brian Campbell, aunque noble y bien ejecutada, tiene algunos problemas, así que pensé en proporcionar mi propia bashsolución.

Los problemas con el shuno:

  • La tilde ~/foono se expande a su directorio principal dentro de heredocs. Y tampoco cuando se lee en la readdeclaración o se cita en la rmdeclaración. Lo que significa que obtendrá No such file or directoryerrores.
  • Bifurcar grepy cosas así para operaciones básicas es una tontería. Especialmente cuando estás usando un caparazón de mierda para evitar el peso "pesado" de bash.
  • También noté algunos problemas de citas, por ejemplo, en torno a una expansión de parámetros en su echo.
  • Aunque es poco común, la solución no puede hacer frente a los nombres de archivo que contienen nuevas líneas. (Casi ninguna solución shpuede hacerles frente, por lo que casi siempre prefiero bash, es mucho más a prueba de balas y más difícil de explotar cuando se usa bien).

Si bien, sí, usar /bin/shpara su hashbang significa que debe evitar los bashismos a toda costa, puede usar todos los bashismos que desee, incluso en Ubuntu o cualquier otra cosa cuando sea honesto y esté #!/bin/bashen la cima.

Entonces, aquí hay una bashsolución que es más pequeña, más limpia, más transparente, probablemente "más rápida" y más a prueba de balas.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. ¡Observe las citas $1en la rmdeclaración!
  2. La -dverificación también fallará si $1está vacía, por lo que son dos verificaciones en una.
  3. Evité las expresiones regulares por una razón. Si debe usar =~en bash, debe poner la expresión regular en una variable. En cualquier caso, los globs como el mío son siempre preferibles y compatibles con muchas más versiones de bash.
lhunath
fuente
1
Entonces, ¿la pieza globbing $1 != *[^0-9]*bash es específica?
Grinch
15

Yo usaría bash's [[:

if [[ ! ("$#" == 1 && $1 =~ ^[0-9]+$ && -d $1) ]]; then 
    echo 'Please pass a number that corresponds to a directory'
    exit 1
fi

Encontré que estas preguntas frecuentes son una buena fuente de información.

Johannes Schaub - litb
fuente
13

No tan a prueba de balas como la respuesta anterior, pero sigue siendo eficaz:

#!/bin/bash
if [ "$1" = "" ]
then
  echo "Usage: $0 <id number to be cleaned up>"
  exit
fi

# rm commands go here
Factura de la caldera
fuente
9

La página de manual de test ( man test) proporciona todos los operadores disponibles que puede usar como operadores booleanos en bash. Use esos indicadores al comienzo de su script (o funciones) para la validación de entrada como lo haría en cualquier otro lenguaje de programación. Por ejemplo:

if [ -z $1 ] ; then
  echo "First parameter needed!" && exit 1;
fi

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
whaley
fuente
8

Use '-z' para probar cadenas vacías y '-d para verificar directorios.

if [[ -z "$@" ]]; then
    echo >&2 "You must supply an argument!"
    exit 1
elif [[ ! -d "$@" ]]; then
    echo >&2 "$@ is not a valid directory!"
    exit 1
fi
armas
fuente
2
¿Por qué necesitas el doble [[]]?
vehomzzz
5

Puede validar los puntos ayb de forma compacta haciendo algo como lo siguiente:

#!/bin/sh
MYVAL=$(echo ${1} | awk '/^[0-9]+$/')
MYVAL=${MYVAL:?"Usage - testparms <number>"}
echo ${MYVAL}

Lo que nos da ...

$ ./testparams.sh 
Usage - testparms <number>

$ ./testparams.sh 1234
1234

$ ./testparams.sh abcd
Usage - testparms <number>

Este método debería funcionar bien en sh.

MattK
fuente
2

validación de un argumento de línea Bash, con y sin validación de directorio

Aquí hay algunos métodos que me han funcionado. Puede usarlos en el espacio de nombres de script global (si está en el espacio de nombres global, no puede hacer referencia a las variables integradas de la función)

un forro rápido y sucio

: ${1?' You forgot to supply a directory name'}

salida:

./my_script: line 279: 1: You forgot to supply a directory name

Fancier: nombre y uso de la función de suministro

${1? ERROR Function: ${FUNCNAME[0]}() Usage: " ${FUNCNAME[0]} directory_name"}

salida:

./my_script: line 288: 1:  ERROR Function: deleteFolders() Usage:  deleteFolders directory_name

Agregue una lógica de validación compleja sin saturar su función actual

Agregue la siguiente línea dentro de la función o secuencia de comandos que recibe el argumento.

: ${1?'forgot to supply a directory name'} && validate $1 || die 'Please supply a valid directory'

Luego puede crear una función de validación que haga algo como

validate() {

    #validate input and  & return 1 if failed, 0 if succeed
    if [[ ! -d "$1" ]]; then
        return 1
    fi
}

y una función de troquel que aborta el script en caso de falla

die() { echo "$*" 1>&2 ; exit 1; }

Para argumentos adicionales, simplemente agregue una línea adicional, replicando el formato.

: ${1?' You forgot to supply the first argument'}
: ${2?' You forgot to supply the second argument'}
AndrewD
fuente
1

Publicación antigua, pero pensé que podría contribuir de todos modos.

Podría decirse que un script no es necesario y, con cierta tolerancia a los comodines, se podría ejecutar desde la línea de comandos.

  1. salvaje en cualquier lugar que coincida. Eliminemos cualquier aparición de sub "carpeta"

    $ rm -rf ~/*/folder/*
  2. Shell repitió. Eliminemos las carpetas previas y posteriores específicas con una línea

    $ rm -rf ~/foo{1,2,3}/folder/{ab,cd,ef}
  3. Shell iterado + var (probado en BASH).

    $ var=bar rm -rf ~/foo{1,2,3}/${var}/{ab,cd,ef}
Xarses
fuente