สำหรับการวนซ้ำด้วยตัวอักษร


12

มันทำงานได้อย่างสมบูรณ์บน OSX

#!/bin/bash
chars=( {a..z} )
n=3
for ((i=0; i<n; i++))
do
  echo "${chars[i]}"
done

แต่เมื่อฉันเรียกใช้บน Ubuntu ฉันได้รับข้อผิดพลาดต่อไปนี้

ForLoopAlphabetTest.sh: 2: ForLoopAlphabetTest.sh: Syntax error: "(" unexpected

ฉันดูเหมือนจะไม่สามารถแก้ปัญหาได้ ข้อเสนอแนะใด ๆ


2
สิ่งนี้ใช้ได้กับ Ubuntu
Pilot6

ฉันไม่สามารถใช้ 16.04 bash 4.3 เป็นสคริปต์ได้ แต่มันจะทำงานถ้าฉันคัดลอกมันไปยังเทอร์มินัล
denski

คำตอบ:


25

สมมุติว่าคุณกำลังเรียกใช้สคริปต์เป็น:

sh ForLoopAlphabetTest.sh

ในอูบุนตูshเป็น symlinked ไปdash; เป็นแนวคิดของอาร์เรย์ไม่มีคุณจะได้รับข้อผิดพลาดไวยากรณ์สำหรับdash(

สคริปต์ทำงานได้อย่างสมบูรณ์แบบbashดังนั้นจึงจะดีถ้าคุณเรียกใช้เป็นbashอาร์กิวเมนต์:

bash ForLoopAlphabetTest.sh

ตอนนี้คุณมีbashshebang บนสคริปต์ดังนั้นคุณสามารถทำให้สคริปต์เรียกใช้ ( chmod u+x ForLoopAlphabetTest.sh) และเรียกใช้เป็น:

/path/to/ForLoopAlphabetTest.sh

หรือจากไดเรกทอรีของสคริปต์:

./ForLoopAlphabetTest.sh

นอกจากนี้โปรดทราบว่าสคริปต์ของคุณมีการขยายรั้ง{a..z}และforโครงสร้างสไตล์ C : for (( ... ))ซึ่งยังไม่ได้รับการสนับสนุนโดยdash; ดังนั้นหากเป้าหมายของคุณคือการพกพาคุณควรดูshไวยากรณ์POSIX เท่านั้น


ขอขอบคุณ. มีวิธีหลีกเลี่ยงการขาดแนวคิดของอาเรย์หรือไม่?
denski

3
@denski หากคุณต้องการเขียนสคริปต์แบบพกพาที่สามารถทำงานได้/bin/shบนระบบปฏิบัติการแบบ Unix ใด ๆ คุณจะไม่สามารถใช้อาร์เรย์ได้ Bash (และกระสุนอื่น ๆ ) เพิ่มเข้ามาเพราะมันสะดวกมากและไม่สามารถแทนที่ด้วยรหัสแบบพกพาได้ง่ายกว่าเสมอ อย่างไรก็ตามสำหรับสคริปต์ของคุณโดยเฉพาะคุณสามารถทำได้โดยไม่มีปัญหาและไม่ต้องใช้คุณสมบัติเฉพาะของ bash คุณสนใจที่จะทำเช่นนั้น?
Eliah Kagan

หากคุณมีข้อเสนอแนะการอ่านที่จะเป็นประโยชน์ ขอบคุณ
denski

1
@denski ฉันได้โพสต์คำตอบซึ่งมีลิงค์และตัวอย่าง ในความคิดเห็นก่อนหน้าของฉันที่นี่ฉันได้กล่าวว่าคุณใช้อาร์เรย์และ C-style สำหรับลูป แต่ไม่ได้พูดถึงการใช้การขยายรั้งของคุณ คำตอบของฉันครอบคลุมวิธีการทำโดยไม่ต้องทั้งสาม โปรดทราบว่าคำตอบนี้ (เช่น heemayl ไม่ใช่ไม่ใช่ของฉัน) เป็นวิธีการแก้ปัญหาหลักของคุณ ฉันมุ่งเน้นไปที่วิธีที่คุณอาจเขียนสคริปต์ของคุณใหม่หากคุณไม่สามารถพึ่งพาคุณลักษณะเฉพาะของ bash ได้
Eliah Kagan

@ heemayl สำหรับบันทึกฉันต้องการเพิ่มว่าคุณถูกต้องในข้อสันนิษฐานของคุณว่าฉันกำลังเรียกใช้สคริปต์ด้วยsh
denski

10

สคริปต์ของคุณใช้คุณสมบัติสามอย่างของ Bash shell ซึ่งไม่ได้จัดทำโดยเชลล์สไตล์ Bourne ทั้งหมด ในฐานะที่เป็นheemayl บอกว่าคุณก็สามารถเรียกใช้สคริปต์ที่มีแทนbash shคุณhashbangบรรทัดที่ด้านบน ( #!/bin/bash) ระบุbashแต่จะมีผลเฉพาะถ้าคุณรันสคริปต์ที่เป็นheemayl อธิบาย หากคุณส่งชื่อของสคริปต์เพื่อsh, shจะไม่เรียกโดยอัตโนมัติbashแต่ก็จะเรียกใช้สคริปต์ เนื่องจากเมื่อสคริปต์ของคุณทำงานจริงบรรทัด hashbang จะไม่มีผลใด

ทางเลือกอื่น ๆ ของคุณหากคุณต้องการเขียนสคริปต์แบบพกพาที่ไม่ขึ้นอยู่กับคุณสมบัติของ Bash คือการเปลี่ยนสคริปต์ของคุณเพื่อให้สคริปต์ทำงานได้โดยไม่ต้องใช้สคริปต์ คุณสมบัติ Bash ที่คุณใช้คือ:

  • อาร์เรย์ นี้มาก่อนดังนั้นนี่คือสิ่งที่ผลิตข้อผิดพลาดเมื่อคุณพยายามที่จะเรียกใช้สคริปต์ของคุณกับเปลือกชน นิพจน์เครื่องหมายวงเล็บ( {a..z} )ซึ่งคุณกำหนดให้charsสร้างอาร์เรย์และ${chars[i]}ซึ่งปรากฏในลูปของคุณจะจัดทำดัชนีในอาร์เรย์
  • การขยายรั้ง ในทุบตีและยังอยู่ในเปลือกหอยอื่น ๆ อีกมากมายมีการขยายไปยัง{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อย่างไรก็ตามนี่ไม่ใช่คุณสมบัติสากล (หรือเป็นมาตรฐาน) ของเชลล์สไตล์ Bourne และ Dash ไม่รองรับ
  • ไวยากรณ์ C- สไตล์ทางเลือก - forลูป แม้ว่าจะขึ้นอยู่กับการขยายตัวทางคณิตศาสตร์ซึ่งไม่ได้ระบุเฉพาะกับ Bash (แม้ว่าบางเชลล์ที่เก่าและไม่ใช่ POSIX ที่เข้ากันไม่ได้ก็ตาม) C-style forloop เป็น Bash-ism และไม่สามารถพกพาไปได้อย่างกว้างขวาง กระสุนอื่น ๆ

Bash พร้อมใช้งานอย่างกว้างขวางโดยเฉพาะอย่างยิ่งในระบบ GNU / Linux เช่น Ubuntu และ (ตามที่คุณเห็น) ยังมีอยู่ใน macOS และระบบอื่น ๆ อีกมากมาย เมื่อพิจารณาว่าคุณใช้คุณลักษณะเฉพาะของ Bash คุณอาจต้องการใช้คุณลักษณะเหล่านั้นและตรวจสอบให้แน่ใจว่าคุณกำลังใช้ Bash (หรือเชลล์อื่น ๆ ที่สนับสนุนคุณสมบัติที่คุณใช้) เมื่อคุณเรียกใช้สคริปต์

อย่างไรก็ตามคุณสามารถแทนที่ด้วยโครงสร้างแบบพกพาได้หากต้องการ อาเรย์และ C-style forloop นั้นง่ายต่อการเปลี่ยน การสร้างช่วงของตัวอักษรโดยไม่มีการขยายรั้ง (และไม่มีการเข้ารหัสอย่างหนักในสคริปต์ของคุณ) เป็นส่วนหนึ่งที่ค่อนข้างยุ่งยาก


ก่อนอื่นนี่คือสคริปต์ที่พิมพ์ตัวอักษรละตินตัวพิมพ์เล็กทั้งหมด:

#!/bin/sh

for i in $(seq 97 122); do
    printf "\\$(printf %o $i)\n"
done
  • seqคำสั่งสร้างลำดับตัวเลข $( )ดำเนินการคำสั่งเปลี่ยนตัวจึงถูกแทนที่ด้วยการส่งออกของ$(seq 97 122) seq 97 122เหล่านี้เป็นรหัสอักขระสำหรับผ่านaz
  • printfคำสั่งที่มีประสิทธิภาพสามารถเปลี่ยนรหัสตัวอักษรให้เป็นตัวอักษร (เช่นprintf '\141'พิมพ์aตามด้วยบรรทัดใหม่ ) แต่รหัสจะต้องอยู่ในรูปฐานแปดในขณะที่seqเอาท์พุทเป็นทศนิยมเท่านั้น ดังนั้นฉันจึงใช้printfสองครั้ง: Inner printf %o $iแปลงตัวเลขทศนิยม (ให้โดยseq) เป็นฐานแปดและแทนที่ด้วยprintfคำสั่งouter (แม้ว่าจะเป็นไปได้ที่จะใช้เลขฐานสิบหกแต่ก็ไม่ง่ายและดูเหมือนจะพกพาได้น้อยลง )
  • printfตีความ\ตามด้วยหมายเลขฐานแปดเป็นอักขระที่มีรหัสนั้นและ\nเป็นบรรทัดใหม่ แต่เชลล์ยังใช้\เป็นอักขระยกเว้น \ในด้านหน้าของ$จะป้องกันไม่ให้$จากที่ก่อให้เกิดการขยายตัวที่จะเกิดขึ้น (ในกรณีนี้แทนคำสั่ง ) แต่ฉันไม่ต้องการที่จะป้องกันไม่ให้ดังนั้นฉันได้หนีด้วยอื่น\; \\นั่นคือเหตุผลที่ ที่สอง\ก่อนที่จะnไม่จำเป็นที่จะต้องหนีเพราะไม่เหมือน\$, \nไม่ได้มีความหมายพิเศษกับเปลือกในสตริงยกมาสองครั้ง
  • สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการและคำพูดทับขวาคู่ถูกนำมาใช้ในการเขียนโปรแกรมเปลือกดูส่วนที่เกี่ยวกับข้อความในมาตรฐานสากล ดูเพิ่มเติม3.1.2 Quotingในคู่มือการทุบตีอ้างอิงโดยเฉพาะอย่างยิ่ง3.1.2.1 ตัวหนีและ3.1.2.3 คำพูดคู่ (นี่คือส่วนทั้งหมดตามบริบท) โปรดทราบว่าเครื่องหมายอัญประกาศเดี่ยว ( ') เป็นส่วนสำคัญของไวยากรณ์ข้อความเปลือกหอยฉันไม่ได้ใช้มันในสคริปต์นั้น

สิ่งนี้สามารถพกพาไปได้กับระบบที่เหมือน Unix ส่วนใหญ่และไม่ได้ขึ้นอยู่กับเชลล์สไตล์ Bourne ที่คุณใช้ อย่างไรก็ตามระบบที่คล้าย Unix บางตัวไม่ได้seqติดตั้งไว้ตามค่าเริ่มต้น (มักจะใช้jotแทนซึ่งไม่ได้ติดตั้งตามค่าเริ่มต้นของระบบ GNU / Linux ส่วนใหญ่) คุณสามารถใช้การวนซ้ำด้วยexprหรือการแทนที่ทางคณิตศาสตร์เพื่อเพิ่มความสะดวกในการพกพาเพิ่มเติมหากคุณต้องการ:

#!/bin/sh

i=97
while [ $i -le 122 ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

ที่ใช้while-loop กับคำสั่งเพื่อดำเนินการต่อบ่วงเฉพาะเมื่ออยู่ในช่วง[$i


แทนที่จะพิมพ์ตัวอักษรทั้งหมดสคริปต์ของคุณจะกำหนดตัวแปรnและพิมพ์$nตัวอักษรพิมพ์เล็กตัวแรก นี่คือเวอร์ชันของสคริปต์ของคุณที่ไม่ขึ้นอยู่กับฟีเจอร์เฉพาะของ Bash และใช้งานได้บน Dash แต่ต้องการseq:

#!/bin/sh

n=3 start=97
for i in $(seq $start $((start + n - 1))); do
    printf "\\$(printf %o $i)\n"
done

การปรับค่าnการเปลี่ยนแปลงจำนวนตัวอักษรที่พิมพ์เช่นเดียวกับในสคริปต์ของคุณ

นี่คือรุ่นที่ไม่ต้องการseq:

#!/bin/sh

n=3 i=97 stop=$((i + n))
while [ $i -lt $stop ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

มี$stopหนึ่งสูงกว่ารหัสตัวอักษรของตัวอักษรสุดท้ายที่ควรจะพิมพ์ดังนั้นฉันใช้-lt(น้อยกว่า) มากกว่า-le(น้อยกว่าหรือเท่ากับ) ด้วย[คำสั่ง (มันจะต้องทำงานstop=$((i + n - 1))และใช้งานด้วย[ $i -le $stop ])


1
นี่เป็นคำตอบที่ละเอียดยิ่งกว่าการศึกษา ฉันเป็นผู้เริ่มต้นมากดังนั้นวิธีการเขียนสคริปต์ของฉันคือการนำองค์ประกอบการทำงานที่พบบนอินเทอร์เน็ตมารวมกันจนกว่าจะได้ผล ฉันไม่สงสัยเลยว่ามี 1) ดีกว่าและ 2) วิธีที่ง่ายกว่าในการทำสิ่งต่าง ๆ ที่ฉันสร้างด้วยสคริปต์
denski

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.