Jalankan melalui dua urutan dalam satu lingkaran

8

Saya mencoba menjalankan dua urutan dalam loop yang sama di shell saya seperti di bawah ini:

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

ada ide bagaimana saya bisa mencapai ini?

HISI
sumber
@Zanna - pikiran pertama saya adalah bahwa boolean "dan" adalah eksklusif, artinya hasilnya adalah angka yang ada pada kedua set; yang tidak ada dalam kasus ini. Apakah ada inklusif "dan"?
Ravery
1
@ setiap saya menempatkan "dan" hanya untuk menjelaskan apa yang saya cari
HISI
2
@YassineSihi - Buat catatan. Banyak programmer baru tersandung pada titik ini sampai mereka dapat melatih otak mereka, karena bahasa lisan menggunakan "dan" secara inklusif tetapi logis "dan" adalah eksklusif di sebagian besar bahasa pemrograman.
Ravery

Jawaban:

10

Anda hanya perlu ekspansi penjepit untuk itu

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

Kami dapat meneruskan daftar ke for( ).for i in x y z; do stuff "$i"; done

Jadi di sini, kawat gigi { }mendapatkan shell untuk memperluas urutan Anda ke dalam daftar. Anda hanya perlu menempatkan spasi di antara mereka, karena shell membagi daftar argumen pada mereka.

Zanna
sumber
Ya, kawat gigi. . . Dan Anda bahkan tidak perlu loop untuk itu ^ _0
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Saya pikir mereka tidak benar-benar hanya ingin echonomor
Zanna
ya, jika mereka ingin melakukan beberapa tindakan, seperti touchfile, mereka bisa melakukannya touch {1..15}.txt {20..25}.txt, tidak perlu loop di sini. Tapi tentu saja jika beberapa tindakan pada nomor yang sama - OK, itu bisa menggunakan perulangan.
Sergiy Kolodyazhnyy
6

Atau kita dapat menggunakan seq( mencetak urutan angka ), berikut adalah dua contoh yang setara:

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

Jika ini adalah skrip, untuk tugas berulang, Anda dapat menggunakan fungsi:

#!/bin/bash
my_function() { echo "$1"; }
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 $1 $2`; do echo "$i"; done; }
my_function "1" "3"
my_function "101" "103"
pa4080
sumber
4

Jawaban Zanna dan jawaban pa4080 sama-sama baik dan saya mungkin akan pergi dengan salah satu dari mereka dalam banyak situasi. Mungkin tidak perlu dikatakan lagi, tetapi demi kelengkapan, saya tetap akan mengatakannya: Anda dapat memuat setiap nilai ke dalam array dan kemudian mengulangi array. Sebagai contoh:

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
GreenMatt
sumber
@SergiyKolodyazhnyy: Terima kasih atas umpan baliknya. Saya cukup tua sehingga saya diajarkan, dan biasanya masih melakukannya pada kesempatan yang jarang saya menulis skrip shell. Namun, saya telah memperbarui jawaban untuk menggunakan array.
GreenMatt
Sangat baik ! Selamat menulis!
Sergiy Kolodyazhnyy
3

Looping tanpa loop

Jawaban Zanna benar-benar benar dan cocok untuk bash, tetapi kita dapat meningkatkannya lebih banyak lagi tanpa menggunakan loop.

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

Perilaku printfadalah sedemikian rupa sehingga jika jumlah ARGUMENTSlebih besar dari kontrol format 'FORMAT STRING', maka printfakan membagi semua ARGUMENTS menjadi potongan yang sama dan terus menyesuaikan mereka ke string format berulang-ulang sampai kehabisan ARGUMENTSdaftar.

Jika kita berjuang untuk portabilitas, kita dapat memanfaatkan printf "%d\n" $(seq 1 15) $(seq 20 25)sebagai gantinya

Mari kita lanjutkan ini dan lebih menyenangkan. Katakanlah kita ingin melakukan tindakan daripada hanya mencetak angka. Untuk membuat file dari urutan angka itu, kita bisa dengan mudah melakukannya touch {1..15}.txt {20..25}.txt. Bagaimana jika kita ingin banyak hal terjadi? Kita juga bisa melakukan sesuatu seperti ini:

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

Atau jika kita ingin menjadikannya gaya sekolah lama:

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

Alternatif yang portable tapi verbose

Jika kita ingin membuat solusi skrip yang bekerja dengan shell yang tidak memiliki ekspansi brace (yang {1..15} {20..25}bergantung pada), kita dapat menulis loop sementara sederhana:

#!/bin/sh
start=$1
jump=$2
new_start=$3
end=$4

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

Tentu saja solusi ini lebih bertele-tele, beberapa hal bisa dipersingkat, tetapi berhasil. Diuji dengan ksh, dash, mksh, dan tentu saja bash.


Bash C-style loop

Tetapi jika kita ingin membuat loop bash-spesifik (untuk alasan apa pun, mungkin tidak hanya mencetak tetapi juga melakukan sesuatu dengan angka-angka itu), kita juga dapat melakukan ini (pada dasarnya versi C-loop dari solusi portabel):

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

Atau dalam format yang lebih mudah dibaca:

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

Perbandingan kinerja dari berbagai pendekatan pengulangan

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

Alternatif non-shell

Hanya karena kita dapat inilah solusi Python

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

Atau dengan sedikit cangkang:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
Sergiy Kolodyazhnyy
sumber
1
Saya baru saja menguji touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080
1
@ pa4080 sebenarnya untuk bashAnda bahkan tidak perlu di $()sana, hanya touch {1..15}.txt {20..25}.txt :) Tapi tentu saja kita bisa menggunakan printf "%d\n{1..15} {20..25} `dengan xargsjika kita ingin melakukan lebih dari sekadar touchfile. Ada banyak cara untuk melakukan sesuatu dan itu membuat penulisan skrip jadi sangat menyenangkan!
Sergiy Kolodyazhnyy