Pertanyaan Bagaimana cara melakukan iterasi pada rentang angka yang ditentukan oleh variabel di Bash?


Bagaimana cara melakukan iterasi pada rentang angka di Bash ketika rentang diberikan oleh variabel?

Saya tahu saya bisa melakukan ini (disebut "ekspresi urut" di Bash dokumentasi):

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

Pemberian yang mana:

1
  2
  3
  4
  5

Namun, bagaimana saya bisa mengganti salah satu dari berbagai titik akhir dengan variabel? Ini tidak berfungsi:

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

Cetakan mana:

{1..5}


1056
2017-10-04 01:38


asal


Jawaban:


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

edit: Saya lebih suka seq atas metode lain karena saya benar-benar dapat mengingatnya;)


1186
2017-10-04 01:41



Itu seq metode yang paling sederhana, tetapi Bash memiliki evaluasi aritmatika bawaan.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

Itu for ((expr1;expr2;expr3)); membangun karya seperti for (expr1;expr2;expr3) dalam bahasa C dan bahasa yang serupa, dan seperti lainnya ((expr)) kasus, Bash memperlakukan mereka sebagai aritmatika.


309
2017-10-04 21:43



diskusi

Menggunakan seq baik-baik saja, seperti saran Jiaaro. Pax Diablo menyarankan Bash loop untuk menghindari memanggil subprocess, dengan keuntungan tambahan menjadi lebih ramah memori jika $ END terlalu besar. Zathrus melihat bug yang khas dalam implementasi loop, dan juga mengisyaratkan bahwa sejak itu i adalah variabel teks, konversi kontinyu ke-dan-fro nomor dilakukan dengan slow-down yang terkait.

aritmatika bilangan bulat

Ini adalah versi perbaikan dari loop Bash:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

Jika satu-satunya hal yang kami inginkan adalah echo, lalu kita bisa menulis echo $((i++)).

ephemient mengajari saya sesuatu: Bash memungkinkan for ((expr;expr;expr)) konstruksi. Karena saya belum pernah membaca seluruh halaman manual untuk Bash (seperti yang saya lakukan dengan shell Korn (ksh) halaman manual, dan itu sudah lama sekali), saya merindukan itu.

Begitu,

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

tampaknya merupakan cara yang paling hemat memori (tidak perlu mengalokasikan memori untuk dikonsumsi seqOutput, yang bisa menjadi masalah jika END sangat besar), meskipun mungkin bukan yang "tercepat".

pertanyaan awal

eschercycle mencatat bahwa {Sebuah..b} Notasi Bash hanya bekerja dengan literal; benar, sesuai dengan manual Bash. Seseorang dapat mengatasi hambatan ini dengan satu (internal) fork() tanpa sebuah exec() (Seperti halnya panggilan telepon seq, yang menjadi gambar lain membutuhkan garpu + exec):

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

Kedua eval dan echo adalah Bash builtins, tetapi a fork() diperlukan untuk substitusi perintah (yang $(…) membangun).


160
2017-10-04 02:38



Inilah mengapa ekspresi aslinya tidak berfungsi.

Dari bash manusia:

Ekspansi brace dilakukan sebelumnya   perluasan lainnya, dan apa saja   karakter khusus untuk lainnya   ekspansi dipertahankan dalam   hasil. Ini sangat tekstual. Pesta   tidak menerapkan sintaksis apa pun   interpretasi ke konteks   perluasan atau teks antara   kawat gigi.

Begitu, brace ekspansi adalah sesuatu yang dilakukan sejak awal sebagai operasi makro tekstual murni, sebelumnya ekspansi parameter.

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

Rekomendasi

Saya menyarankan berpegang pada Posix1 fitur. Ini berarti menggunakan for i in <list>; do, jika daftar sudah diketahui, sebaliknya, gunakan while 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 cangkang yang hebat dan saya menggunakannya secara interaktif, tetapi saya tidak memasukkan bash-isme ke dalam skrip saya. Skrip mungkin membutuhkan cangkang yang lebih cepat, yang lebih aman, yang lebih bergaya tertanam. Mereka mungkin perlu menjalankan apa pun yang diinstal sebagai / bin / sh, dan kemudian ada semua argumen pro-standar yang biasa. Ingat shellshock, aka bashdoor? 


81
2018-04-19 22:32



Cara POSIX

Jika Anda peduli dengan portabilitas, gunakan contoh dari standar POSIX:

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

Keluaran:

2
3
4
5

Hal-hal yang ada tidak POSIX:


43
2017-07-12 07:54



Lapisan lain dari tipuan:

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

28
2018-03-14 19:51



Kamu dapat memakai

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

21
2017-10-04 01:41



Jika Anda menggunakan BSD / OS X, Anda dapat menggunakan jot alih-alih seq:

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

18
2018-03-15 23:29



Ini berfungsi dengan baik di bash:

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

13
2017-10-04 01:42



Jika Anda membutuhkannya awalan dari Anda mungkin seperti ini

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

yang akan menghasilkan

07
08
09
10
11
12

11
2018-06-03 20:14



Saya tahu pertanyaan ini adalah tentang bash, tapi - hanya untuk catatan - ksh93 lebih pintar dan menerapkannya seperti 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}

6
2017-09-19 12:33