Aqui está uma maneira de contar quantos 4, 5 ou 6 aparecem em seu número e ter bash
executar uma instrução com base no fato de o resultado ser dois ou não:
$ con1=1457
$ a=${con1//[^456]/}; [ ${#a} -eq 2 ] && echo Yes
Yes
Sou novo em scripts e preciso de ajuda. Apreciará suas respostas.
Eu tenho essa atribuição, que é encontrar a soma de todos os números de cinco dígitos (no intervalo 10000 - 99999) contendo exatamente dois dos seguintes conjuntos de dígitos: {4, 5, 6}. Estes podem repetir dentro do mesmo número e, se for o caso, contar uma vez para cada ocorrência.
Alguns exemplos de números correspondentes são 42057, 74638 e 89515. Eu só tenho esse pequeno pedaço de código.
#! /bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
do
## UNKNOWN COMMANDS
done
Aqui está uma maneira de contar quantos 4, 5 ou 6 aparecem em seu número e ter bash
executar uma instrução com base no fato de o resultado ser dois ou não:
$ con1=1457
$ a=${con1//[^456]/}; [ ${#a} -eq 2 ] && echo Yes
Yes
Suponho que você tenha que fazer isso em puro script Bash, mas traduzir o algoritmo de John1024 para o awk dá uma aceleração considerável :
awk 'BEGIN{k=0;for(i=10000;i<100000;i++){j=i;if(gsub(/[456]/,"",j)==2)k+=i};print k}'
Isso é executado em menos de 1/20 do tempo que a versão bash leva; também é um pouco mais rápido que uma versão em Python que usa o método str.count()
do Python.
Sempre que tenho um projeto como este, gosto de abordá-lo em etapas. A primeira coisa que eu gosto de fazer é adicionar um echo
no interior do loop e depois executá-lo, para ter certeza de que o loop está me dando o que eu quero.
#! /bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
do
echo $CON1
done
Agora, quando eu executar, usarei head -5
para mostrar apenas as 5 primeiras linhas geradas.
$ ./cmd.bash | head -5
10000
10001
10002
10003
10004
OK, para que fique bem, verifique o final assim:
$ ./cmd.bash | tail -5
99995
99996
99997
99998
99999
Isso parece bom também. Então, agora vamos descobrir algumas maneiras pelas quais poderíamos abordar a próxima etapa de identificação de números com 2 dígitos do conjunto {4,5,6}. Meu primeiro instinto aqui é ir para grep
. Também existem métodos para fazer isso puramente no Bash, mas eu gosto de usar as várias ferramentas, grep
, awk
e sed
para fazer esses tipos de coisas, principalmente porque é assim que minha mente funciona.
Então, como podemos grep
linhas que contêm 2 dígitos do conjunto {4,5,6}? Para isso, você pode usar uma notação de conjunto, que é escrita assim na regex, [456]
. Você também pode especificar quantos dígitos deseja corresponder a partir desse conjunto. Está escrito assim:
[456]{#}
Onde #
é um número ou intervalo de números. Se quiséssemos 3, escreveríamos [456]{3}
. Se quiséssemos de 2 a 5 dígitos, escreveríamos [456]{2,5}
. Se você quisesse 3 ou mais, [456] {3,} '.
Então, para o seu cenário, é [456]{2}
. Para usar uma regex em grep
, sua versão específica de grep
precisa suportar o -E
swtich. Isso geralmente está disponível na maioria dos grep
'padrão.
$ echo "45123" | grep -E "[456]{2}"
45123
Parece funcionar, mas se dermos números com 3, começaremos a ver um problema:
$ echo "45423" | grep -E "[456]{2}"
45423
Essa correspondência também. Isso ocorre porque grep
não tem noção do fato de que esses são dígitos de uma string. É burro. Nós dissemos para nos dizer se a série de caracteres em nossa string é de um conjunto e se há 2 deles e se há 2 dígitos na string 45423
.
Ele também falha nessas sequências:
$ echo "41412" | grep -E "[456]{2}"
$
Então este método é utilizável? É se mudarmos um pouco as táticas, mas teremos que rejigar o regex.
$ echo -e "41123\n44123\n44423\n41423" | grep -E "[^456]*([456][^456]*){2}"
44123
44423
41423
O texto acima apresenta 4 tipos de strings. O echo -e "41123\n44123\n44423\n41423"
apenas imprime 4 dos números do nosso intervalo.
$ echo -e "41123\n44123\n44423\n41423"
41123
44123
44423
41423
Como funciona esse regex? Ele configura um padrão de expressão regular de zero ou mais "não [456]" seguido por 1 ou mais [456] ou zero ou mais caracteres "não [456]", procurando por 2 ocorrências do último.
Agora, fazemos uma pequena montagem no seu script.
for (( CON1=10000; CON1<=99999; CON1++ )) ;
do
if echo $CON1 | grep -q -E "[^456]*([456][^456]*){2}"; then
echo $CON1
fi
done
Usando nosso head
& % truque de tail
de cima podemos ver que está funcionando:
$ ./cmd.bash | head -5
10044
10045
10046
10054
10055
$ ./cmd.bash | tail -5
99955
99956
99964
99965
99966
Mas este método prova ser lento. O problema é que grep
. É caro, e estamos rodando 'grep 1 time, por iteração através do loop, então é ~ 80k vezes!
Para melhorar, poderíamos mover nosso comando grep
para fora do loop e executá-lo 1 vez depois que a lista fosse gerada, usando a versão original do script que acabou de reproduzir os números:
$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}"
NOTA: Poderíamos eliminar completamente o loop for e usar a ferramenta de linha de comando, seq
. Isso gerará a mesma sequência de números, seq 10000 99999
.
Uma maneira sofisticada de fazer isso seria usar a sequência de números do comando acima e, em seguida, canalizá-la para o comando paste
, que inseria um +
entre cada número e, em seguida, executaria essa saída no calculadora de linha de comando, bc
.
$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+"
10044+10045+10046+10054+10055+10056+10064+10065+10066+10144+10145+...
$ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+" | bc
2409327540
Mas essa é uma maneira completamente diferente de resolver esse problema, então vamos voltar para o loop for
.
Portanto, precisamos de algum método para testar se um dígito tem exatamente dois dígitos no Bash, mas não é tão caro quanto chamar grep
80k vezes. As versões modernas do Bash incluem a capacidade de corresponder usando o operador =~
, que pode fazer correspondência semelhante como grep
. Vamos dar uma olhada nisso em seguida.
#!/bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
if [[ $CON1 =~ [^456]*([456][^456]*){2} ]]; then
echo $CON1
fi
done
A execução disso parece fazer exatamente o que queremos.
$ ./cmd1.bash | head -5
10044
10045
10046
10054
10055
$ ./cmd1.bash | tail -5
99955
99956
99964
99965
99966
Verificar mostra que funciona com 41511 agora:
$ ./cmd1.bash | grep 41511
41511
Tags bash scripting shell-script