วิ่งผ่านสองฉากในหนึ่งวง


8

ฉันพยายามที่จะทำงานผ่านสองลำดับในวงเดียวกันในเปลือกของฉันเช่นด้านล่าง:

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

ความคิดใดที่ฉันสามารถบรรลุสิ่งนี้


@zanna - ความคิดแรกของฉันคือบูลีน "และ" พิเศษเฉพาะซึ่งหมายความว่าผลลัพธ์คือตัวเลขที่มีอยู่ในทั้งสองชุด ซึ่งไม่มีในกรณีนี้ มี "และ" รวม?
ravery

1
@ ทุกอย่างที่ฉันใส่ "และ" เพียงเพื่ออธิบายสิ่งที่ฉันกำลังมองหา
HISI

2
@YassineSihi - จดบันทึกไว้อย่างดี โปรแกรมเมอร์ใหม่หลายคนสะดุดในจุดนี้จนกว่าพวกเขาจะสามารถฝึกสมองได้อีกครั้งเพราะภาษาพูดใช้ "และ" รวม แต่ตรรกะ "และ" เป็นพิเศษในภาษาโปรแกรมส่วนใหญ่
ravery

คำตอบ:


10

คุณต้องการเพียงการขยายรั้งสำหรับสิ่งนั้น

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

เราสามารถส่งรายการไปยังfor( )for i in x y z; do stuff "$i"; done

ดังนั้นที่นี่วงเล็บปีกกา{ }จะได้รับเปลือกเพื่อขยายลำดับของคุณลงในรายการ คุณจะต้องเว้นช่องว่างระหว่างมันเนื่องจากเชลล์แยกรายการของอาร์กิวเมนต์บนสิ่งเหล่านั้น


ใช่วงเล็บปีกกา . . และคุณไม่จำเป็นต้องวนซ้ำสำหรับสิ่งนั้น ^ _0
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy ฉันคิดว่าพวกเขาไม่เพียงต้องการechoตัวเลข
Zanna

ใช่ถ้าพวกเขาต้องการการกระทำบางอย่างเช่นtouchไฟล์พวกเขาสามารถทำได้touch {1..15}.txt {20..25}.txtโดยไม่ต้องวนซ้ำที่นี่ แต่แน่นอนว่ามันเป็นการกระทำหลายอย่างในหมายเลขเดียวกัน - ตกลงนั่นอาจใช้การวนซ้ำ
Sergiy Kolodyazhnyy

6

หรือเราสามารถใช้seq( พิมพ์ลำดับตัวเลข ) ต่อไปนี้เป็นตัวอย่างที่เทียบเท่าสองรายการ:

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

หากเป็นสคริปต์สำหรับงานซ้ำ ๆ คุณสามารถใช้ฟังก์ชัน:

#!/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"

4

คำตอบของ Zannaและคำตอบของpa4080นั้นทั้งดีและฉันอาจจะไปกับพวกเขาในสถานการณ์ส่วนใหญ่ บางทีมันอาจจะไปโดยไม่บอก แต่เพื่อความสมบูรณ์ฉันจะบอกว่าต่อไป: คุณสามารถโหลดแต่ละค่าลงในอาร์เรย์แล้ววนรอบอาร์เรย์ ตัวอย่างเช่น:

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

@SergiyKolodyazhnyy: ขอบคุณความคิดเห็น ฉันแก่พอที่เป็นวิธีการสอนของฉันและมักจะทำในโอกาสที่หายากที่ฉันเขียนเชลล์สคริปต์ อย่างไรก็ตามฉันได้อัปเดตคำตอบเพื่อใช้อาร์เรย์
GreenMatt

ดีมาก ! มีความสุขในการเขียนสคริปต์!
Sergiy Kolodyazhnyy

3

วนลูปโดยไม่ต้องห่วง

คำตอบของ Zanna นั้นถูกต้องและเหมาะสมสำหรับการทุบตี แต่เราสามารถปรับปรุงให้ดียิ่งขึ้นโดยไม่ต้องใช้ลูป

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

พฤติกรรมของprintfเป็นเช่นนั้นถ้าจำนวนARGUMENTSมากกว่าการควบคุมรูปแบบใน'FORMAT STRING'นั้นprintfจะแบ่งทั้งหมดARGUMENTS เป็นชิ้นเท่ากันและให้เหมาะสมกับสตริงรูปแบบซ้ำไปซ้ำมาจนหมดARGUMENTSรายการ

หากเรามุ่งมั่นในการพกพาเราสามารถใช้ประโยชน์printf "%d\n" $(seq 1 15) $(seq 20 25)แทน

มาสนุกกันเถอะ สมมติว่าเราต้องการดำเนินการมากกว่าแค่พิมพ์ตัวเลข touch {1..15}.txt {20..25}.txtสำหรับการสร้างไฟล์ออกจากลำดับของตัวเลขที่เราสามารถทำได้ จะเป็นอย่างไรถ้าเราต้องการให้มีหลายสิ่งเกิดขึ้น เราสามารถทำอะไรเช่นนี้:

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

หรือถ้าเราต้องการทำให้เป็นโรงเรียนแบบเก่า:

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

ทางเลือกแบบพกพา แต่ verbose

หากเราต้องการสร้างโซลูชันสคริปต์ที่ทำงานกับเชลล์ที่ไม่มีการขยายรั้ง (ซึ่งเป็น{1..15} {20..25}ที่พึ่ง) เราสามารถเขียนวิในขณะที่วนรอบ:

#!/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

แน่นอนว่าวิธีนี้เป็นวิธีที่ละเอียดกว่าบางสิ่งอาจสั้นลง แต่ก็ใช้งานได้ ทดสอบกับksh, dash, และแน่นอนmkshbash


Bash C-style loop

แต่ถ้าเราต้องการสร้าง loop bash-specific (ไม่ว่าจะด้วยเหตุผลใดก็ตามไม่ใช่แค่พิมพ์ แต่ทำบางอย่างกับตัวเลขเหล่านั้น) เราสามารถทำเช่นนี้ได้ (โดยทั่วไปแล้วรุ่น C-loop ของโซลูชันพกพา):

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

หรือในรูปแบบที่อ่านได้มากขึ้น:

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

การเปรียบเทียบประสิทธิภาพของวิธีการวนลูปที่แตกต่างกัน

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

ทางเลือกที่ไม่ใช่เปลือก

เพียงเพราะเราสามารถแก้ปัญหางูหลามได้ที่นี่

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

หรือกับเปลือกเล็กน้อย:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF

1
ฉันเพิ่งทดสอบtouch $(printf "%d\n" {1..15} {20..25}):-)
pa4080

1
@ pa4080 จริงๆแล้วสำหรับbashคุณไม่จำเป็นต้อง$()มีแค่touch {1..15}.txt {20..25}.txt :) แต่แน่นอนเราสามารถใช้printf "%d\n{1..15} {20..25} ` xargsถ้าเราต้องการทำมากกว่าtouchไฟล์ มีหลายวิธีในการทำสิ่งต่าง ๆ และทำให้การเขียนสคริปต์สนุกมาก!
Sergiy Kolodyazhnyy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.