Executar através de duas seqüências em um loop

8

Estou tentando executar duas seqüências no mesmo loop do meu shell, como abaixo:

#!/bin/bash
for i in (1..15) and (20..25) ;
do
     echo $i
     ......
     .....other process
done

alguma ideia de como posso conseguir isso?

    
por HISI 24.10.2017 / 14:52

4 respostas

10

Você só precisa de expansão de chaves para isso

$ for n in {1..3} {200..203}; do echo $n; done
1
2
3
200
201
202
203

Podemos passar uma lista para for ( for i in x y z; do stuff "$i"; done ).

Então, chaves { } pegam o shell para expandir suas seqüências em uma lista. Você só precisa colocar um espaço entre eles, já que o shell divide as listas de argumentos sobre eles.

    
por Zanna 24.10.2017 / 15:18
6

Como alternativa, podemos usar seq ( imprimir uma seqüência de números ), aqui estão dois exemplos equivalentes:

for i in 'seq 1 3' 'seq 101 103'; do echo $i; done
for i in $(seq 1 3) $(seq 101 103); do echo $i; done

Se for um script, para tarefas repetitivas, você pode usar funções:

#!/bin/bash
my_function() { echo ""; }
for i in {1..3}; do my_function "$i"; done
for i in {101..103}; do my_function "$i"; done
#!/bin/bash
my_function() { for i in 'seq  '; do echo "$i"; done; }
my_function "1" "3"
my_function "101" "103"
    
por pa4080 24.10.2017 / 15:28
4

A resposta de Zanna e A resposta do pa4080 é boa e eu provavelmente vou com uma delas na maioria das circunstâncias. Talvez seja óbvio, mas por uma questão de completude, vou dizer de qualquer maneira: você pode carregar cada valor em uma matriz e, em seguida, fazer um loop sobre a matriz. Por exemplo:

the_array=( 1 2 3 4 5 6 7 8 9 10 20 21 22 23 24 25 )
for i in "${the_array[@]}";
do
    echo $i
done
    
por GreenMatt 24.10.2017 / 17:15
3

Looping sem loop

A resposta de Zanna é absolutamente correta e adequada para o bash, mas podemos melhorar ainda mais sem utilizar um loop.

printf "%d\n"  {1..15} {20..25}

Comportamento de printf é tal que se o número de ARGUMENTS for maior que os controles de formato em 'FORMAT STRING' , então printf dividirá todo ARGUMENTS em partes iguais e continuará ajustando-as à cadeia de formato e acabou até ficar sem ARGUMENTS list.

Se estamos nos esforçando para portabilidade, podemos utilizar printf "%d\n" $(seq 1 15) $(seq 20 25)

Vamos levar isso mais longe e mais divertido. Digamos que queremos realizar uma ação em vez de apenas imprimir números. Para criar arquivos fora dessa sequência de números, poderíamos facilmente fazer touch {1..15}.txt {20..25}.txt . E se quisermos que várias coisas aconteçam? Nós também poderíamos fazer algo assim:

$ printf "%d\n" {1..15} {20..25} | xargs -I % bash -c 'touch ".txt"; stat ".txt"' sh %

Ou se quisermos tornar o estilo antigo:

printf "%d\n" {1..15} {20..25} | while read -r line; do 
    touch "$line".txt;
    stat "$line".txt;
    rm "$line".txt; 
done

Alternativa portátil, mas detalhada

Se quisermos criar uma solução de script que funcione com shells que não tenham uma expansão de chaves (que é o que usa o {1..15} {20..25} ), podemos escrever um loop while simples:

#!/bin/sh
start=
jump=
new_start=
end=

i=$start
while [ $i -le $jump ]
do
    printf "%d\n" "$i"
    i=$((i+1))
    if [ $i -eq $jump ] && ! [ $i -eq $end ];then
        printf "%d\n" "$i"
        i=$new_start
        jump=$end
    fi
done

Claro que esta solução é mais detalhada, algumas coisas podem ser encurtadas, mas funciona. Testado com ksh , dash , mksh e, claro, bash .

Loop de estilo C de Bash

Mas se quiséssemos criar um loop bash-specific (por qualquer motivo, talvez não apenas imprimindo, mas também fazendo algo com esses números), também podemos fazer isso (basicamente a versão C-loop da solução portátil):

last=15; for (( i=1; i<=last;i++ )); do printf "%d\n" "$i"; [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} ;done

Ou em formato mais legível:

last=15
for (( i=1; i<=last;i++ )); 
do 
    printf "%d\n" "$i"
    [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} 
done

Comparação de desempenho de diferentes abordagens de looping

bash-4.3$ time bash -c 'printf "%d\n" {0..50000}>/dev/null'

real    0m0.196s
user    0m0.124s
sys 0m0.028s
bash-4.3$ time bash -c 'for i in {1..50000}; do echo $i > /dev/null; done'

real    0m1.819s
user    0m1.328s
sys 0m0.476s
bash-4.3$ time bash -c ' i=0;while [ $i -le 50000 ]; do echo $i>/dev/null; i=$((i+1)); done'

real    0m3.069s
user    0m2.544s
sys 0m0.500s
bash-4.3$ time bash -c 'for i in $(seq 1 50000); do printf "%d\n" > /dev/null; done'

real    0m1.879s
user    0m1.344s
sys 0m0.520s

Alternativa não shell

Só porque podemos aqui está a solução Python

$ python3 -c 'print("\n".join([str(i) for i in (*range(1,16),*range(20,26))]))'

Ou com um pouco de shell:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
    
por Sergiy Kolodyazhnyy 26.10.2017 / 21:41