Bagaimana cara saya mengulangi rentang angka di Bash ketika rentang diberikan oleh variabel?
Saya tahu saya bisa melakukan ini (disebut "ekspresi urutan" dalam dokumentasi Bash ):
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 titik akhir rentang dengan variabel? Ini tidak berfungsi:
END=5
for i in {1..$END}; do echo $i; done
Yang mencetak:
{1..5}
for i in {01..10}; do echo $i; done
akan memberikan angka seperti01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(perhatikan tanda seru). Ini lebih spesifik daripada pertanyaan awal, tetapi bisa membantu. Lihat ekspansi parameter bash{jpg,png,gif}
yang tidak secara langsung dibahas di sini, meskipun jawabannya akan sama. Lihat ekspansi Brace dengan variabel? [duplikat] yang ditandai sebagai duplikat yang satu ini.Jawaban:
sunting: Saya lebih suka
seq
daripada metode lain karena saya benar-benar dapat mengingatnya;)sumber
seq $END
sudah cukup, karena standarnya mulai dari 1. Dariman seq
: "Jika PERTAMA atau INCREMENT dihilangkan, standarnya adalah 1".The
seq
metode adalah yang paling sederhana, tapi Bash telah built-in evaluasi aritmatika.The
for ((expr1;expr2;expr3));
membangun bekerja sepertifor (expr1;expr2;expr3)
di C dan bahasa yang sama, dan seperti lainnya((expr))
kasus, Bash memperlakukan mereka sebagai aritmatika.sumber
seq
. Gunakan!#!/bin/bash
baris pertama skrip Anda. wiki.ubuntu.com/…diskusi
Menggunakan
seq
tidak apa-apa, seperti yang disarankan Jiaaro. Pax Diablo menyarankan Bash loop untuk menghindari memanggil subproses, dengan keuntungan tambahan menjadi lebih ramah memori jika $ END terlalu besar. Zathrus melihat bug khas dalam implementasi loop, dan juga mengisyaratkan bahwa karenai
merupakan variabel teks, konversi berkesinambungan ke-dan-nomor mondar-mandir dilakukan dengan perlambatan terkait.aritmatika integer
Ini adalah versi perbaikan dari Bash loop:
Jika satu-satunya hal yang kita inginkan adalah
echo
, maka kita bisa menulisecho $((i++))
.ephemient mengajari saya sesuatu: Bash memungkinkan
for ((expr;expr;expr))
konstruksi. Karena saya belum pernah membaca halaman manual untuk Bash (seperti yang saya lakukan dengan halaman manual shell Korn (ksh
), dan itu sudah lama sekali), saya melewatkan itu.Begitu,
tampaknya menjadi cara yang paling hemat memori (tidak perlu mengalokasikan memori untuk menggunakan
seq
output, yang bisa menjadi masalah jika END sangat besar), walaupun mungkin bukan yang "tercepat".pertanyaan awal
eschercycle mencatat bahwa notasi { a .. b } hanya bekerja dengan literal; benar, sesuai dengan manual Bash. Seseorang dapat mengatasi kendala ini dengan satu (internal)
fork()
tanpaexec()
(seperti halnya dengan panggilanseq
, yang menjadi gambar lain membutuhkan fork + exec):Kedua
eval
danecho
Bash builtins, tetapifork()
diperlukan untuk substitusi perintah ($(…)
konstruk).sumber
for ((i=$1;i<=$2;++i)); do echo $i; done
dalam skrip berfungsi dengan baik untuk saya di bash v.4.1.9, jadi saya tidak melihat masalah dengan argumen baris perintah. Apakah Anda bermaksud sesuatu yang lain?time for i in $(seq 100000); do :; done
jauh lebih cepat!Inilah mengapa ekspresi asli tidak berfungsi.
Dari man bash :
Jadi, ekspansi brace adalah sesuatu yang dilakukan sejak awal sebagai operasi makro murni tekstual, sebelum ekspansi parameter.
Shell adalah hibrida yang sangat dioptimalkan antara prosesor makro dan bahasa pemrograman yang lebih formal. Untuk mengoptimalkan kasus penggunaan umum, bahasa dibuat agak lebih kompleks dan beberapa batasan diterima.
Saya sarankan tetap menggunakan fitur Posix 1 . Ini berarti menggunakan
for i in <list>; do
, jika daftar sudah diketahui, jika tidak, gunakanwhile
atauseq
, seperti dalam:1. Bash adalah shell yang bagus dan saya menggunakannya secara interaktif, tetapi saya tidak memasukkan bash-isme ke dalam skrip saya. Script mungkin membutuhkan shell yang lebih cepat, yang lebih aman, yang lebih tertanam-gaya. Mereka mungkin perlu dijalankan pada apa pun yang diinstal sebagai / bin / sh, dan kemudian ada semua argumen pro-standar yang biasa. Ingat shellshock, alias bashdoor?
sumber
seq
untuk rentang besar. Misalnya,echo {1..1000000} | wc
mengungkapkan bahwa gema menghasilkan 1 baris, sejuta kata, dan 6.888.896 byte. Mencobaseq 1 1000000 | wc
menghasilkan sejuta baris, sejuta kata, dan 6.888.896 byte dan juga lebih dari tujuh kali lebih cepat, sebagaimana diukur olehtime
perintah.while
metode POSIX sebelumnya dalam jawaban saya: stackoverflow.com/a/31365662/895245 Tapi senang Anda setuju :-)Cara POSIX
Jika Anda peduli tentang portabilitas, gunakan contoh dari standar POSIX :
Keluaran:
Hal-hal yang bukan POSIX:
(( ))
tanpa dolar, meskipun itu adalah ekstensi umum seperti yang disebutkan oleh POSIX sendiri .[[
.[
sudah cukup di sini. Lihat juga: Apa perbedaan antara tanda kurung kotak tunggal dan ganda di Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, dan itu tidak bisa bekerja dengan variabel seperti yang disebutkan oleh manual Bash .let i=i+1
: POSIX 7 2. Bahasa Perintah Shell tidak mengandung katalet
, dan gagal padabash --posix
4.3.42dolar
i=$i+1
mungkin diperlukan, tapi saya tidak yakin. POSIX 7 2.6.4 Ekspansi Aritmatika mengatakan:tetapi membacanya secara harfiah yang tidak menyiratkan bahwa
$((x+1))
memperluas karenax+1
bukan variabel.sumber
x
, bukan keseluruhan ekspresi.$((x + 1))
baik-baik saja.seq
(BSDseq
memungkinkan Anda untuk mengatur urutan string pemutusan dengan-t
), FreeBSD dan NetBSD juga memilikiseq
masing-masing sejak 9.0 dan 3.0.$((x+1))
dan$((x + 1))
parse persis sama, seperti ketika parser tokenizesx+1
akan dibagi menjadi 3 token:x
,+
, dan1
.x
bukan token numerik yang valid, tetapi token nama variabel yang valid, namunx+
tidak, karenanya pemisahan.+
adalah token operator aritmatika yang valid, namun+1
tidak, sehingga token tersebut dibagi lagi di sana. Dan seterusnya.Lapisan tipuan lainnya:
sumber
Kamu bisa menggunakan
sumber
Jika Anda membutuhkannya awalan daripada yang Anda mungkin suka ini
itu akan menghasilkan
sumber
printf "%02d\n" $i
lebih mudah dari ituprintf "%2.0d\n" $i |sed "s/ /0/"
?Jika Anda menggunakan BSD / OS X Anda dapat menggunakan jot alih-alih seq:
sumber
Ini berfungsi dengan baik di
bash
:sumber
echo $((i++))
bekerja dan menggabungkannya menjadi satu baris.bash
, kita bisa melakukanwhile [[ i++ -le "$END" ]]; do
untuk melakukan (post-) selisih dalam tesSaya telah menggabungkan beberapa ide di sini dan mengukur kinerja.
TL; DR Takeaways:
seq
dan{..}
sangat cepatfor
danwhile
loop lambat$( )
lambatfor (( ; ; ))
loop lebih lambat$(( ))
bahkan lebih lambatIni bukan kesimpulan . Anda harus melihat kode C di belakang masing-masing untuk menarik kesimpulan. Ini lebih tentang bagaimana kita cenderung menggunakan masing-masing mekanisme ini untuk pengulangan kode. Kebanyakan operasi tunggal cukup dekat untuk menjadi kecepatan yang sama sehingga tidak menjadi masalah dalam banyak kasus. Tetapi mekanisme seperti
for (( i=1; i<=1000000; i++ ))
ini adalah banyak operasi yang dapat Anda lihat secara visual. Ini juga lebih banyak operasi per loop dari yang Anda dapatkanfor i in $(seq 1 1000000)
. Dan itu mungkin tidak jelas bagi Anda, itulah sebabnya melakukan tes seperti ini sangat berharga.Demo
sumber
$(seq)
adalah tentang kecepatan yang sama dengan{a..b}
. Juga, setiap operasi membutuhkan waktu yang sama, jadi menambahkan sekitar 4μs untuk setiap iterasi dari loop untuk saya. Di sini operasi adalah gema dalam tubuh, perbandingan aritmatika, kenaikan, dll. Apakah ini mengejutkan? Siapa yang peduli berapa lama perangkat loop untuk melakukan tugasnya — runtime cenderung didominasi oleh konten loop.Saya tahu pertanyaan ini tentang
bash
, tetapi - hanya untuk catatan -ksh93
lebih pintar dan mengimplementasikannya seperti yang diharapkan:sumber
Ini adalah cara lain:
sumber
Jika Anda ingin tetap sedekat mungkin dengan sintaks ekspresi-brace, cobalah
range
fungsi dari bash-tricks 'range.bash
.Misalnya, semua hal berikut akan melakukan hal yang sama persis seperti
echo {1..10}
:Ia mencoba untuk mendukung sintaks bash asli dengan sesedikit mungkin "gotcha": tidak hanya variabel yang didukung, tetapi perilaku rentang tidak valid yang sering diberikan sebagai string (misalnya
for i in {1..a}; do echo $i; done
) dicegah juga.Jawaban lain akan berfungsi dalam kebanyakan kasus, tetapi mereka semua memiliki setidaknya satu dari kekurangan berikut:
seq
adalah biner yang harus diinstal untuk digunakan, harus dimuat oleh bash, dan harus berisi program yang Anda harapkan, agar bisa berfungsi dalam kasus ini. Di mana-mana atau tidak, itu jauh lebih bisa diandalkan daripada hanya bahasa Bash itu sendiri.{a..z}
; ekspansi brace akan. Pertanyaannya adalah tentang rentang angka , jadi ini adalah pertengkaran.{1..10}
sintaks rentang yang dikembangkan, jadi program yang menggunakan keduanya mungkin sedikit lebih sulit untuk dibaca.$END
variabel bukan rentang "bookend" yang valid untuk sisi lain dari rentang. JikaEND=a
, misalnya, kesalahan tidak akan terjadi dan nilai kata demi kata{1..a}
akan digaungkan. Ini adalah perilaku default Bash, juga - itu sering tak terduga.Penafian: Saya adalah pembuat kode tertaut.
sumber
Ganti
{}
dengan(( ))
:Hasil:
sumber
Ini semua bagus tapi seq seharusnya sudah usang dan sebagian besar hanya berfungsi dengan rentang numerik.
Jika Anda menyertakan for loop dalam tanda kutip ganda, variabel awal dan akhir akan ditinjau ketika Anda mengulangi string, dan Anda dapat mengirimkan string kembali ke BASH untuk dieksekusi.
$i
perlu melarikan diri dengan sehingga TIDAK dievaluasi sebelum dikirim ke subkulit.Output ini juga dapat ditugaskan ke variabel:
Satu-satunya "overhead" yang harus dihasilkan ini adalah instance kedua dari bash sehingga harus sesuai untuk operasi intensif.
sumber
Jika Anda melakukan perintah shell dan Anda (seperti saya) memiliki jimat untuk pipelining, ini bagus:
seq 1 $END | xargs -I {} echo {}
sumber
Ada banyak cara untuk melakukan ini, namun yang saya sukai diberikan di bawah ini
Menggunakan
seq
Perintah penuh
seq first incr last
Contoh:
Hanya dengan yang pertama dan terakhir:
Hanya dengan yang terakhir:
Menggunakan
{first..last..incr}
Di sini pertama dan terakhir adalah wajib dan tambahan adalah opsional
Menggunakan hanya pertama dan terakhir
Menggunakan incr
Anda dapat menggunakan ini bahkan untuk karakter seperti di bawah ini
sumber
jika Anda tidak ingin menggunakan format ekspansi '
seq
' atau 'eval
' ataujot
atau aritmatika misalnya.for ((i=1;i<=END;i++))
, atau loop lain misalnya.while
, dan Anda tidak ingin 'printf
' dan senang 'echo
' saja, maka solusi sederhana ini mungkin sesuai dengan anggaran Anda:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PS: Lagipula bash saya tidak punya
seq
perintah.Diuji pada Mac OSX 10.6.8, Bash 3.2.48
sumber
Ini bekerja di Bash dan Korn, juga bisa berubah dari yang lebih tinggi ke angka yang lebih rendah. Mungkin tidak tercepat atau tercantik tetapi bekerja dengan cukup baik. Menangani negatif juga.
sumber