Script Shell - Como imprimir o eco se duas condições forem atendidas? (Apenas números, exatas 2 condições)

Script Shell - Como imprimir o eco se duas condições forem atendidas? (Apenas números, exatas 2 condições)

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: $1e $2.
  • [é um comando ( [ whatever ]é equivalente a test whatever). Pode ser um shell embutido, mas sintaticamente é como lsou man. 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 ^5se 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.
  • -eqrequer 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 $1a expansão para várias palavras (e separadamente $2e 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 [(e test) seja exigido pelo POSIX, [[não é.

    Coisa importante: [[não é um comando como lsou [. É 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é executado barse for foobem-sucedido. O segundo [[foi testado apenas se $#expandido para 2, portanto teve que ser bem-sucedido porque 2tem 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 $1expandir 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 $1expandir 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 onde 9precede 0ou onde 2está além 0-9. A maneira mais geral de corresponder a um dígito é [[:digit:]]. Veresse.

    Por outro lado, você pode querer usar $1e/ou $2com 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 respeito 1e100?

informação relacionada