ฉันจะวนซ้ำช่วงของตัวเลขที่กำหนดโดยตัวแปรใน Bash ได้อย่างไร


1543

ฉันจะวนซ้ำในช่วงของตัวเลขใน Bash ได้อย่างไรเมื่อช่วงถูกกำหนดโดยตัวแปร

ฉันรู้ว่าฉันสามารถทำได้ (เรียกว่า "ลำดับนิพจน์" ในเอกสาร Bash ):

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

ซึ่งจะช่วยให้:

1
2
3
4
5

แต่ฉันจะแทนที่จุดปลายของช่วงใดช่วงหนึ่งด้วยตัวแปรได้อย่างไร สิ่งนี้ใช้ไม่ได้:

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

สิ่งที่พิมพ์:

{} 1..5


26
สวัสดีทุกข้อมูลและคำแนะนำที่ฉันได้อ่านที่นี่ล้วนมีประโยชน์จริงๆ ฉันคิดว่ามันเป็นการดีที่สุดที่จะหลีกเลี่ยงการใช้ seq เหตุผลก็คือสคริปต์บางตัวจำเป็นต้องพกพาได้และต้องทำงานกับระบบยูนิกซ์ที่หลากหลายซึ่งบางคำสั่งอาจไม่ปรากฏ เพียงเพื่อทำตัวอย่าง seq ไม่มีอยู่ในระบบ FreeBSD


9
ฉันจำไม่ได้ว่าเวอร์ชันของ Bash ตรงไหน แต่คำสั่งนี้รองรับค่าศูนย์ต่อท้ายเช่นกัน ซึ่งบางครั้งก็มีประโยชน์จริงๆ คำสั่งจะให้ตัวเลขเช่นfor i in {01..10}; do echo $i; done 01, 02, 03, ..., 10
topr

1
สำหรับผู้ที่ชอบฉันที่ต้องการย้ำช่วงดัชนีของอาร์เรย์วิธีทุบตีจะเป็น: myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done(สังเกตเครื่องหมายอัศเจรีย์) มันเฉพาะเจาะจงกว่าคำถามเดิม แต่สามารถช่วยได้ ดูการขยายพารามิเตอร์ bash
PlasmaBinturong

1
วงเล็บปีกกายังใช้สำหรับการแสดงออก{jpg,png,gif}ที่ไม่ได้กล่าวถึงที่นี่โดยตรงแม้ว่าคำตอบจะเหมือนกัน ดูการขยายรั้งด้วยตัวแปร? [ซ้ำ]ซึ่งทำเครื่องหมายว่าซ้ำกับอันนี้
tripleee

คำตอบ:


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

แก้ไข: ฉันชอบseqวิธีอื่นมากกว่าเพราะฉันจำได้จริง)


36
seq เกี่ยวข้องกับการดำเนินการของคำสั่งภายนอกซึ่งมักจะทำให้สิ่งต่าง ๆ ช้าลง สิ่งนี้อาจไม่สำคัญ แต่สำคัญหากคุณกำลังเขียนสคริปต์เพื่อจัดการข้อมูลจำนวนมาก
paxdiablo

37
ได้ดีสำหรับหนึ่งซับ วิธีการแก้ปัญหาของ Pax ก็ดีเช่นกัน แต่ถ้าประสิทธิภาพเป็นเรื่องที่น่ากังวลจริงๆฉันจะไม่ใช้เชลล์สคริปต์
eschercycle

17
seq ถูกเรียกเพียงครั้งเดียวเพื่อสร้างตัวเลข exec () โดยไม่ควรมีนัยสำคัญเว้นแต่ว่าการวนซ้ำนี้จะอยู่ในการวนรอบที่แน่นอีกครั้ง
Javier

29
คำสั่งภายนอกนั้นไม่เกี่ยวข้องกันจริงๆ: ถ้าคุณกังวลเกี่ยวกับค่าใช้จ่ายในการรันคำสั่งภายนอกคุณไม่ต้องการใช้เชลล์สคริปต์เลย แต่โดยทั่วไปบนยูนิกซ์ค่าโสหุ้ยจะต่ำ อย่างไรก็ตามมีปัญหาในการใช้หน่วยความจำถ้า END สูง
Mark Baker

18
โปรดทราบว่าseq $ENDจะพอเพียงตามค่าเริ่มต้นคือเริ่มต้นจาก 1 จากman seq: "ถ้าไม่ระบุ FIRST หรือ INCREMENT ค่าเริ่มต้นจะเป็น 1"
fedorqui 'ดังนั้นหยุดทำอันตราย'

473

seqวิธีที่ง่าย แต่ทุบตีได้สร้างในการประเมินผลทางคณิตศาสตร์

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

for ((expr1;expr2;expr3));สร้างทำงานเช่นเดียวกับfor (expr1;expr2;expr3)ใน C และคล้ายภาษาและอื่น ๆ เช่น((expr))กรณีถือว่าทุบตีพวกเขาเป็นเลขคณิต


67
seqวิธีนี้หลีกเลี่ยงค่าใช้จ่ายในความทรงจำของรายการใหญ่และการพึ่งพา ใช้มัน!
bobbogo

3
@MarinSagovac วิธีนี้ใช้งานได้และไม่มีข้อผิดพลาดทางไวยากรณ์ คุณแน่ใจหรือว่าเปลือกของคุณคือ Bash?
gniourf_gniourf

3
@MarinSagovac อย่าลืมทำให้#!/bin/bashบรรทัดแรกของสคริปต์ของคุณ wiki.ubuntu.com/…
Melebius

7
เป็นเพียงคำถามสั้น ๆ เกี่ยวกับสิ่งนั้น: เหตุใด ((i = 1; i <= END; i ++)) และไม่ ((i = 1; i <= $ END; i ++)); ทำไมไม่มี $ ก่อน END
Baedsch

5
@ Baedsch: ด้วยเหตุผลเดียวกับที่ฉันไม่ได้ใช้เป็น $ i bash man page ระบุสำหรับการประเมินผลเลขคณิต: "ภายในนิพจน์ตัวแปรเชลล์อาจถูกอ้างอิงด้วยชื่อโดยไม่ต้องใช้ไวยากรณ์การขยายพารามิเตอร์"
user3188140

193

อภิปรายผล

ใช้งานseqได้ดีตามที่ Jiaaro แนะนำ Pax Diablo แนะนำ Bash loop เพื่อหลีกเลี่ยงการเรียก subprocess ด้วยข้อได้เปรียบเพิ่มเติมของการเป็นมิตรกับหน่วยความจำถ้า $ END ใหญ่เกินไป Zathrus พบข้อผิดพลาดทั่วไปในการใช้งานลูปและยังบอกเป็นนัยว่าเนื่องจากiเป็นตัวแปรข้อความการแปลงหมายเลขต่อเนื่องเป็นไปอย่างต่อเนื่องจะดำเนินการด้วยการชะลอตัวที่เกี่ยวข้อง

เลขคณิตจำนวนเต็ม

นี่เป็นรุ่นปรับปรุงของ Bash loop:

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

หากสิ่งเดียวที่เราต้องการก็คือechoเราสามารถเขียนecho $((i++))ได้

ephemientสอนฉันบางอย่าง: Bash อนุญาตให้for ((expr;expr;expr))สร้าง เนื่องจากฉันไม่เคยอ่าน man page ทั้งหมดสำหรับ Bash (เหมือนกับที่ฉันทำกับ Korn shell ( ksh) man page และนั่นก็เป็นเวลานานแล้ว) ฉันจึงคิดถึงมัน

ดังนั้น,

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

ดูเหมือนจะเป็นวิธีที่มีประสิทธิภาพมากที่สุดของหน่วยความจำ (ไม่จำเป็นต้องจัดสรรหน่วยความจำให้ใช้งานseqเอาต์พุตซึ่งอาจเป็นปัญหาหาก END มีขนาดใหญ่มาก) แม้ว่าอาจไม่ใช่ "เร็วที่สุด"

คำถามเริ่มต้น

eschercycle ตั้งข้อสังเกตว่า { a .. b } สัญกรณ์ Bash ใช้ได้กับตัวอักษรเท่านั้น จริงตามคู่มือ Bash เราสามารถเอาชนะสิ่งกีดขวางนี้ได้ด้วยเดี่ยว (ภายใน) fork()โดยไม่ต้องมีexec()(เช่นกรณีที่มีการโทรseqซึ่งเป็นภาพอื่นต้องใช้ส้อม + exec):

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

ทั้งสองevalและechoเป็น Bash ในตัว แต่fork()จำเป็นต้องมีสำหรับการทดแทนคำสั่ง (ตัว$(…)สร้าง)


1
ข้อเสียเปรียบเพียงอย่างเดียวของการวนซ้ำสไตล์ C ที่ไม่สามารถใช้อาร์กิวเมนต์บรรทัดคำสั่งได้เนื่องจากเริ่มต้นด้วย "$"
karatedog

3
@karatedog: for ((i=$1;i<=$2;++i)); do echo $i; doneในสคริปต์ทำงานได้ดีสำหรับฉันใน bash v.4.1.9 ดังนั้นฉันไม่เห็นปัญหาเกี่ยวกับอาร์กิวเมนต์บรรทัดคำสั่ง คุณหมายถึงอะไรอย่างอื่นหรือ
tzot

ดูเหมือนว่าโซลูชัน eval นั้นเร็วกว่าแบบ C-like สำหรับ: $ time สำหรับ ((i = 1; i <= 100000; ++ i)); ทำ:; ทำจริงผู้ใช้ 0m21.220s 0m19.763s sys 0m1.203s $ เวลาสำหรับ i ใน $ (eal evcho "{1..100000}"); ทำ:; ทำ; จริงผู้ใช้ 0m13.881s 0m13.536s sys 0m0.152s
Marcin Zaluski

3
ใช่ แต่eval นั้นชั่วร้าย ... @MarcinZaluski time for i in $(seq 100000); do :; doneเร็วกว่ามาก!
F. Hauri

ประสิทธิภาพจะต้องเป็นแพลตฟอร์มที่เฉพาะเจาะจงตั้งแต่รุ่น Eval เร็วที่สุดในเครื่องของฉัน
Andrew Prock

103

นี่คือเหตุผลที่การแสดงออกดั้งเดิมไม่ทำงาน

จากคนทุบตี :

การขยายของ Brace จะดำเนินการก่อนการขยายอื่น ๆ และอักขระใด ๆ ที่พิเศษสำหรับการขยายอื่น ๆ จะถูกเก็บไว้ในผลลัพธ์ มันเป็นข้อความอย่างเคร่งครัด Bash ไม่ได้ใช้การตีความทางวากยสัมพันธ์ใด ๆ กับบริบทของการขยายตัวหรือข้อความระหว่างวงเล็บปีกกา

ดังนั้นการขยายตัวของรั้งเป็นสิ่งที่ทำเร็วเท่าการดำเนินการแมโครต้นฉบับเดิมอย่างหมดจดก่อนที่จะขยายพารามิเตอร์

เชลล์เป็นไฮบริดที่เหมาะสมที่สุดระหว่างตัวประมวลผลแมโครและภาษาการเขียนโปรแกรมที่เป็นทางการมากขึ้น เพื่อเพิ่มประสิทธิภาพกรณีการใช้งานทั่วไปภาษาจึงค่อนข้างซับซ้อนและยอมรับข้อ จำกัด บางอย่าง

คำแนะนำ

ฉันอยากจะแนะนำให้ติดกับฟีเจอร์Posix 1 นี่หมายถึงการใช้for i in <list>; doถ้าเป็นที่รู้จักแล้วมิฉะนั้นให้ใช้whileหรือseqตามที่:

#!/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 เป็นเปลือกที่ยอดเยี่ยมและฉันใช้มันแบบโต้ตอบ แต่ฉันไม่ใส่ bash-isms ลงในสคริปต์ของฉัน สคริปอาจต้องการเชลล์ที่เร็วกว่า, อันที่ปลอดภัยมากกว่า, อันที่ฝังตัวมากกว่า พวกเขาอาจจำเป็นต้องเรียกใช้สิ่งที่ติดตั้งเป็น / bin / sh แล้วมีข้อโต้แย้งตามมาตรฐาน pro ทั้งหมด จำshellshockหรือที่รู้จักbashdoor?


13
ฉันไม่ได้มีอำนาจ แต่ฉันจะย้ายรายการนี้ขึ้นเล็กน้อยเหนือสิ่งอื่นใดทุบตีสะดือจ้อง แต่ทันทีหลังจาก C-style สำหรับการประเมินผลห่วงและเลขคณิต
ร่วมงาน

2
ความหมายคือการขยายรั้งไม่บันทึกหน่วยความจำมากเมื่อเทียบกับseqช่วงที่มีขนาดใหญ่ เช่นecho {1..1000000} | wcแสดงให้เห็นว่าเสียงสะท้อนผลิต 1 บรรทัดล้านคำและ 6,888,896 ไบต์ การลองใช้จะseq 1 1000000 | wcให้ผลลัพธ์เป็นล้านบรรทัดคำล้านคำและ 6,888,896 ไบต์และเร็วกว่าเจ็ดเท่าตามที่วัดโดยtimeคำสั่ง
George

หมายเหตุ: ฉันได้กล่าวถึงwhileวิธีPOSIX ก่อนหน้านี้ในคำตอบของฉัน: stackoverflow.com/a/31365662/895245แต่ดีใจที่คุณเห็นด้วย :-)
Ciro Santilli 冠状病毒审查审查六四事件法轮功

ฉันได้รวมคำตอบนี้ไว้ในคำตอบการเปรียบเทียบประสิทธิภาพด้านล่างแล้ว stackoverflow.com/a/54770805/117471 (นี่เป็นบันทึกสำหรับตัวเองที่ฉันต้องทำ)
Bruno Bronosky

@mate หรือฉันคิดว่ารูปแบบ C สำหรับการประเมินลูปและเลขคณิตเป็นวิธีการแก้ปัญหาเดียวกัน ฉันพลาดอะไรไปรึเปล่า?
ออสการ์จาง

72

วิธี POSIX

หากคุณสนใจเกี่ยวกับการพกพาให้ใช้ตัวอย่างจากมาตรฐาน POSIX :

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

เอาท์พุท:

2
3
4
5

สิ่งที่ไม่ใช่ POSIX:


เพิ่งมีผู้โหวตเพิ่มขึ้น 4 คนสำหรับคำตอบนี้ซึ่งแปลกมาก หากสิ่งนี้ถูกโพสต์ในเว็บไซต์รวมลิงค์โปรดส่งลิงค์ให้ฉันไชโย
Ciro Santilli 法轮功病毒审查六四事件法轮功

เครื่องหมายคำพูดอ้างถึงxไม่ใช่นิพจน์ทั้งหมด $((x + 1))ไม่เป็นไร
chepner

ในขณะที่ไม่พกพาและแตกต่างจาก GNU seq(BSD seqช่วยให้คุณสามารถตั้งค่าสตริงการยกเลิกลำดับด้วย-t) FreeBSD และ NetBSD ยังมีseqตั้งแต่ 9.0 และ 3.0 ตามลำดับ
Adrian Günter

@CiroSantilli @chepner $((x+1))และ$((x + 1))แยกตรงเดียวกันเช่นเมื่อแยกวิเคราะห์ tokenizes x+1มันจะแยกออกเป็น 3 ราชสกุล: x, และ+ ไม่ใช่โทเค็นตัวเลขที่ถูกต้อง แต่เป็นโทเค็นชื่อตัวแปรที่ถูกต้อง แต่ยังไม่ได้ดังนั้นการแยก เป็นโทเค็นตัวดำเนินการทางคณิตศาสตร์ที่ถูกต้อง แต่ยังไม่ได้ดังนั้นโทเค็นจะถูกแยกอีกครั้ง และอื่น ๆ 1xx+++1
Adrian Günter

ฉันได้รวมคำตอบนี้ไว้ในคำตอบการเปรียบเทียบประสิทธิภาพด้านล่างแล้ว stackoverflow.com/a/54770805/117471 (นี่เป็นบันทึกสำหรับตัวเองที่ฉันต้องทำ)
Bruno Bronosky

35

อีกชั้นของการอ้อม:

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

2
+1: ประเมิน 'สำหรับฉันใน {1 .. ' $ END '}; ทำ ... 'ดูเหมือนว่าจะเป็นวิธีธรรมชาติในการแก้ปัญหานี้
William Pursell

28

คุณสามารถใช้ได้

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

seq เกี่ยวข้องกับการดำเนินการของคำสั่งภายนอกซึ่งมักจะทำให้สิ่งต่าง ๆ ช้าลง
paxdiablo

9
ไม่เกี่ยวข้องกับการดำเนินการคำสั่งภายนอกสำหรับการวนซ้ำแต่ละครั้งเพียงครั้งเดียว หากเวลาในการเรียกใช้คำสั่งภายนอกหนึ่งรายการเป็นปัญหาแสดงว่าคุณใช้ภาษาผิด
Mark Baker

1
ดังนั้นการทำรังเป็นกรณีเดียวที่เรื่องนี้? ฉันสงสัยว่ามีความแตกต่างด้านประสิทธิภาพหรือไม่หรือมีผลข้างเคียงทางเทคนิคที่ไม่ทราบแน่ชัด?
Sqeaky

@Squeaky นั่นเป็นคำถามแยกต่างหากซึ่งมีคำตอบอยู่ที่นี่: stackoverflow.com/questions/4708549//
tripleee

ฉันได้รวมคำตอบนี้ไว้ในคำตอบการเปรียบเทียบประสิทธิภาพด้านล่างแล้ว stackoverflow.com/a/54770805/117471 (นี่เป็นบันทึกสำหรับตัวเองที่ฉันต้องทำ)
Bruno Bronosky

21

หากคุณต้องการคำนำหน้ามันมากกว่าที่คุณอาจชอบ

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

ที่จะให้ผล

07
08
09
10
11
12

4
จะไม่printf "%02d\n" $iง่ายกว่านี้printf "%2.0d\n" $i |sed "s/ /0/"ไหม
zb226


17

วิธีนี้ใช้ได้ผลดีในbash:

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

6
echo $((i++))ทำงานและรวมเข้ากับหนึ่งบรรทัด
Bruno Bronosky

1
นี่มีส่วนขยายทุบตีที่ไม่จำเป็น รุ่น POSIX: stackoverflow.com/a/31365662/895245
Ciro Santilli 法轮功病毒审查审查六四事件法轮功

1
@Ciro เนื่องจากคำถามระบุว่าทุบตีโดยเฉพาะและมีแท็กทุบตีฉันคิดว่าคุณอาจพบว่าทุบตี 'ส่วนขยาย' นั้นดีกว่าโอเค :-)
paxdiablo

@paxdiablo ผมไม่ได้หมายความว่ามันไม่ถูกต้อง แต่ทำไมไม่เป็นแบบพกพาเมื่อเราสามารถ ;-)
Ciro Santilli冠状病毒审查六四事件法轮功

ในbashเราสามารถทำได้โดยwhile [[ i++ -le "$END" ]]; doการเพิ่ม (หลัง) ในการทดสอบ
Aaron McDaid

14

ฉันได้รวบรวมแนวคิดสองสามข้อที่นี่และประสิทธิภาพที่วัดได้

TL; DR ซื้อกลับบ้าน:

  1. seqและ{..}รวดเร็วจริงๆ
  2. forและwhileลูปช้า
  3. $( ) ช้า
  4. for (( ; ; )) ลูปช้ากว่า
  5. $(( )) ช้ากว่า
  6. การกังวลเกี่ยวกับตัวเลขNในหน่วยความจำ (seq หรือ {.. }) นั้นโง่ (อย่างน้อยที่สุด 1 ล้าน)

เหล่านี้ไม่ได้ข้อสรุป คุณจะต้องดูรหัส C ด้านหลังแต่ละข้อเพื่อสรุป นี่เป็นข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่เราใช้กลไกเหล่านี้ในการวนซ้ำโค้ด การปฏิบัติการเดี่ยวส่วนใหญ่นั้นใกล้เคียงกับความเร็วเดียวกันกับที่มันไม่สำคัญในกรณีส่วนใหญ่ แต่กลไกเช่นfor (( i=1; i<=1000000; i++ ))นั้นมีหลายวิธีที่คุณสามารถมองเห็นได้ นอกจากนี้ยังมีการดำเนินงานอื่น ๆ อีกมากมายต่อห่วงfor i in $(seq 1 1000000)กว่าที่คุณได้รับจาก และนั่นอาจไม่ชัดเจนสำหรับคุณซึ่งเป็นสาเหตุที่ทำแบบทดสอบนี้มีค่า

สาธิตการใช้งาน

# 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<=1000000; i++ )) ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m12.391s
user    0m11.069s
sys     0m3.437s

# show that arithmetic expansion is even slower
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; i=$(($i+1)); done | wc)
 1000000 1000000 6888896

real    0m19.696s
user    0m18.017s
sys     0m3.806s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)
 1000000 1000000 6888896

real    0m18.629s
user    0m16.843s
sys     0m3.936s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)
 1000000 1000000 6888896

real    0m17.012s
user    0m15.319s
sys     0m3.906s

# even a noop is slow
$ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)
       0       0       0

real    0m12.679s
user    0m11.658s
sys 0m1.004s

1
ดี! ไม่เห็นด้วยกับการสรุปของคุณ หน้าตาที่ผมชอบเป็นเรื่องเกี่ยวกับความเร็วเดียวกับ$(seq) {a..b}นอกจากนี้การดำเนินการแต่ละอย่างใช้เวลาประมาณเดียวกันดังนั้นเพิ่มประมาณ4μsให้กับการวนซ้ำแต่ละรอบสำหรับฉัน ที่นี่การดำเนินการเป็นเสียงสะท้อนในร่างกาย, การเปรียบเทียบทางคณิตศาสตร์, การเพิ่มขึ้น, ฯลฯ เป็นสิ่งที่น่าแปลกใจนี้หรือไม่? ใครสนใจว่าต้องใช้เวลานานเท่าใดในการทำงานของลูปเพื่อทำงาน - รันไทม์มีแนวโน้มที่จะถูกครอบงำโดยเนื้อหาของลูป
bobbogo

@bbbogo คุณพูดถูกมันเป็นเรื่องของการดำเนินการ ฉันอัปเดตคำตอบเพื่อสะท้อนสิ่งนี้ การโทรจำนวนมากที่เราดำเนินการจริงเกินกว่าที่เราคาดไว้ ฉันทำให้มันแคบลงจากรายการการทดสอบประมาณ 50 รายการที่ฉันวิ่ง ฉันคาดหวังว่างานวิจัยของฉันจะโง่เกินไปสำหรับคนกลุ่มนี้ และเช่นเคยฉันขอแนะนำให้คุณจัดลำดับความสำคัญในการเขียนโปรแกรมดังนี้: ทำให้สั้นลง ทำให้อ่านง่าย ทำให้เร็วขึ้น ทำให้พกพาได้ บ่อยครั้งที่ # 1 ทำให้เกิด # 3 อย่าเสียเวลาใน # 4 จนกว่าคุณจะต้อง
Bruno Bronosky

8

ฉันรู้ว่าคำถามนี้เกี่ยวกับbashแต่สำหรับบันทึกksh93เป็นอย่างชาญฉลาดและดำเนินการตามที่คาดไว้:

$ 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}

8

นี่เป็นอีกวิธีหนึ่ง:

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

1
นี่เป็นโอเวอร์เฮดของการวางไข่ของกระสุนอีกอัน
codeforester

1
ที่จริงแล้วมันแย่มากเป็นพิเศษเพราะมันวางไข่ 2 เม็ดเมื่อ 1 พอเพียง
Bruno Bronosky

8

หากคุณต้องการที่จะอยู่ใกล้เคียงเป็นไปได้ที่จะรั้งไวยากรณ์แสดงออกที่ลองrangeฟังก์ชั่นจากทุบตีเทคนิคrange.bash

ตัวอย่างเช่นทั้งหมดต่อไปนี้จะทำสิ่งเดียวกันecho {1..10}:

source range.bash
one=1
ten=10

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

มันพยายามที่จะสนับสนุนไวยากรณ์ bash ดั้งเดิมที่มี "gotchas" น้อยที่สุดเท่าที่จะเป็นไปได้: ไม่เพียง แต่รองรับตัวแปรเท่านั้น แต่ยังมีการป้องกันพฤติกรรมที่ไม่พึงประสงค์ของช่วงที่ไม่ถูกต้องซึ่งจัดเป็นสตริง (เช่นfor i in {1..a}; do echo $i; done)

คำตอบอื่น ๆ จะใช้งานได้ในกรณีส่วนใหญ่ แต่ทั้งหมดมีข้อเสียอย่างน้อยหนึ่งข้อต่อไปนี้:

  • หลายคนใช้subshellsซึ่งอาจเป็นอันตรายต่อประสิทธิภาพการทำงานและอาจเป็นไปไม่ได้ในบางระบบ
  • หลายคนพึ่งพาโปรแกรมภายนอก แม้seqจะเป็นไบนารีซึ่งจะต้องติดตั้งเพื่อใช้งานจะต้องโหลดโดย bash และต้องมีโปรแกรมที่คุณคาดไว้เพื่อให้มันทำงานได้ในกรณีนี้ แพร่หลายหรือไม่นั้นเป็นสิ่งที่ต้องพึ่งพามากกว่าเพียงแค่ภาษา Bash เท่านั้น
  • โซลูชั่นที่ใช้ฟังก์ชั่น Bash แบบเนทีฟเท่านั้นเช่น @ ephemient's จะไม่ทำงานในช่วงตัวอักษรเช่น{a..z}; รั้งการขยายตัวจะ คำถามคือเกี่ยวกับช่วงของตัวเลขแต่นี่คือการพูดคลุมเครือ
  • ส่วนใหญ่ไม่ได้มีลักษณะคล้ายกับ{1..10}ไวยากรณ์ของช่วงที่มีการขยายรั้งดังนั้นโปรแกรมที่ใช้ทั้งคู่อาจอ่านยากขึ้นเล็กน้อย
  • คำตอบของ @ bobbogo ใช้ไวยากรณ์ที่คุ้นเคยบางอย่าง แต่ทำสิ่งที่ไม่คาดคิดหาก$ENDตัวแปรนั้นไม่ใช่ช่วงที่ถูกต้อง "bookend" สำหรับอีกด้านหนึ่งของช่วง END=aตัวอย่างเช่นหากข้อผิดพลาดจะไม่เกิดขึ้นและค่าคำต่อคำ{1..a}จะถูกสะท้อน นี่เป็นพฤติกรรมเริ่มต้นของ Bash เช่นกัน - บ่อยครั้งที่ไม่คาดคิด

ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียนรหัสที่เชื่อมโยง


7

แทนที่{}ด้วย(( )):

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

อัตราผลตอบแทน:

0
1
2
3
4

ฉันได้รวมคำตอบนี้ไว้ในคำตอบการเปรียบเทียบประสิทธิภาพด้านล่างแล้ว stackoverflow.com/a/54770805/117471 (นี่เป็นบันทึกสำหรับตัวเองที่ฉันต้องทำ)
Bruno Bronosky

6

สิ่งเหล่านี้เป็นสิ่งที่ดี แต่ seq ถูกคัดค้านและคาดว่าจะใช้ได้กับช่วงที่เป็นตัวเลขเท่านั้น

หากคุณใส่ for for loop ด้วยเครื่องหมายอัญประกาศคู่ตัวแปรเริ่มต้นและสิ้นสุดจะถูกยกเลิกเมื่อคุณสะท้อนสตริงและคุณสามารถส่งสตริงกลับไปยัง BASH เพื่อดำเนินการได้ $iต้องมีการหลบหนีด้วย \ ดังนั้นจึงไม่ได้รับการประเมินก่อนที่จะถูกส่งไปยัง subshell

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

เอาต์พุตนี้ยังสามารถกำหนดให้กับตัวแปร:

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

"โอเวอร์เฮด" เท่านั้นที่ควรสร้างควรเป็นอินสแตนซ์ที่สองของการทุบตีดังนั้นจึงควรเหมาะสำหรับการใช้งานที่หนักหน่วง


5

หากคุณกำลังทำคำสั่งเชลล์และคุณ (เช่นฉัน) มีเครื่องรางสำหรับการวางท่อสิ่งนี้ดีมาก:

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


3

มีหลายวิธีในการทำเช่นนี้อย่างไรก็ตามวิธีที่ฉันชอบมีให้ด้านล่าง

การใช้ seq

เรื่องย่อจาก man seq

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

วากยสัมพันธ์

คำสั่งแบบเต็ม
seq first incr last

  • แรกคือหมายเลขเริ่มต้นในลำดับ [เป็นตัวเลือกโดยค่าเริ่มต้น: 1]
  • incr จะเพิ่มขึ้น [เป็นทางเลือกโดยค่าเริ่มต้น: 1]
  • สุดท้ายคือหมายเลขสุดท้ายในลำดับ

ตัวอย่าง:

$ seq 1 2 10
1 3 5 7 9

เฉพาะกับตัวแรกและตัวสุดท้าย:

$ seq 1 5
1 2 3 4 5

สุดท้ายเท่านั้น:

$ seq 5
1 2 3 4 5

การใช้ {first..last..incr}

ที่นี่เป็นครั้งแรกและครั้งสุดท้ายมีผลบังคับใช้และรวมเป็นตัวเลือก

ใช้เพียงครั้งแรกและครั้งสุดท้าย

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

ใช้ incr

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

คุณสามารถใช้สิ่งนี้ได้แม้กับตัวละครด้านล่าง

$ 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

3

หากคุณไม่ต้องการใช้รูปแบบการขยาย' seq' หรือ ' eval' หรือjotหรือการคำนวณทางคณิตศาสตร์เช่น for ((i=1;i<=END;i++))หรือลูปอื่น ๆ เช่น whileและคุณไม่ต้องการ ' printf' และยินดีที่จะ ' echo' เท่านั้นการแก้ไขปัญหาง่าย ๆ นี้อาจเหมาะกับงบประมาณของคุณ:

a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash

PS: ทุบตีของฉันไม่ได้มีseqคำสั่ง ''

ทดสอบบน Mac OSX 10.6.8, Bash 3.2.48


0

การทำงานใน Bash และ Korn สามารถไปจากตัวเลขที่สูงขึ้นและต่ำลงได้ อาจไม่เร็วที่สุดหรือสวยที่สุด แต่ทำงานได้ดีพอ จัดการกับเนกาทีฟด้วย

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
   elif (( ${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"
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.