Validando parâmetros para um script Bash

95

Eu criei um básico para ajudar a automatizar o processo de remoção de várias pastas à medida que se tornam desnecessárias.

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

Isso é evocado assim:

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

O problema é que, se você se esquecer de digitar id-number (como acabei de fazer) , isso poderá potencialmente excluir muitas coisas que você realmente não deseja excluir.

Existe uma maneira de adicionar qualquer forma de validação aos parâmetros da linha de comando? No meu caso, seria bom verificar se a) existe um parâmetro, b) é numérico ec) essa pasta existe; antes de continuar com o script.

nickf
fonte

Respostas:

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 : Eu perdi a parte sobre verificar se os diretórios existem primeiro, então eu adicionei isso, concluindo o script. Além disso, tratou das questões levantadas nos comentários; corrigiu a expressão regular, mudou de ==para eq.

Este deve ser um script compatível com POSIX portátil, tanto quanto posso dizer; ele não usa nenhum bashism, o que é realmente importante porque /bin/shno Ubuntu atualmente dash, não bash.

Brian Campbell
fonte
lembre-se de definir + e e usar '-eq' em vez de '==' para comparações de inteiros
armas
Alterou para -eq; o que set + e compra você aqui?
Brian Campbell,
Encontrei duas coisas na minha resposta que você também pode querer corrigir na sua: primeiro, o SO hilighter enlouquece por $ # (tratando-o como um comentário). fiz "$ #" para consertar. segundo, a regex também corresponde a "foo123bar". eu consertei fazendo ^ [0-9] + $. você também pode corrigi-lo usando a opção -x do grep
Johannes Schaub - litb
1
@ojblass Eu estava faltando um dos testes que ele estava perguntando. Adicionar isso significava também adicionar seus diretórios para testar, o que expandiu significativamente o tamanho da resposta, já que eles não cabem em uma linha. Você pode sugerir uma forma mais compacta de testar a existência de cada diretório?
Brian Campbell
1
de acordo com a resposta-comentário de @Morten Nielsen abaixo, o grep '$ [0-9] + ^' parece realmente estranho. Não deveria ser '^ [0-9] + $'?
martin jakubik
21

A shsolução por Brian Campbell, embora nobre e bem executada, tem alguns problemas, então pensei em fornecer minha própria bashsolução.

Os problemas com shaquele:

  • O til em ~/foonão se expande para seu diretório de origem dentro de heredocs. E nem quando é lido pela readdeclaração ou citado na rmdeclaração. O que significa que você obterá No such file or directoryerros.
  • Fazer bifurcações grepe coisas assim para operações básicas é uma tolice. Especialmente quando você está usando uma casca de baixa qualidade para evitar o peso "pesado" do bash.
  • Também notei alguns problemas de citação, por exemplo, em torno de uma expansão de parâmetro em seu echo.
  • Embora raro, a solução não pode lidar com nomes de arquivos que contenham novas linhas. (Quase nenhuma solução shpode lidar com eles - e é por isso que quase sempre prefiro bash, é muito mais à prova de balas e mais difícil de explorar quando bem usado).

Embora, sim, usar /bin/shpara o seu hashbang signifique que você deve evitar bashismos a todo custo, você pode usar todos os bashismos que quiser, mesmo no Ubuntu ou qualquer outra coisa, quando for honesto e colocar#!/bin/bash no topo.

Portanto, aqui está uma bashsolução que é menor, mais limpa, mais transparente, provavelmente "mais rápida" e mais à prova de balas.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Observe as aspas $1norm declaração!
  2. A -dverificação também falhará se$1 estiver vazia, ou seja, duas verificações em uma.
  3. Evitei expressões regulares por um motivo. Se você deve usar =~no bash, deve colocar a expressão regular em uma variável. Em qualquer caso, globs como o meu são sempre preferíveis e suportados em muito mais versões do bash.
lhunath
fonte
1
Então, o $1 != *[^0-9]*bash da peça globbing é específico?
grinch
15

Eu usaria o bash's [[:

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

Achei este faq uma boa fonte de informação.

Johannes Schaub - litb
fonte
13

Não tão à prova de balas quanto a resposta acima, porém ainda eficaz:

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

# rm commands go here
Boiler Bill
fonte
11

Use o set -uque fará com que qualquer referência de argumento não definida falhe imediatamente no script.

Por favor, veja o artigo: Escrevendo Scripts Robust Bash Shell - David Pashley.com .

shmichael
fonte
Em muitos casos, essa é uma solução fácil e excelente e também funciona para funções. Obrigado!
Manfred Moser,
9

A página man para test ( man test) fornece todos os operadores disponíveis que você pode usar como operadores booleanos no bash. Use esses sinalizadores no início do seu script (ou funções) para validação de entrada, assim como você faria em qualquer outra linguagem de programação. Por exemplo:

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

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

Use '-z' para testar strings vazias e '-d para verificar diretórios.

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
fonte
2
por que você precisa do dobro [[]]?
vehomzzz
5

Você pode validar os pontos aeb compactamente fazendo algo como o seguinte:

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

O que nos dá ...

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

$ ./testparams.sh 1234
1234

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

Este método deve funcionar bem em sh.

MattK
fonte
2

validação de argumento de um liner Bash, com e sem validação de diretório

Aqui estão alguns métodos que funcionaram para mim. Você pode usá-los no namespace de script global (se estiver no namespace global, você não pode fazer referência às variáveis ​​internas da função)

um forro rápido e sujo

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

resultado:

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

Fancier - fornece o nome da função e uso

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

resultado:

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

Adicione lógica de validação complexa sem bagunçar sua função atual

Adicione a seguinte linha na função ou script que recebe o argumento.

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

Você pode então criar uma função de validação que faz algo como

validate() {

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

e uma função die que aborta o script em caso de falha

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

Para argumentos adicionais, basta adicionar uma linha adicional, replicando o formato.

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

Postagem antiga, mas achei que poderia contribuir de qualquer maneira.

Um script sem dúvida não é necessário e com alguma tolerância a curingas pode ser executado a partir da linha de comando.

  1. selvagem em qualquer lugar correspondente. Vamos remover qualquer ocorrência da sub "pasta"

    $ rm -rf ~/*/folder/*
  2. Shell iterado. Vamos remover as pastas específicas pré e pós com uma linha

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

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