Sou novo em scripts de shell e estou tentando aprender o básico. Estou iniciando um script usando o terminal como "./scriptgoeshere.sh Argument1 Argument2".
Quero que o script imprima algum eco, "Hello World" é provavelmente o melhor para usar, mas só quero imprimir hello world se houver: Exatamente dois argumentos, e esses argumentos devem consistir apenas em números. Por exemplo:
./scriptgoeshere.sh 1523 9643 ./scriptgoeshere.sh 7 96 Deve funcionar, entretanto; ./scriptgoeshere.sh 594 arg NÃO deveria funcionar, este é o script que tenho no momento, mas não funciona, tentei descobrir isso o dia inteiro, mas não consigo acertar, até tentei if/elif/else onde pedi para verificar se há 2 entradas na parte if e se ambos são números em elif antes de continuar.
#!/bin/bash
if [ "$#" -eq 2 ] && [ "$@" -eq ^[0-9] ];
then
i=0
while [ $i -lt 3 ]
do
echo "Hello World!"
sleep 5
i=$((i+1))
done
else
echo "Need two arguments and only numbers will be accepted, such as ./scriptgoeshere 153 251"
fi
Ele bloqueia o loop hello world até que eu coloque 2 argumentos, mas não importa se são números ou texto, e recebo um erro: "./scriptgoeshere.sh: line 2: [: ^[0-9]: expressão inteira esperada". Recebo esse erro mesmo se escrever ./scriptgoeshere.sh 123 123 no terminal para iniciá-lo. Prefiro não resolver isso por variáveis, pois quero descobrir os argumentos ao iniciar o script real.
Isso está dando cabo da minha cabeça!
Foi com isso que comecei para verificar se há exatamente 2 argumentos e funciona, só quero adicionar uma pequena correção para que esses 2 argumentos tenham que ser números. Não entendo por que parece ser tão difícil.
#!/bin/bash
if [ "$#" -eq 2 ];
then
i=0
while [ $i -lt 3 ]
do
echo "Hello World!"
sleep 1
i=$((i+1))
done
else
echo "Need two argument, only numbers accepted"
fi
Editar: eu resolvi isso. Eu trabalhei tão perto. Só precisei adicionar [] extra à parte numérica da string if. Alguém sabe por que precisa ser escrito como [[ "$@" -eq ^[0-9] ]] enquanto a primeira parte pode ser escrita com apenas um [] ?
Responder1
A princípio [ "$#" -eq ^[0-9] ]
ou [ "$@" -eq ^[0-9] ]
(a pergunta foi editada enquanto eu redigia minha resposta…). Pelo contexto, acho que você deseja testar se o parâmetro é um número.
$#
é sempre um número, o número de parâmetros posicionais em decimal. Você deseja testar dois parâmetros posicionais:$1
e$2
.[
é um comando ([ whatever ]
é equivalente atest whatever
). Pode ser um shell embutido, mas sintaticamente é comols
ouman
. As palavras que se seguem (incluindo]
) são analisadas pelo shell como qualquer palavra que segue um comando: o shell as expande (por exemplo,$#
torna-se um número), remove as aspas; eventualmente eles se tornam parâmetros para[
. Uma dessas expansões é a expansão de nome de arquivo, onde vários padrões são comparados com nomes de arquivos existentes (expansão de nome de arquivo, globbing). Sem aspas^[0-9]
pode ser expandido para^2 ^5
se esses arquivos existirem. Se não houver correspondência ou se você desabilitar o recurso^[0-9]
chegará[
(como um de seus argumentos) literalmente. Parece um padrão, mas[
não oferece suporte à correspondência de padrões.-eq
requer que o argumento anterior e o próximo sejam números inteiros.$#
expande para um número inteiro com certeza. Não há como^[0-9]
expandir para um número neste contexto.Aspas duplas
$@
são especiais, podem se expandir para várias palavras ("campos" em termos de POSIX), apesar de estarem entre aspas. Normalmente citamos para evitar a divisão de palavras:"$foo"
expande para uma palavra mesmo se o valor contiver espaços e tal."$@"
expande como"$1" "$2" …
, suas aspas duplas impedem$1
a expansão para várias palavras (e separadamente$2
e assim por diante). Neste fragmento:[ "$#" -eq 2 ] && [ "$@" -eq ^[0-9] ]
o segundo
[
é executado se o primeiro for bem-sucedido (é assim que&&
funciona), ou seja, se houver exatamente dois parâmetros posicionais. Nesse caso"$@"
é equivalente a"$1" "$2"
e o segundo teste torna-se[ "$1" "$2" -eq ^[0-9] ]
Isso não faz sentido.
[
pode reclamar de maneiras diferentes dependendo dos valores reais dos parâmetros e da expansão de^[0-9]
, ainda assim é lixo.
Agora sobre [[ "$#" = [0-9] ]]
.
Nota: este trecho estava na primeira revisão da pergunta. A pergunta foi editada enquanto eu redigia minha resposta. Decidi manter este fragmento porque [[
pode ser útil de qualquer maneira.
- Novamente:
$#
é sempre um número. [[
não é equivalente a[
. Qualquer teste sensato que você possa fazer[
pode ser reescrito[[
(embora você não deva fazer isso sem pensar, apenas substituindo[
por[[
). Existem testes adicionais[[
que você pode fazer, é mais poderoso. Embora[
(etest
) seja exigido pelo POSIX,[[
não é.Coisa importante:
[[
não é um comando comols
ou[
. É uma palavra-chave, muda a maneira como o shell analisa as palavras a seguir. Tudo entre[[
e]]
é analisado de forma diferente.Se o operador for
=
, o próximo argumento será um padrão. Alguns caracteres/sintaxes (por exemplo,*
ou[0-9]
), quando não estão entre aspas, são comparados ao argumento anterior=
. É como a expansão do nome de arquivo, mas não contra nomes de arquivos existentes. Seu código testado se (expandido)$#
tem exatamente um dígito. Todo o teste foi[[ "$#" -eq 2 ]] && [[ "$#" = [0-9] ]]
foo && bar
é executadobar
se forfoo
bem-sucedido. O segundo[[
foi testado apenas se$#
expandido para2
, portanto teve que ser bem-sucedido porque2
tem exatamente um dígito. Neste caso, o segundo[[
não muda nada.
Digamos que você percebeu seu erro e testou se $1
é um número.
- Como acima,
[[ "$1" = [0-9] ]]
retorna sucesso se$1
expandir para exatamente um dígito. [[ "$1" = [0-9][0-9] ]]
– exatamente dois dígitos.[[ "$1" = [0-9]* ]]
– algo que começa com um dígito.
Para detectar qualquer número de dígitos que você precisa =~
. Este operador interno [[ ]]
é um pouco semelhante a =
, mas agora o padrão é uma expressão regular estendida. Porém, não é necessário corresponder a string inteira à esquerda do operador, uma substring é suficiente (portanto, ^
são $
necessárias âncoras no seu caso):
[[ "$1" =~ [0-9] ]]
retorna sucesso se$1
expandir para uma string contendo um dígito.[[ "$1" =~ [0-9][0-9] ]]
– … contendo dois dígitos próximos um do outro.[[ "$1" =~ ^[0-9]$ ]]
– uma string que tem exatamente um dígito.[[ "$1" =~ ^[0-9]*$ ]]
– zero ou mais dígitos.[[ "$1" =~ ^[0-9][0-9]*$ ]]
– um ou mais dígitos.[[ "$1" =~ ^[0-9]+$ ]]
– um ou mais dígitos.
[[ "$1" =~ ^[0-9]+$ ]]
e [[ "$2" =~ ^[0-9]+$ ]]
são os testes que você pode querer.Para permitir a liderança -
modifique-os assim: [[ "$1" =~ ^-?[0-9]+$ ]]
. Todo o fragmento de teste pode ser
[ "$#" -eq 2 ] && `[[ "$1" =~ ^-?[0-9]+$ ]]` && `[[ "$2" =~ ^-?[0-9]+$ ]]`
Variáveis/parâmetros internos [[ ]]
não podem ser colocados entre aspas duplas e seus valores expandidos não sofrerão divisão e globbing de palavras. Então você pode escrever [[ $1 =~ ^-?[0-9]+$ ]]
e é seguro. É uma exceção; em geral, você deve colocar aspas duplas nas variáveis. Aqui aspas duplas não atrapalham nada, então aconselho citar mesmo assim para se acostumar com essa boa prática geral.
Você afirma que resolveu o problema usando [[ "$@" -eq ^[0-9] ]]
. Desculpe, não acredito em você. Se houver exatamente dois parâmetros posicionais e eles forem números, não vejo como isso pode ser sintaticamente correto, muito menos testar o que você deseja. Em outros casos… ainda não vejo jeito.
… usando [[ "$@" =~ ^[0-9] ]]
(do seu comentário, incoerente com a pergunta). Esta não é uma solução adequada. Meus testes indicam que "$@"
inside [[ ]]
se comporta como sem aspas, $@
tratado como uma palavra, sem divisão e globbing de palavras. Isso é muito semelhante ao comportamento das variáveis regulares dentro de [[ ]]
, embora não fosse óbvio para mim de antemão, porque eu sei "$@"
que não é como as variáveis regulares.
Nota lateral: por esse motivo nunca quis usar "$@"
inside [[ ]]
. Não consigo imaginar um cenário em que seja a coisa certa.
Quando existem exatamente dois parâmetros posicionais, sua "solução" se torna [[ "$1 $2" =~ ^[0-9] ]]
. É sintaticamente válido e verifica se o primeiro caractere do primeiro argumento do seu script é um dígito. Só isso. Execute ./scriptgoeshere.sh 0h well
, os argumentos passarão no teste.
Notas:
[0-9]
(como padrão global ou expressão regular) depende da sua localidade atual. Provavelmente funcionará conforme o esperado, mas tecnicamente é possível ter um local onde9
precede0
ou onde2
está além0-9
. A maneira mais geral de corresponder a um dígito é[[:digit:]]
. Veresse.Por outro lado, você pode querer usar
$1
e/ou$2
com aritmética de shell ($((…))
),[ "$1" -ge 50 ]
ou algo semelhante. Então você precisa apenas de algarismos arábicos. Mesmo que[[:digit:]]
os inclua em sua localidade (provavelmente inclui), também pode incluir outros dígitos. Considere[0123456789]
; é explícito e não depende da localidade.Nem toda string correspondente
^-?[0-9]+$
é considerada inteira do ponto de vista de[
e/ou[[
(por exemplo, quando você faz[ "$1" -eq 0 ]
). Cada ferramenta possui um intervalo de números inteiros que pode manipular. Da mesma forma, a aritmética do shell pode produzir resultados matematicamente errados se você alimentá-la com uma sequência de dígitos que descreve um número inteiro fora do intervalo.Matematicamente
1.00
é um número inteiro. Algumas ferramentas podem aceitá-lo como número inteiro, outras não. A respeito1e100
?