Bagaimana cara iterate atas kisaran angka yang didefinisikan oleh variabel-variabel di Bash?

Bagaimana cara iterate atas kisaran angka di Bash ketika kisaran yang diberikan oleh variabel?

Aku tahu aku bisa melakukan ini (yang disebut "urutan ekspresi" di Bash dokumentasi):

 for i in {1..5}; do echo $i; done

Yang memberikan:

1
2
3
4
5

Namun, bagaimana saya bisa mengganti salah satu dari berbagai endpoint dengan variabel? Ini doesn't bekerja:

END=5
for i in {1..$END}; do echo $i; done

Yang mencetak:

{1..5}

Mengomentari pertanyaan (5)
Larutan
for i in $(seq 1 $AKHIR); echo $i; selesai

edit: saya lebih suka seq dibandingkan metode lain karena aku benar-benar ingat itu ;)

Komentar (11)

The seq adalah metode yang paling sederhana, tetapi Bash memiliki built-in aritmatika evaluasi.

END=5
for ((i=1;i outputs 1 2 3 4 5 on separate lines

Untuk ((expr1;expr2;expr3)); membangun bekerja seperti untuk (expr1;expr2;expr3) di C dan bahasa yang sama, dan seperti yang lain ((expr)) kasus, Bash memperlakukan mereka seperti aritmatika.

Komentar (10)

diskusi

Menggunakan seq baik-baik saja, sebagai Jiaaro yang disarankan. Pax Diablo menyarankan Bash loop untuk menghindari memanggil subproses, dengan tambahan keuntungan yang lebih banyak memori cocok jika $END terlalu besar. Zathrus melihat sebuah bug yang khas dalam lingkaran pelaksanaan, dan juga mengisyaratkan bahwa sejak aku adalah teks variabel, terus-menerus konversi untuk bolak-balik angka-angka dilakukan dengan terkait slow-down.

hitung bilangan bulat

Ini adalah versi perbaikan dari Bash loop:


typeset -i i END
let END=5 i=1
while ((i
Komentar (5)

Berikut ini adalah mengapa ekspresi asli didn't bekerja.

Dari man bash:

Penjepit ekspansi ini dilakukan sebelum lainnya ekspansi, dan setiap karakter khusus lainnya ekspansi yang diawetkan di hasil. Hal ini sangat tekstual. Bash tidak menerapkan sintaksis interpretasi untuk konteks perluasan atau teks antara kawat gigi.

Jadi, penjepit ekspansi adalah sesuatu yang dilakukan sejak dini sebagai murni tekstual makro operasi, sebelum parameter ekspansi.

Kerang yang sangat dioptimalkan hibrida antara makro prosesor dan lebih formal bahasa pemrograman. Dalam rangka mengoptimalkan penggunaan yang khas kasus, bahasa ini dibuat agak lebih kompleks dan beberapa keterbatasan yang diterima.

Rekomendasi

Saya akan menyarankan menempel dengan Posix1 fitur. Ini berarti menggunakan for i in <daftar>; do, jika daftar yang sudah diketahui, jika tidak, gunakan sementara atau seq, seperti dalam:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done

1. Bash adalah shell yang besar dan saya menggunakannya secara interaktif, tetapi saya don't menempatkan bash-isms ke script saya. Script mungkin perlu lebih cepat shell, lebih aman, lebih tertanam-gaya satu. Mereka mungkin perlu untuk berjalan pada apa pun yang diinstal seperti /bin/sh, dan kemudian ada semua biasa pro-standar argumen. Ingat *shellshock,* aka *bashdoor?*
Komentar (4)

POSIX cara

Jika anda peduli tentang portabilitas, gunakan contoh dari standar POSIX:

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

Output:

2
3
4
5

Hal-hal yang tidak POSIX:

Jika shell variabel x yang berisi nilai yang membentuk berlaku konstanta integer, opsional termasuk terkemuka plus atau tanda minus, maka aritmatika ekspansi "$((x))" dan "$(($x))" akan mengembalikan nilai yang sama.

tapi membaca itu benar-benar itu tidak berarti bahwa $((x+1)) mengembang sejak x+1 bukan sebuah variabel.

Komentar (5)

Lapisan lain dari tipuan:

for i in $(eval echo {1..$END}); do
    ∶
Komentar (1)

Anda dapat menggunakan

for i in $(seq $END); do echo $i; done
Komentar (8)

Jika anda membutuhkannya awalan dari anda mungkin seperti ini


 for ((i=7;i
Komentar (1)

Jika anda're di BSD / OS X anda dapat menggunakan iota bukan seq:

for i in $(jot $END); do echo $i; done
Komentar (2)

Ini bekerja dengan baik di bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
Komentar (7)

I've gabungan beberapa ide berikut ini dan diukur kinerjanya.

TL;DR Takeaways:

  1. seq dan {..} benar-benar cepat
  2. untuk dan sementara loop lambat
  3. $( ) lambat
  4. untuk (( ; ; )) loop lambat
  5. $(( )) ini bahkan lebih lambat
  6. Khawatir tentang N angka dalam memori (seq atau {..}) konyol (setidaknya sampai dengan 1 juta.)

Ini bukan kesimpulan. Anda akan memiliki untuk melihat kode C di belakang masing-masing untuk menarik kesimpulan. Ini lebih tentang bagaimana kita cenderung untuk menggunakan masing-masing dari mekanisme ini untuk perulangan di atas kode. Paling satu operasi yang cukup dekat untuk menjadi kecepatan yang sama bahwa itu's tidak akan peduli dalam kebanyakan kasus. Tapi mekanisme seperti untuk (( i=1; i<=1000000; i++ )) lebih banyak operasi yang anda bisa melihat secara visual. Hal ini juga lebih banyak operasi per loop dari yang anda dapatkan dari for i in $(seq 1 1000000). Dan yang mungkin tidak jelas bagi anda, yang mengapa melakukan tes seperti ini sangat berharga.

Demo


# show that seq is fast
$ time (seq 1 1000000 | wc)
 1000000 1000000 6888894

real    0m0.227s
user    0m0.239s
sys     0m0.008s

# show that {..} is fast
$ time (echo {1..1000000} | wc)
       1 1000000 6888896

real    0m1.778s
user    0m1.735s
sys     0m0.072s

# Show that for loops (even with a : noop) are slow
$ time (for i in {1..1000000} ; do :; done | wc)
       0       0       0

real    0m3.642s
user    0m3.582s
sys 0m0.057s

# show that echo is slow
$ time (for i in {1..1000000} ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m7.480s
user    0m6.803s
sys     0m2.580s

$ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)
 1000000 1000000 6888894

real    0m7.029s
user    0m6.335s
sys     0m2.666s

# show that C-style for loops are slower
$ time (for (( i=1; i
Komentar (2)

Aku tahu pertanyaan ini adalah tentang bash, tapi hanya untuk catatan - ksh93 lebih pintar dan mengimplementasikannya sesuai dengan yang diharapkan:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
1
2
3
4
5
$ ksh -c 'echo $KSH_VERSION'
Version JM 93u+ 2012-02-29

$ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
{1..5}
Komentar (0)

Jika anda ingin tinggal sedekat mungkin dengan brace-sintaks ekspresi, mencoba range fungsi dari bash-trik' range.bash.

Sebagai contoh, berikut akan melakukan hal yang sama persis seperti echo {1..10}:

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

Mencoba untuk dukungan asli bash sintaks dengan sedikit "yang perlu diperhatikan" mungkin: tidak hanya variabel didukung, tapi sering-perilaku yang tidak diinginkan yang tidak valid berkisar dipasok sebagai string (misalnya for i in {1..a}; echo $i; dilakukan) dicegah juga.

Dengan jawaban yang lain akan bekerja pada sebagian besar kasus, tetapi mereka semua memiliki setidaknya satu dari kelemahan berikut:

  • Banyak dari mereka menggunakan subshells, yang dapat membahayakan kinerja](http://rus.har.mn/blog/2010-07-05/subshells/) dan mungkin tidak possible pada beberapa sistem.
  • Banyak dari mereka bergantung pada program eksternal. Bahkan seq adalah biner yang harus diinstal untuk digunakan, harus dimuat oleh bash, dan harus berisi program yang anda harapkan, untuk itu untuk bekerja dalam kasus ini. Di mana-mana atau tidak, yang's lebih banyak mengandalkan dari sekedar Bash bahasa itu sendiri.
  • Solusi yang dilakukan hanya menggunakan native Bash fungsi, seperti @ephemient's, tidak akan bekerja pada abjad berkisar, seperti {a..z}; penjepit ekspansi akan. Pertanyaannya adalah tentang rentang numbers, meskipun, jadi ini adalah berdalih.
  • Sebagian besar dari mereka tidak't secara visual mirip dengan {1..10} brace-memperluas jangkauan sintaks, sehingga program-program yang menggunakan kedua mungkin akan sedikit sulit untuk dibaca.
  • @bobbogo's jawaban menggunakan beberapa sintaks akrab, tapi apakah sesuatu yang tak terduga jika $END variabel tidak valid range "bookend" untuk sisi lain dari jangkauan. Jika END=a, misalnya, kesalahan tidak akan terjadi dan verbatim nilai {1..a} akan bergema. Ini adalah perilaku default Bash, juga-itu adalah hanya sering tak terduga.

Disclaimer: saya penulis terkait kode.

Komentar (0)

Ini adalah cara lain:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
Komentar (2)

Ganti {} dengan (( )):


tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i
Komentar (1)

Ini semua bagus tapi seq diduga usang dan sebagian besar hanya bekerja dengan angka kisaran.

Jika anda menyertakan anda untuk loop dalam tanda kutip ganda, awal dan akhir variabel yang akan dereferenced ketika anda echo string, dan anda dapat mengirimkan string segera kembali ke BASH untuk eksekusi. $i harus melarikan diri dengan \'s sehingga TIDAK dievaluasi sebelum dikirim ke subkulit.

RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash

Output ini juga dapat diberikan ke sebuah variabel:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`

Hanya "overhead" ini harus menghasilkan harus menjadi contoh kedua bash sehingga harus cocok untuk operasi intensif.

Komentar (0)

Jika anda're lakukan perintah shell dan anda (seperti saya) memiliki jimat untuk pipelining, yang satu ini lebih baik:

seq 1 $END | xargs -aku {} echo {}

Komentar (0)

Ada banyak cara untuk melakukan hal ini, namun orang-orang yang saya sukai adalah yang diberikan di bawah ini

Menggunakan seq

Sinopsis dari man seq

$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last

Sintaks

Perintah penuh
seq pertama incr terakhir

  • pertama adalah nomor awal di urutan [opsional, secara default:1]
  • incr adalah kenaikan [opsional, secara default:1]
  • terakhir adalah nomor terakhir dalam urutan

Contoh:

$ seq 1 2 10
1 3 5 7 9

Hanya dengan pertama dan terakhir:

$ seq 1 5
1 2 3 4 5

Hanya dengan terakhir:

$ seq 5
1 2 3 4 5

Menggunakan {pertama..lalu..incr}

Di sini pertama dan terakhir yang wajib dan incr adalah opsional

Menggunakan hanya pertama dan terakhir

$ echo {1..5}
1 2 3 4 5

Menggunakan incr

$ echo {1..10..2}
1 3 5 7 9

Anda dapat menggunakan ini bahkan untuk karakter seperti di bawah ini

$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
Komentar (0)

Ini bekerja di Bash dan Korn, juga dapat pergi dari tinggi ke rendah angka. Mungkin bukan yang tercepat atau tercantik tetapi bekerja cukup baik. Menangani negatif juga.

function num_range {
   # Return a range of whole numbers from beginning value to ending value.
   # >>> num_range start end
   # start: Whole number to start with.
   # end: Whole number to end with.
   typeset s e v
   s=${1}
   e=${2}
   if (( ${e} >= ${s} )); then
      v=${s}
      while (( ${v} = ${e} )); do
         echo ${v}
         ((v=v-1))
      done
   fi
}

function test_num_range {
   num_range 1 3 | egrep "1|2|3" | assert_lc 3
   num_range 1 3 | head -1 | assert_eq 1
   num_range -1 1 | head -1 | assert_eq "-1"
   num_range 3 1 | egrep "1|2|3" | assert_lc 3
   num_range 3 1 | head -1 | assert_eq 3
   num_range 1 -1 | tail -1 | assert_eq "-1"
}
Komentar (0)

jika anda don't ingin menggunakan 'seq' atau 'eval' atau iota atau aritmatika ekspansi format misalnya. untuk ((i=1;i<=AKHIR;i++)), atau loop lain misalnya. sementara, dan anda don't ingin 'printf' dan 'echo' hanya saja, maka ini solusi sederhana yang mungkin sesuai anggaran anda:

a=1; b=5; d=&#39;for i in {&#39;$a&#39;..&#39;$b&#39;}; echo -n "$i"; dilakukan;&#39; echo "$d" | bash

PS: Saya bash doesn't memiliki 'seq' perintah pula.

Diuji pada Mac OSX 10.6.8, Bash 3.2.48

Komentar (0)