Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> การเขียนโปรแกรม Bash

สร้างเทมเพลต Bash Script ที่ทนทานสำหรับ System Administrators

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

ในบทความที่สองนี้ คุณจะเริ่มสร้างเทมเพลตสคริปต์ Bash ที่สามารถใช้เป็นจุดเริ่มต้นสำหรับสคริปต์ Bash อื่นๆ เทมเพลตสุดท้ายจะประกอบด้วยสิ่งช่วยเหลือ (Help Facility) ข้อความลิขสิทธิ์ (Licensing Statement) ฟังก์ชันง่ายๆ หลายตัว และตรรกะบางอย่างเพื่อจัดการตัวเลือกเหล่านั้นและตัวเลือกอื่นๆ ที่อาจจำเป็นสำหรับสคริปต์ที่จะพัฒนาจากเทมเพลตนี้

ทำไมต้องสร้างเทมเพลต?

เหมือนกับอัตโนมัติโดยทั่วไป แนวคิดเบื้องหลังการสร้างเทมเพลต คือการเป็น "Lazy Sysadmin" เทมเพลตประกอบด้วยองค์ประกอบพื้นฐานที่คุณต้องการในทุกสคริปต์ มันประหยัดเวลาเทียบกับการเพิ่มองค์ประกอบเหล่านั้นในทุกสคริปต์ใหม่ และทำให้เริ่มสคริปต์ใหม่ได้ง่าย

แม้จะลुभาว่าจะแค่รวมคำสั่ง Bash บรรทัดคำสั่งหลายคำสั่งเข้าไฟล์ และทำให้รันได้ (Executable) แต่สิ่งนั้นอาจให้ผลลัพธ์ตรงข้ามในระยะยาว โปรแกรม Bash ที่เขียนดีและคอมเมนต์ดี พร้อม Help Facility และความสามารถรับตัวเลือกบรรทัดคำสั่ง ให้จุดเริ่มต้นที่ดีสำหรับซิสแอดมินที่ดูแลโปรแกรม รวมถึงโปรแกรมที่ คุณ เขียนและดูแล

ข้อกำหนด (Requirements)

คุณควรสร้างชุดข้อกำหนด (Requirements) สำหรับทุกโปรเจกต์ที่คุณทำ รวมถึงสคริปต์ แม้จะเป็นแค่รายการง่ายๆ ที่มีเพียง 2-3 รายการ ผมเคยเกี่ยวข้องกับโปรเจกต์หลายอย่าง ที่ล้มเหลวทั้งหมด หรือไม่ตอบสนองความต้องการลูกค้า มักเพราะขาดข้อกำหนด (Requirements Statement) หรือข้อกำหนดเขียนไม่ดี

ข้อกำหนดสำหรับเทมเพลต Bash นี้ ค่อนข้างง่าย:

  1. สร้างเทมเพลตที่ใช้เป็นจุดเริ่มต้นสำหรับโปรเจกต์โปรแกรมมิ่ง Bash ในอนาคต
  2. เทมเพลตควรปฏิบัติตามแนวทางปฏิบัติมาตรฐานของการเขียนโปรแกรม Bash
  3. ต้องประกอบด้วย:
    • ส่วนหัว (Heading Section) สำหรับอธิบายฟังก์ชันของโปรแกรมและ Changelog
    • ข้อความลิขสิทธิ์ (Licensing Statement)
    • ส่วนสำหรับฟังก์ชัน (Functions Section)
    • ฟังก์ชัน Help
    • ฟังก์ชันทดสอบว่าผู้ใช้โปรแกรมเป็น root หรือไม่
    • วิธีประเมินตัวเลือกบรรทัดคำสั่ง

โครงสร้างพื้นฐาน (Basic Structure)

สคริปต์ Bash พื้นฐาน มี 3 ส่วน Bash ไม่มีวิธีแบ่งส่วน (Delineate Sections) แต่ขอบเขตระหว่างส่วนเป็นแบบโดยนัย (Implicit)

  • ทุกสคริปต์ต้องเริ่มด้วย Shebang (#!) และต้องเป็นบรรทัดแรกในโปรแกรม Bash ใดๆ
  • ส่วนฟังก์ชัน (Functions Section) ต้องเริ่มหลัง Shebang และก่อน Body ของโปรแกรม เนื่องจากความต้องการของผมในการจัดทำเอกสารทุกอย่าง ผมวางคอมเมนต์ก่อนแต่ละฟังก์ชัน พร้อมคำอธิบายสั้นว่ามันทำอะไร ผมยังรวมคอมเมนต์ภายในฟังก์ชันเพื่อรายละเอียดเพิ่มเติม โปรแกรมสั้นๆ เรียบง่าย อาจไม่ต้องการฟังก์ชัน
  • ส่วนหลักของโปรแกรม (Main Program) มาหลังส่วนฟังก์ชัน อาจเป็นคำสั่ง Bash เดียว หรือพันบรรทัดของโค้ด โปรแกรมหนึ่งของผมมีโค้ดประมาณ 200 บรรทัด ไม่นับคอมเมนต์ โปรแกรมนั้นมีคอมเมนต์มากกว่า 600 บรรทัด

นี่คือทั้งหมด—แค่ 3 ส่วนในโครงสร้างของโปรแกรม Bash ใดๆ

คอมเมนต์นำหน้า (Leading Comments)

ผมเพิ่มมากกว่านี้เสมอด้วยเหตุผลหลายอย่าง ก่อนอื่น ผมเพิ่มส่วนคอมเมนต์ 2 ส่วน ทันทีหลัง Shebang ส่วนคอมเมนต์เหล่านี้เป็นตัวเลือก (Optional) แต่ผมพบว่ามีประโยชน์มาก

ส่วนคอมเมนต์แรก คือ ชื่อโปรแกรมและคำอธิบาย และ Change History (ประวัติการเปลี่ยนแปลง) ผมเรียนรู้รูปแบบนี้ขณะทำงานที่ IBM และมันให้วิธีจัดทำเอกสารการพัฒนาระยะยาวของโปรแกรมและการแก้ไขที่นำไปใช้ นี่คือการเริ่มต้นที่สำคัญในการจัดทำเอกสารโปรแกรมของคุณ

ส่วนคอมเมนต์ที่สอง คือ ข้อความลิขสิทธิ์และสิทธิ์การใช้งาน (Copyright and License Statement) ผมใช้ GPLv2 และนี่ดูเป็นข้อความมาตรฐานสำหรับโปรแกรมที่ได้รับลิขสิทธิ์ภายใต้ GPLv2 หากคุณใช้ลิขสิทธิ์โอเพนซอร์สอื่น ไม่เป็นไร แต่ผมนำเสนอให้เพิ่มข้อความชัดเจนในโค้ด เพื่อกำจัดความสับสนที่อาจเกิดขึ้นเกี่ยวกับลิขสิทธิ์ บทความของ Scott Peterson The source code is the license ช่วยอธิบายเหตุผลเบื้องหลังสิ่งนี้

ดังนั้น ตอนนี้สคริปต์เป็นแบบนี้:

#!/bin/bash
################################################################################
# scriptTemplate #
# #
# Use this template as the beginning of a new program. Place a short #
# description of the script here. #
# #
# Change History #
# 11/11/2019 David Both Original code. This is a template for creating #
# new Bash shell scripts. #
# Add new history entries as needed. #
# #
# #
################################################################################
################################################################################
################################################################################
# #
# Copyright (C) 2007, 2019 David Both #
# LinuxGeek46@both.org #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
################################################################################
################################################################################
################################################################################

echo "hello world!"

รันโปรแกรมที่ปรับปรุงแล้ว เพื่อตรวจสอบว่ายังทำงานได้ตามที่คาดไว้

เกี่ยวกับการทดสอบ (About Testing)

ตอนนี้เป็นเวลาที่ดีพอสมควรที่จะพูดถึงการทดสอบ

"There is always one more bug."

— Lubarsky's Law of Cybernetic Entomology

Lubarsky—ไม่ว่าจะเป็นใคร—ถูกต้อง คุณไม่มีทางหาบั๊กทั้งหมดในโค้ดของคุณ สำหรับทุกบั๊กที่ผมหาเจอ ยังมีอีกตัวหนึ่งขึ้นมาเสมอ มักในเวลาที่ไม่ค่อยเหมาะสม

การทดสอบไม่ได้เกี่ยวกับโปรแกรมอย่างเดียว มันยังเกี่ยวกับการตรวจสอบ (Verification) ว่าปัญหา—ไม่ว่าจะเกิดจากฮาร์ดแวร์ ซอฟต์แวร์ หรือวิธีที่ผู้ใช้หาทำให้เสียได้ไม่รู้จบ—ที่ควรจะได้รับการแก้ไข จริงๆ แล้วได้รับการแก้ไขแล้ว สิ่งสำคัญเช่นกัน การทดสอบยังเกี่ยวกับการให้แน่ใจว่าโค้ดใช้งานง่าย และอินเทอร์เฟซทำให้ผู้ใช้เข้าใจ

การปฏิบัติตามกระบวนการที่ชัดเจน เมื่อเขียนและทดสอบเชลล์สคริปต์ สามารถช่วยให้ผลลัพธ์สม่ำเสมอและมีคุณภาพ กระบวนการของผมง่าย:

  1. สร้างแผนทดสอบง่ายๆ (Simple Test Plan)
  2. เริ่มทดสอบตั้งแต่ต้นการพัฒนา
  3. ทดสอบขั้นสุดท้าย (Final Test) เมื่อโค้ดเสร็จ
  4. ย้ายไป Production และทดสอบเพิ่ม

แผนทดสอบ (Test Plan)

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

ในฐานะซิสแอดมินที่เคยเป็น (แต่ไม่ใช่ตอนนี้) Tester ผมพยายามเลือกจุดกลาง การมีแผนทดสอบเขียนไว้แม้สั้น จะช่วยให้ความสม่ำเสมอจากการรันทดสอบครั้งหนึ่งไปอีกครั้ง รายละเอียดที่ต้องการ ขึ้นอยู่กับว่าการพัฒนาและฟังก์ชันทดสอบเป็นทางการแค่ไหน

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

  • ชื่อและคำอธิบายสั้นของซอฟต์แวร์ที่ทดสอบ
  • คำอธิบายคุณสมบัติซอฟต์แวร์ที่จะทดสอบ
  • เงื่อนไขเริ่มต้นสำหรับแต่ละการทดสอบ
  • ฟังก์ชันที่จะปฏิบัติสำหรับแต่ละการทดสอบ
  • คำอธิบายผลลัพธ์ที่พึงประสงค์สำหรับแต่ละการทดสอบ
  • การทดสอบเฉพาะที่ออกแบบมาเพื่อทดสอบผลลัพธ์เชิงลบ (Negative Outcomes)
  • การทดสอบว่าโปรแกรมจัดการอินพุตที่ไม่คาดคิดอย่างไร
  • คำอธิบายชัดเจนว่าอะไรถือว่าผ่าน (Pass) หรือล้มเหลว (Fail) สำหรับแต่ละการทดสอบ
  • Fuzzy Testing ซึ่งอธิบายด้านล่าง

รายการนี้ควรให้แนวคิดบางอย่างสำหรับการสร้างแผนทดสอบของคุณ ซิสแอดมินส่วนใหญ่ควรเก็บมันให้เรียบง่ายและไม่เป็นทางการ

ทดสอบเร็ว—ทดสอบบ่อย (Test Early—Test Often)

ผมเริ่มทดสอบเชลล์สคริปต์ของผมทันทีที่เสร็จส่วนแรกที่รันได้ นี้เป็นจริง ไม่ว่าผมจะเขียนโปรแกรม CLI สั้น หรือสคริปต์ที่เป็นไฟล์รันได้

ผมมักเริ่มสร้างโปรแกรมใหม่ด้วยเทมเพลตเชลล์สคริปต์ ผมเขียนโค้ดสำหรับฟังก์ชัน Help และทดสอบมัน นี้มักเป็นส่วนเล็กน้อยของกระบวนการ แต่ช่วยให้ผมเริ่มต้นได้ และให้แน่ใจว่าสิ่งต่างๆ ในเทมเพลตทำงานได้อย่างถูกต้องตั้งแต่ต้น ที่จุดนี้ ง่ายต่อการแก้ไขปัญหากับส่วนเทมเพลตของสคริปต์ หรือปรับเปลี่ยนเพื่อตอบสนองความต้องการที่เทมเพลตมาตรฐานไม่ครอบคลุม

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

นี่คือจุดที่การทดสอบมากกว่าการป้อนข้อมูลและตรวจสอบผลลัพธ์ ต้องทำงานเพิ่มเติมบ้าง บางครั้งผมเพิ่มคำสั่งที่แค่พิมพ์ผลลัพธ์ระหว่างทาง (Intermediate Result) ของโค้ดที่เพิ่งเขียน และตรวจสอบมัน สำหรับสคริปต์ซับซ้อนกว่า ผมเพิ่มตัวเลือก -t สำหรับ "Test Mode" ในกรณีนี้ โค้ดทดสอบภายในจะรันเฉพาะเมื่อตัวเลือก -t ถูกป้อนบนบรรทัดคำสั่ง

การทดสอบขั้นสุดท้าย (Final Testing)

หลังโค้ดเสร็จ ผมกลับมาทำการทดสอบครบถ้วน (Complete Test) ของทุกฟีเจอร์และฟังก์ชัน โดยใช้อินพุตที่รู้จัก เพื่อสร้างเอาต์พุตเฉพาะ ผมยังทดสอบอินพุตสุ่มบางอย่าง เพื่อดูว่าโปรแกรมจัดการอินพุตที่ไม่คาดคิดได้ไหม

การทดสอบขั้นสุดท้าย มีเจตนาตรวจสอบว่าโปรแกรมทำงานโดยพื้นฐานแล้ว ตามที่ตั้งใจ ส่วนใหญ่ของการทดสอบขั้นสุดท้าย คือการให้แน่ใจว่าฟังก์ชันที่ทำงานได้ก่อนหน้าในรอบพัฒนา ไม่ได้เสียหายจากโค้ดที่เพิ่มหรือเปลี่ยนแปลงในภายหลังของรอบ

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

การทดสอบใน Production (Testing in Production)

ฮับ—อะไร?

"Not until a program has been in production for at least six months will the most harmful error be discovered."

— Troutman's Programming Postulates

ใช่ การทดสอบใน Production ตอนนี้ถือเป็นเรื่องปกติและพึงประสงค์ เคยเป็น Tester เองสิ่งนี้ดูสมเหตุสมผล "แต่รอสิ! นั้นอันตราย" คุณพูด ประสบการณ์ของผม คือ ไม่ได้อันตรายกว่าการทดสอบอย่างกว้างขวางและเข้มงวดในสภาพแวดล้อมทดสอบเฉพาะ ในบางกรณี ไม่มีตัวเลือก เพราะไม่มีสภาพแวดล้อมทดสอบ—มีแค่ Production

ซิสแอดมินไม่แปลกหน้ากับความจำเป็นต้องทดสอบสคริปต์ใหม่หรือปรับปรุงใน Production ทุกครั้งที่สคริปต์ย้ายเข้าสู่ Production นั่นกลายเป็นการทดสอบขั้นสุดท้าย สภาพแวดล้อม Production เป็นส่วนสำคัญที่สุดของการทดสอบนั้น ไม่มีสิ่งที่ Tester จินตนาการได้ในสภาพแวดล้อมทดสอบ ที่สามารถจำลองสภาพแวดล้อม Production ที่แท้จริงได้เต็มที่

การปฏิบัติที่เป็นใหม่ล่าสุดของการทดสอบใน Production เพียงแค่การยอมรับสิ่งที่ซิสแอดมินรู้มาตลอด การทดสอบที่ดีที่สุดคือ Production — ตราบใดที่มันไม่ใช่การทดสอบเพียงอย่างเดียว

Fuzzy Testing (การทดสอบแบบสุ่ม)

นี่อีกหนึ่ง Buzzword ที่ทำให้ผมพลิกตา (Roll Eyes) ตอนแรก ความหมายจริงของมันง่าย: ให้ใครบางคนโดนแป้นพิมพ์จนเกิดอะไรขึ้น และดูว่าโปรแกรมจัดการได้ดีแค่ไหน แต่จริงๆ แล้วมีรายละเอียดมากกว่านั้น

Fuzzy Testing คล้ายกับเวลาที่ลูกชายของผมทำลายโค้ดเกมในไม่กี่นาทีด้วยอินพุตสุ่ม นั่นจบความพยายามของผมในการเขียนเกมให้เขา

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

Fuzzy Testing เกี่ยวกับการจัดการความสุ่มในทุกด้านของการทดสอบ เช่น เงื่อนไขเริ่มต้น อินพุตสุ่มและไม่คาดคิดมาก การผสมผสานตัวเลือกสุ่ม หน่วยความจำต่ำ ระดับ CPU สูงแข่งขันกับโปรแกรมอื่น หลายอินสแตนซ์ของโปรแกรมที่ทดสอบ และเงื่อนไขสุ่มอื่นๆ ที่คุณคิดไดเพื่อนำไปใช้ในการทดสอบ

ผมพยายามทำ Fuzzy Testing บ้างตั้งแต่ต้น หากสคริปต์ Bash ไม่สามารถจัดการความสุ่มที่สำคัญในขั้นตอนเริ่มต้นได้ มันไม่น่าจะดียิ่งขึ้นเมื่อคุณเพิ่มโค้ดมากขึ้น นี่เป็นเวลาที่ดีในการจับปัญหาเหล่านี้และแก้ไข ขณะที่โค้ดยังค่อนข้างง่าย Fuzzy Testing นิดหน่อยในแต่ละขั้นตอน ยังมีประโยชน์ในการค้นหาปัญหาก่อนที่จะถูกบดบังโดยโค้ดที่มากขึ้น

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

ตัวอย่างสิ่งที่จะมาถึง (Previews of Coming Attractions)

บทความนี้ทำได้นิดหน่อยในการสร้างเทมเพลต แต่ว่างใหญ่พูดถึงการทดสอบ เพราะการทดสอบเป็นส่วนสำคัญของการสร้างโปรแกรมประเภทใดๆ ในบทความถัดไปในชุดนี้ คุณจะเพิ่มฟังก์ชัน Help พื้นฐาน รวมถึงโค้ดบางอย่างเพื่อตรวจจับและกระทำต่อตัวเลือก เช่น -h ให้กับเทมเพลตสคริปต์ Bash ของคุณ

แหล่งข้อมูล (Resources)

  • How to program with Bash: Syntax and tools
  • How to program with Bash: Logical operators and shell expansions
  • How to program with Bash: Loops

    ชุดบทความนี้บางส่วนอิงจาก Volume 2, Chapter 10 ของหลักสูตร Linux เรียนรู้ด้วยตนเอง 3 ส่วน ของ David Both: Using and Administering Linux—Zero to SysAdmin.