Bash เป็นภาษาการเขียนโปรแกรมที่ทรงพลัง ซึ่งออกแบบมาอย่างสมบูรณ์แบบสำหรับใช้กับบรรทัดคำสั่งและในเชลล์สคริปต์ ซีรีส์สามส่วนนี้ซึ่งอิงตามหลักสูตรการศึกษาด้วยตนเองของ Linux สามเล่มของฉัน สำรวจโดยใช้ Bash เป็นภาษาการเขียนโปรแกรมบนอินเทอร์เฟซบรรทัดคำสั่ง (CLI)
บทความแรกในชุดนี้สำรวจการเขียนโปรแกรมบรรทัดคำสั่งง่ายๆ ด้วย Bash รวมถึงการใช้ตัวแปรและตัวดำเนินการควบคุม บทความที่สองกล่าวถึงประเภทของตัวดำเนินการทางตรรกะของไฟล์ สตริง ตัวเลข และตรรกะอื่นๆ ที่ให้ตรรกะการควบคุมขั้นตอนการดำเนินการและการขยายเชลล์ประเภทต่างๆ ใน Bash บทความที่สาม (และสุดท้าย) นี้ตรวจสอบการใช้ลูปสำหรับการดำเนินการวนซ้ำประเภทต่างๆ และวิธีควบคุมลูปเหล่านั้น
ลูป
ภาษาการเขียนโปรแกรมทุกภาษาที่ฉันเคยใช้มีโครงสร้างลูปอย่างน้อยสองสามประเภทที่มีความสามารถหลากหลายในการดำเนินการซ้ำ ๆ ฉันใช้ for loop ค่อนข้างบ่อย แต่ฉันก็พบว่า while และ until loop มีประโยชน์เช่นกัน
สำหรับลูป
การใช้งาน Bash ของ สำหรับ ในความคิดของฉัน คำสั่งมีความยืดหยุ่นมากกว่าคำสั่งส่วนใหญ่เล็กน้อย เนื่องจากสามารถจัดการกับค่าที่ไม่ใช่ตัวเลขได้ ในทางตรงกันข้าม ตัวอย่างเช่น ภาษา C มาตรฐาน สำหรับ ลูปสามารถจัดการกับค่าตัวเลขเท่านั้น
โครงสร้างพื้นฐานของเวอร์ชัน Bash ของ สำหรับ คำสั่งง่ายๆ:
for Var in list1 ; do list2 ; done
ซึ่งแปลว่า: "สำหรับแต่ละค่าในรายการ 1 ให้ตั้งค่า $Var ไปที่ค่านั้นแล้วดำเนินการคำสั่งโปรแกรมในรายการที่ 2 โดยใช้ค่านั้น เมื่อใช้ค่าทั้งหมดใน list1 เสร็จแล้ว ให้ออกจากลูป" ค่าใน list1 อาจเป็นสตริงค่าที่เรียบง่ายและชัดเจน หรืออาจเป็นผลลัพธ์ของการแทนที่คำสั่ง บทความในชุด) ฉันใช้โครงสร้างนี้บ่อยๆ
หากต้องการลอง ตรวจสอบให้แน่ใจว่า ~/testdir ยังคงเป็นไดเร็กทอรีการทำงานปัจจุบัน (PWD) ทำความสะอาดไดเรกทอรี จากนั้นดูตัวอย่างเล็กน้อยของ สำหรับ วนซ้ำเริ่มต้นด้วยรายการค่าที่ชัดเจน รายการนี้เป็นการผสมผสานระหว่างค่าตัวเลขและตัวอักษร แต่อย่าลืมว่าตัวแปรทั้งหมดเป็นสตริงและสามารถดำเนินการได้เช่นเดียวกัน
[student@studentvm1 testdir]$ rm *
[student@studentvm1 testdir]$ for I in a b c d 1 2 3 4; ทำ echo $I; เสร็จแล้ว
a
b
c
d
1
2
3
4
นี่เป็นเวอร์ชันที่มีประโยชน์มากกว่าเล็กน้อยพร้อมชื่อตัวแปรที่มีความหมายมากกว่า:
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research; ทำ echo "แผนก $Dept"; เสร็จสิ้น
ฝ่ายทรัพยากรบุคคล
ฝ่ายขาย
ฝ่ายการเงิน
ฝ่ายเทคโนโลยีสารสนเทศของแผนก
วิศวกรรมภาควิชา
การบริหารภาควิชา
การวิจัยภาควิชา
สร้างไดเร็กทอรี (และแสดงข้อมูลความคืบหน้าขณะดำเนินการ):
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research; do echo "กำลังดำเนินการแผนก $Dept"; mkdir "$Dept" ; เสร็จแล้ว
ทำงานแผนกทรัพยากรบุคคล
ทำงานแผนกขาย
ทำงานแผนกการเงิน
ทำงานแผนกเทคโนโลยีสารสนเทศ
ทำงานแผนกวิศวกรรม
ทำงานแผนก ธุรการ
ทำงานวิจัยภาควิชา
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Administration
drwxrwxr- x 2 student student 4096 Apr 8 15:45 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Human Resources'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Information Technology'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:45 การขาย
$Dept ตัวแปรต้องอยู่ในเครื่องหมายคำพูดใน mkdir คำแถลง; มิฉะนั้น ชื่อแผนกที่มีสองส่วน (เช่น "เทคโนโลยีสารสนเทศ") จะถือว่าเป็นสองแผนกที่แยกจากกัน ซึ่งเน้นย้ำถึงแนวทางปฏิบัติที่ดีที่สุดที่ฉันชอบติดตาม:ชื่อไฟล์และไดเรกทอรีทั้งหมดควรเป็นคำเดียว แม้ว่าระบบปฏิบัติการสมัยใหม่ส่วนใหญ่สามารถจัดการกับช่องว่างในชื่อได้ แต่ผู้ดูแลระบบต้องทำงานพิเศษเพื่อให้แน่ใจว่ากรณีพิเศษเหล่านั้นได้รับการพิจารณาในสคริปต์และโปรแกรม CLI (ควรพิจารณาถึงแม้จะน่ารำคาญเพราะคุณไม่มีทางรู้ว่าจะมีไฟล์อะไรบ้าง)
ดังนั้น ลบทุกอย่างใน ~/testdir —อีกครั้ง—และทำอีกครั้ง:
[student@studentvm1 testdir]$ rm -rf *; ll
total 0
[student@studentvm1 testdir]$ for Dept in Human-Resources Sales Finance Information-Technology Engineering Administration Research; do echo "กำลังดำเนินการแผนก $Dept"; mkdir "$Dept" ; เสร็จสิ้น
ทำงานแผนกทรัพยากรบุคคล
ทำงานแผนกขาย
ทำงานที่แผนกการเงิน
ทำงานที่แผนกสารสนเทศ-เทคโนโลยี
ทำงานเกี่ยวกับแผนกวิศวกรรม
ทำงาน on Department Administration
Working on Department Research
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:52 ฝ่ายบริหาร
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Human-Resources
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Information-Technology
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:52 ยอดขาย
สมมติว่ามีคนขอรายการ RPM ทั้งหมดบนคอมพิวเตอร์ Linux เครื่องหนึ่งๆ และคำอธิบายสั้นๆ ของแต่ละรายการ สิ่งนี้เกิดขึ้นกับฉันเมื่อฉันทำงานให้กับรัฐนอร์ทแคโรไลนา เนื่องจากโอเพ่นซอร์สไม่ "ได้รับการอนุมัติ" ให้ใช้งานโดยหน่วยงานของรัฐในขณะนั้น และฉันใช้ Linux บนคอมพิวเตอร์เดสก์ท็อปเท่านั้น หัวหน้าที่มีผมบาง (PHB) จึงต้องการรายชื่อซอฟต์แวร์แต่ละชิ้นที่ติดตั้งบนคอมพิวเตอร์ของฉัน ดังนั้น ว่าพวกเขาสามารถ "อนุมัติ" ข้อยกเว้นได้
คุณจะเข้าใกล้สิ่งนั้นได้อย่างไร? นี่เป็นวิธีหนึ่งโดยเริ่มจากความรู้ที่ว่า rpm –qa คำสั่งให้คำอธิบายที่สมบูรณ์ของ RPM รวมถึงสองรายการที่ PHB ต้องการ:ชื่อซอฟต์แวร์และข้อมูลสรุปโดยย่อ
สร้างขึ้นเพื่อผลลัพธ์สุดท้ายทีละขั้นตอน ขั้นแรก แสดงรายการ RPM ทั้งหมด:
[student@studentvm1 testdir]$ rpm -qa
perl-HTTP-Message-6.18-3.fc29.noarch
perl-IO-1.39-427.fc29.x86_64
perl -Math-Complex-1.59-429.fc29.noarch
lua-5.3.5-2.fc29.x86_64
java-11-openjdk-headless-11.0.ea.28-2.fc29.x86_64
util-linux-2.32.1-1.fc29.x86_64
libreport-fedora-2.9.7-1.fc29.x86_64
rpcbind-1.2.5-0.fc29.x86_64
libsss_sudo-2.0.0-5.fc29.x86_64
libfontenc-1.1.3-9.fc29.x86_64
เพิ่ม จัดเรียง และ uniq คำสั่งเพื่อจัดเรียงรายการและพิมพ์รายการที่ไม่ซ้ำ (เนื่องจากเป็นไปได้ว่ามีการติดตั้ง RPM บางตัวที่มีชื่อเหมือนกัน):
[student@studentvm1 testdir]$ rpm -qa | เรียงลำดับ | uniq
a2ps-4.14-39.fc29.x86_64
aajohan-comfortaa-fonts-3.001-3.fc29.noarch
abattis-cantarell-fonts-0.111-1.fc29.noarch
abiword-3.0.2-13.fc29.x86_64
abrt-2.11.0-1.fc29.x86_64
abrt-addon-ccpp-2.11.0-1.fc29.x86_64
abrt-addon-coredump-helper-2.11.0-1.fc29.x86_64
abrt-addon-kerneloops-2.11.0-1.fc29.x86_64
abrt-addon-pstoreoops-2.11.0 -1.fc29.x86_64
abrt-addon-vmcore-2.11.0-1.fc29.x86_64
เนื่องจากรายการนี้ให้รายการ RPM ที่ถูกต้องที่คุณต้องการดู คุณสามารถใช้รายการนี้เป็นรายการอินพุตไปยังลูปที่จะพิมพ์รายละเอียดทั้งหมดของแต่ละ RPM ได้:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done
รหัสนี้สร้างข้อมูลมากกว่าที่คุณต้องการ โปรดทราบว่าการวนซ้ำเสร็จสมบูรณ์ ขั้นตอนต่อไปคือการดึงเฉพาะข้อมูลที่ PHB ร้องขอ ดังนั้น เพิ่ม egrep คำสั่งที่ใช้เลือก ^ชื่อ หรือ ^สรุป . กะรัต (^ ) ระบุจุดเริ่มต้นของบรรทัด ดังนั้นบรรทัดใด ๆ ที่มีชื่อหรือบทสรุปที่จุดเริ่มต้นของบรรทัดจะปรากฏขึ้น
[student@studentvm1 testdir]$ สำหรับ RPM ใน `rpm -qa | เรียงลำดับ | ยูนีค'; ทำ rpm -qi $RPM; เสร็จแล้ว | egrep -i "^Name|^Summary"
Name :a2ps
Summary :แปลงข้อความและไฟล์ประเภทอื่นๆ เป็น PostScript
Name :aajohan-comfortaa-fonts
Summary :Modern style true type font
Name :abattis-cantarell-fonts
Summary :Humanist sans serif font
Name :abiword
Summary :Word processing program
Name a t <:/>สรุป :เครื่องมือตรวจจับและรายงานจุดบกพร่องอัตโนมัติ
คุณสามารถลอง grep แทนที่จะเป็น egrep ในคำสั่งด้านบนแต่จะไม่ทำงาน คุณยังสามารถไพพ์เอาต์พุตของคำสั่งนี้ผ่าน น้อยกว่า กรองเพื่อสำรวจผลลัพธ์ ลำดับคำสั่งสุดท้ายมีลักษณะดังนี้:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary" > RPM-summary.txt
โปรแกรมบรรทัดคำสั่งนี้ใช้ไปป์ไลน์ การเปลี่ยนเส้นทาง และ สำหรับ วนซ้ำ—ทั้งหมดในบรรทัดเดียว มันเปลี่ยนเส้นทางผลลัพธ์ของโปรแกรม CLI เล็กๆ ของคุณไปยังไฟล์ที่สามารถใช้ได้ในอีเมลหรือเป็นอินพุตเพื่อวัตถุประสงค์อื่น
ขั้นตอนการสร้างโปรแกรมทีละขั้นตอนนี้ช่วยให้คุณเห็นผลลัพธ์ของแต่ละขั้นตอนและมั่นใจได้ว่าโปรแกรมจะทำงานตามที่คุณคาดหวังและให้ผลลัพธ์ที่ต้องการ
จากแบบฝึกหัดนี้ PHBs ได้รับรายการแพ็คเกจ RPM แยกกันมากกว่า 1,900 รายการ สงสัยจะไม่มีใครอ่านรายการ แต่ฉันให้สิ่งที่พวกเขาขอโดยครบถ้วน และฉันไม่เคยได้ยินคำอื่นใดจากพวกเขาเกี่ยวกับเรื่องนี้เลย
ลูปอื่นๆ
มีโครงสร้างลูปอีกสองประเภทใน Bash:ในขณะที่ และ จนถึง โครงสร้างซึ่งมีความคล้ายคลึงกันมากทั้งในด้านรูปแบบและการทำงาน ไวยากรณ์พื้นฐานของโครงสร้างลูปเหล่านี้เรียบง่าย:
while [ expression ] ; do list ; done
และ
until [ expression ] ; do list ; done
ตรรกะของข้อแรกอ่านว่า "ในขณะที่นิพจน์ประเมินว่าเป็นจริง ให้ดำเนินการรายการคำสั่งโปรแกรม เมื่อนิพจน์ประเมินว่าเป็นเท็จ ให้ออกจากลูป" และข้อที่สอง:"จนกว่านิพจน์จะประเมินว่าเป็นจริง ให้รันรายการคำสั่งโปรแกรม เมื่อนิพจน์ประเมินว่าเป็นจริง ให้ออกจากลูป"
ขณะวนรอบ
ในขณะที่ ลูปใช้เพื่อรันชุดคำสั่งโปรแกรมในขณะที่ (ตราบใดที่) นิพจน์เชิงตรรกะประเมินว่าเป็นจริง PWD ของคุณควรยังคงเป็น ~/testdir .
รูปแบบที่ง่ายที่สุดของ ในขณะที่ ลูปเป็นสิ่งที่ทำงานตลอดไป แบบฟอร์มต่อไปนี้ใช้คำสั่ง true เพื่อสร้างโค้ดส่งคืน "จริง" เสมอ คุณยังใช้ "1" ง่ายๆ ได้ด้วย ซึ่งก็ใช้ได้เหมือนกัน แต่นี่แสดงให้เห็นการใช้ข้อความจริง:
[student@studentvm1 testdir]$ X=0; ในขณะที่ [ จริง ]; ทำ echo $X; X=$((X+1)); เสร็จแล้ว | หัว
0
1
2
3
4
5
6
7
8
9
[student@studentvm1 testdir]$
โปรแกรม CLI นี้น่าจะสมเหตุสมผลกว่าเมื่อคุณได้ศึกษาส่วนต่างๆ ของโปรแกรมแล้ว ขั้นแรก ตั้งค่า $X เป็นศูนย์ในกรณีที่มีค่าเหลือจากโปรแกรมก่อนหน้าหรือคำสั่ง CLI จากนั้นเนื่องจากนิพจน์ตรรกะ [ จริง ] ประเมินเป็น 1 เสมอ ซึ่งจริงแล้ว รายการคำสั่งโปรแกรมระหว่าง ทำ และเสร็จแล้ว จะถูกดำเนินการตลอดไป—หรือจนกว่าคุณจะกด Ctrl+C หรือส่งสัญญาณ 2 ไปที่โปรแกรม คำแนะนำเหล่านี้เป็นการขยายเลขคณิตที่พิมพ์ค่าปัจจุบันของ $X แล้วเพิ่มขึ้นทีละหนึ่ง
หนึ่งในหลักการของ The Linux Philosophy for Sysadmins คือการมุ่งมั่นเพื่อความสง่างาม และวิธีหนึ่งในการบรรลุความสง่างามคือความเรียบง่าย คุณสามารถทำให้โปรแกรมนี้ง่ายขึ้นโดยใช้ตัวดำเนินการเพิ่มตัวแปร ++ . ในตัวอย่างแรก ค่าปัจจุบันของตัวแปรจะถูกพิมพ์ จากนั้นตัวแปรจะเพิ่มขึ้น ซึ่งระบุโดยการวาง ++ โอเปอเรเตอร์หลังตัวแปร:
<ก่อนหน้า>[student@studentvm1 ~]$ X=0; ในขณะที่ [ จริง ]; ทำ echo $((X++)); เสร็จแล้ว | หัว0
1
2
3
4
5
6
7
8
9
ตอนนี้ลบ | หัว จากจุดสิ้นสุดของโปรแกรมแล้วเรียกใช้อีกครั้ง
ในเวอร์ชันนี้ ตัวแปรจะเพิ่มขึ้นก่อนที่จะพิมพ์ค่า ระบุโดยการวาง ++ โอเปอเรเตอร์ก่อนตัวแปร คุณเห็นความแตกต่างไหม
<ก่อนหน้า>[student@studentvm1 ~]$ X=0; ในขณะที่ [ จริง ]; ทำ echo $((++X)); เสร็จแล้ว | หัว1
2
3
4
5
6
7
8
9
คุณได้ลดสองคำสั่งให้เป็นหนึ่งเดียวที่พิมพ์ค่าของตัวแปรและเพิ่มค่านั้น นอกจากนี้ยังมีตัวดำเนินการลด -- .
คุณต้องการวิธีการหยุดการวนซ้ำที่จำนวนเฉพาะ เพื่อให้บรรลุผลดังกล่าว ให้เปลี่ยนนิพจน์จริงเป็นนิพจน์การประเมินตัวเลขจริง ให้โปรแกรมวนซ้ำเป็น 5 แล้วหยุด ในโค้ดตัวอย่างด้านล่าง คุณจะเห็นว่า -le เป็นตัวดำเนินการตัวเลขเชิงตรรกะสำหรับ "น้อยกว่าหรือเท่ากับ" ซึ่งหมายความว่า:"ตราบใดที่ $X น้อยกว่าหรือเท่ากับ 5 การวนซ้ำจะดำเนินต่อไป เมื่อ $X เพิ่มทีละ 6 ลูปจะสิ้นสุดลง"
<ก่อนหน้า>[student@studentvm1 ~]$ X=0; ในขณะที่ [ $X -le 5 ]; ทำ echo $((X++)); เสร็จสิ้น0
1
2
3
4
5
[student@studentvm1 ~]$
จนกว่าจะวนซ้ำ
จนถึง คำสั่งคล้ายกับ ในขณะที่ . มาก สั่งการ. ข้อแตกต่างคือมันจะยังคงวนซ้ำจนกว่านิพจน์เชิงตรรกะจะประเมินเป็น "จริง" ดูรูปแบบที่ง่ายที่สุดของโครงสร้างนี้:
<ก่อนหน้า>[student@studentvm1 ~]$ X=0; จนเป็นเท็จ; ทำ echo $((X++)); เสร็จแล้ว | หัว0
1
2
3
4
5
6
7
8
9
[student@studentvm1 ~]$
ใช้การเปรียบเทียบเชิงตรรกะเพื่อนับเป็นค่าเฉพาะ:
<ก่อนหน้า>[student@studentvm1 ~]$ X=0; จนถึง [ $X -eq 5 ] ; ทำ echo $((X++)); เสร็จสิ้น0
1
2
3
4
[student@studentvm1 ~]$ X=0; จนถึง [ $X -eq 5 ] ; ทำ echo $((++X)); เสร็จแล้ว
1
2
3
4
5
[student@studentvm1 ~]$
สรุป
ชุดนี้ได้สำรวจเครื่องมืออันทรงพลังมากมายสำหรับการสร้างโปรแกรมบรรทัดคำสั่งและเชลล์สคริปต์ของ Bash แต่มันแทบไม่มีรอยขีดข่วนบนพื้นผิวของสิ่งที่น่าสนใจมากมายที่คุณสามารถทำได้ด้วย Bash; ที่เหลือขึ้นอยู่กับคุณ
ฉันได้ค้นพบว่าวิธีที่ดีที่สุดในการเรียนรู้การเขียนโปรแกรม Bash คือการทำ ค้นหาโปรเจ็กต์ง่ายๆ ที่ต้องใช้คำสั่ง Bash หลายคำสั่ง และสร้างโปรแกรม CLI ผู้ดูแลระบบทำงานหลายอย่างที่เอื้อต่อการเขียนโปรแกรม CLI ดังนั้นฉันจึงมั่นใจว่าคุณจะพบงานที่จะทำให้เป็นอัตโนมัติได้อย่างง่ายดาย
เมื่อหลายปีก่อน แม้จะคุ้นเคยกับภาษาเชลล์อื่นๆ และ Perl อยู่แล้ว ฉันก็ตัดสินใจใช้ Bash สำหรับงานระบบอัตโนมัติของผู้ดูแลระบบทั้งหมดของฉัน ฉันได้ค้นพบว่า—บางครั้งด้วยการค้นหาเพียงเล็กน้อย—ฉันสามารถใช้ Bash เพื่อทำทุกสิ่งที่ต้องการได้สำเร็จ