การเขียนโปรแกรมเชลล์ (6)

การสร้างเชลล์สคริปต์

จากที่เราได้ทดลองสร้างเชลล์สคริปต์มาบ้างแล้วใน SHELL (2) จะเห็นว่าการทำเชลล์สคริปต์จริงๆแล้วก็เป็นการนำเอาคำสั่งต่างๆในลีนุกซ์มาประกอบกันเข้าเป็นไฟล์ๆหนึ่งนั่นเอง
จากคำสั่งต่อไปนี้
$ sh			#call subshell
$ echo "Hello world"	#print "Hello world"
$ date			#print date information
$ pwd			#print current directory
$ exit			#exit from subshell
ชุดคำสั่งข้างบนทั้งหมดเราสามารถนำมาใส่ไว้ในไฟล์ เพื่อนำมาจัดทำเป็นเชลล์สคริปต์ได้ ให้คุณสร้างไฟล์ชื่อ "ex1" โดยให้พิมพ์คำสั่งต่อไปนี้ลงไปไว้ในไฟล์ สามารถใช้เอดิเตอร์ "vi" หรือ "pico" ก็ได้ตามความชอบและความถนัด ถ้าคุณยังไม่เข้าใจวิธีการใช้งาน "vi" และ "pico" ก็ขอให้อ่านได้จากเอกสารที่อธิบายการใช้งานเอดิเตอร์ทั้งสองเสียก่อน
echo "Hello world"
date
pwd
เมื่อทำการบันทึกข้อมูลเก็บลงไปในไฟล์ชื่อ "ex1" แล้ว เราสามารถจะสั่งให้โปรแกรมทำงานได้สองวิธีคือ
  1. สั่งผ่านเชลล์ ให้พิมพ์คำสั่งดังนี้
  2. $ sh ex1
    
    วิธีการสั่งงานโปรแกรมแบบนี้ "ex1" จะต้องอนุญาตให้สามารถอ่านไฟล์ได้ (readable) ไม่เช่นนั้นเชลล์จะไม่สามารถทำให้โปรแกรมทำงานได้
  3. สั่งโดยตรงจากชื่อโปรแกรม ให้พิมพ์คำสั่ง
  4. $ ex1
    
    สำหรับวิธีนี้โปรแกรม "ex1" จะต้องอนุญาตให้สามารถสั่งทำงานได้ (executable) ไม่เช่นนั้นเชลล์จะปฏิเสธการสั่งงานโปรแกรม (Permission denied) เราสามารถทำการกำหนดให้ไฟล์อนุญาตให้มีการสั่งทำงานได้โดยการใช้คำสั่ง
    $ chmod u+x ex1		: อนุญาตให้สั่งทำงานได้ในระดับผู้ใช้
    $ chmod g+x ex1		: อนุญาตให้สั่งทำงานได้ในระดับกลุ่ม
    $ chmod o+x ex1		: อนุญาตให้สั่งทำงานได้ในระดับบุคคลอื่นๆ
    $ chmod +x ex1 
    และ
    $ chmod a+x ex1		: เป็นการเปิดให้สั่งทำงานได้ในทุกระดับชั้น
    
หากกำหนดให้ไฟล์อนุญาตให้มีการสั่งทำงานได้แล้ว และทำการพิมพ์ชื่อโปรแกรมเพื่อสั่งให้โปรแกรมทำงาน ปรากฏว่าเชลล์ยังคงปฏิเสธการสั่งงาน (ex1: command not found) แสดงว่าเชลล์ไม่ทราบว่า จะค้นหาชื่อโปรแกรมที่เราพิมพ์ลงไปได้จากที่ไหน ดังนั้นเรา จึงจำเป็นจะต้องระบุชื่อที่อ้างอิงกับไดเรกทอรีด้วย เชลล์จึงจะทราบว่าจะต้องไปค้นหาโปรแกรมได้จากที่ไดเรกทอรีใด วิธีการระบุชื่อที่อ้างอิงกับไดเรกทอรีก็ทำได้สองวิธี
  1. Reletive Pathname วิธีระบุโดยอ้างอิงจากไดเรกทอรีปัจจุบัน
    $ ./ex1
    
  2. Full Pathname ระบุแบบอ้างชื่อเต็ม ในกรณีนี้สมมุติว่าไฟล์อยู่ใน ไดเรกทอรี /home/user1
    $ /home/user1/ex1
    
แต่จะเห็นว่าการงานเรียกใช้งานโปรแกรมโดยต้องระบุชื่อไดเรกทอรี ค่อนข้างจะเป็นเรื่องยุ่งยาก และหากเราเก็บโปรแกรมไว้ในไดเรกทอรีเฉพาะที่ต้องเรียกใช้บ่อยๆเช่น /home/user1/shell สำหรับเก็บโปรแกรมเชลล์ที่เรียกใช้บ่อยๆ หากไม่ได้อยู่ในไดเรกทอรีนั้น ก็จะทำให้การอ้างอิงถึงโปรแกรมเป็นเรื่องยุ่งยาก ในระบบลีนุกซ์จะมีวิธีการกำหนดให้เชลล์ทำการค้นหาโปรแกรมที่สามารถสั่งให้ทำงานได้ จากไดเรกทอรีกลุ่มหนึ่งที่ระบุไว้ ซึ่งไดเรกทอรีเหล่านั้นจะถูกระบุไว้ในตัวแปรสงวนของระบบชื่อ PATH (เรียกว่า search path) เราสามารถตรวจสอบชื่อไดเรกทอรีที่เชลล์จะทำการค้นหาเมื่อมีการเรียกใช้งานโปรแกรมโดยไม่ระบุชื่อไดเรกทอรี ได้โดยการใช้คำสั่ง
$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
จากผลลัพธ์ข้างต้น หากมีการสั่งงานโปรแกรมโดยไม่ระบุชื่อไดเรกทอรีดังกรณีข้างต้น เชลล์จะทำการค้นหาโปรแกรมจากไดเรกทอรีทั้งสี่ที่ระบุไว้ในตัวแปร PATH คือ /usr/local/bin, /bin, usr/bin และ /usr/X11R6/bin ซึ่งแต่ละไดเรกทอรีจะถูกคั่นด้วยเครื่องหมาย colon ":" และโดยปรกติตัวแปร PATH ก็จะถูกกำหนดให้บรรจุไดเรกทอรีเหล่านี้ไว้ เพื่อทำการสั่งงานโปรแกรมที่ใช้บ่อยๆ (เช่น "ls" อยู่ในไดเรกทอรี "/bin" และ "ftp" อยู่ในไดเรกทอรี "/usr/bin" เป็นต้น) สำหรับโปรแกรม ex1 ของเราเนื่องจากอยู่ ในไดเรกทอรี /home/user1 ดังนั้นเชลล์จึงไม่สามารถค้นหาเจอ เราจะต้องทำการเพิ่มไดเรกทอรีนี้ลงไปในตัวแปร PATH
วิธีการเพิ่มไดเรกทอรีจะใช้คำสั่งดังต่อไปนี้
$ PATH=$PATH:/home/user1
$ export PATH
สำหรับคำสั่งแรกจะเป็นการนำเอาชื่อของกลุ่มไดเรกทอรีเดิม ($PATH) มาต่อเข้ากับไดเรกทอรีใหม่ (:/home/user1) หากไม่ระบุชื่อไดเรกทอรีเดิม ($PATH) ก็จะกลายเป็นการแทนที่ของเดิมด้วยของใหม่ไป ซึ่งถ้าเป็นเช่นนั้นก็จะทำให้ไม่สามารถเรียกใช้โปรแกรมที่เคยใช้ได้ (เช่นโปรแกรม ls) หากต้องการให้มีการสั่งงานโปรแกรมที่อยู่ในไดเรกทอรีปัจจุบัน (current directory) ด้วย จะต้องใช้คำสั่งต่อไปนี้
$ PATH=$PATH:.
$ export PATH
คำสั่งข้างต้นจะเป็นการเพิ่มไดเรกทอรีปัจจุบัน (:.) ลงไปในตัวแปร PATH ด้วยเนื่องจาก จุด (dot) "." จะเป็นการแทนความหมายของไดเรกทอรีปัจจุบัน ให้ใช้คำสั่ง "echo" เพื่อตรวจดูว่าเราได้ทำการเพิ่มไดเรกทอรีเข้าไปในตัวแปร PATH เรียบร้อย
$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/user1:.

สำหรับการกำหนดให้เพิ่มไดเรกทอรีปัจจุบัน (:.) ลงไปในตัวแปร PATH ถ้าคำนึงถึงเรื่องของระบบการรักษาความปลอดภัยแล้วเป็นเรื่องที่ไม่ควรทำอย่างยิ่ง ปกติระบบยูนิกซ์ส่วนใหญ่จะไม่นิยมให้ผู้ใช้ในระดับ wheel และ root ซึ่งเป็นกลุ่มที่อยู่ในระดับการดูแลรักษาระบบ มีไดเรกทอรีปัจจุบัน (:.) อยู่ในตัวแปร PATH

เมื่อทำการกำหนดให้โปรแกรมอนุญาตให้สั่งทำงานได้ และกำหนด serch path เรียบร้อยแล้วควรจะสามารถสั่งงานโปรแกรมโดยไม่ต้องทำการระบุชื่อไดเรกทอรีได้

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

ถึงแม้ว่าเราไม่ได้ใส่คำสั่ง exit ไว้ในเชลล์สคริปต์ แต่ก็เปรียบเสมือนว่ามีคำสั่ง exit อยู่ท้ายเชลล์สคริปต์ด้วย และโดยทั่วไปหากเป็นการจบการทำงานแบบปรกติไม่มีข้อผิดพลาดเกิดขึ้น ก็จะทำการส่งรหัสค่า "0" (return code 0) ออกมาให้ (คำสั่ง exit ที่ไม่มีตัวเลขตามท้ายจะถือว่าส่ง return code 0) แต่ถ้าเป็นการจบการทำงานแบบไม่ปรกติ ซึ่งอาจมีข้อผิดพลาดเกิดขึ้นก็จะทำการส่งรหัสค่าที่ไม่เป็น "0" ออกมาให้ (ปรกติจะเป็นรหัสค่า "1") สำหรับ return code ดังกล่าวจะนำไปใช้ทดสอบเงื่อนไขร่วมกับคำสั่งอื่นๆในเชลล์ต่อไป

ทั้งคำสั่งในการเรียกซับเชลล์และคำสั่งที่ออกจากซับเชลล์เราไม่ได้ใส่ลงไปในเชลล์สคริปต์ แต่ว่าเมื่อทำการสั่งให้เชลล์สคริปต์ทำงานแล้ว โปรแกรมเชลล์จะเติมคำสั่งทั้งสองลงไปให้โดยอัตโนมัติ นั่นหมายความว่าโดยปกติแล้วคำสั่งทุกคำสั่งในเชลล์สคริปต์จะต้องทำงานภายใต้ซับเชลล์ ซึ่งการทำงานภายใต้ซับเชลล์ก็จะมีข้อจำกัดดังที่อธิบายในตอนที่แล้ว (SHELL (5)) นั่นคือภายในซับเชลล์จะไม่สามารถทำการใช้งานตัวแปรจากเชลล์ชั้นบนได้ หากไม่ได้ทำการ export ตัวแปรนั้นๆเสียก่อนแต่อย่างไรก็ตามเราก็มีวิธีที่จะบังคับให้เชลล์สคริปต์ทำงานในเชลล์ชั้นปัจจุบันโดยไม่ไปเรียกซับเชลล์ได้ ดังจะได้อธิบายต่อไป

วิธีการสั่งให้เชลล์สคริปต์ทำงานในแบบต่างๆ

รูปแบบการสั่งงานเชลล์สคริปต์ มีได้ 4 แบบดังต่อไปนี้
  1. $ script-name สคริปต์ต้องการ execute permission และวิ่งที่ subshell
  2. $ sh script-name สคริปต์ต้องการ read permission และวิ่งที่ subshell
  3. $ .script-name สคริปต์จะวิ่งที่ current shell
  4. $ exec scriptname สคริปต์จะวิ่งที่ current shell โดยจะทับเชลล์ชั้นปัจจุบัน

ก่อนที่จะอธิบายรูปแบบการสั่งงานในเชลล์สคริปต์ ขอให้กลับไปทบทวนเรื่องของโพรเซสของเชลล์ ในหัวข้อ "ขอบเขตของตัวแปรเชลล์" ใน SHELL (5) ก่อน เนื่องจากจะอธิบายอ้างอิงถึงเรื่องโพรเซสบางส่วนจากหัวข้อดัวกล่าว จะช่วยทำให้เข้าใจเรื่องของโพรเซสได้มากขึ้น

[Shell Process]
ภาพของการแตกโพรเซสในลีนุกซ์

สำหรับการสั่งงานเชลล์สคริปต์ในแบบข้อ 1 และ ข้อ 2 จะสามารถอธิบายได้ดังในรูป (A) นั่นคือเมื่อทำการล็อกอินเข้ามา จะได้เชลล์ใช้งาน (ซึ่งปกติในลีนุกซ์จะเป็น bash) ในที่นี้จะเรียกว่า Parent Shell หรือเชลล์เริ่มต้น เมื่อมีการสั่งให้เชลล์สคริปต์ ทำงานตามรูปแบบในข้อ 1-2 ก็จะเปรียบเสมือนมีการเรียกซับเชลล์เกิดขึ้น (คำสั่ง sh) เมื่อเรียกซับเชลล์แล้วก็จะทำงานตามคำสั่งในเชลล์สคริปต์ทีละคำสั่งจนจบก็จะกลับไปสู่ Parent Shell (คำสั่ง exit) ในกรณีนี้จะเห็นว่าหากเชลล์สคริปต์ต้องการจะใช้งานตัวแปรเชลล์ใน Parent Shell จะต้องมีการทำการ export ตัวแปรนั้นๆมาจาก Parent Shell เสียก่อน

สำหรับการสั่งงานในแบบข้อ 3 จะสามารถอธิบายได้ดังในรูป (B) วิธีการสั่งงานเชลล์สคริปต์แบบนี้ จะทำให้เชลล์สคริปต์ทำงานในเชลล์ชั้นปัจจุบันทันที (ในที่นี้คือ Parent Shell) และจะทำงานตามคำสั่งในเชลล์สคริปต์ทีละคำสั่งจนจบ จะเห็นว่าไม่มีการเรียกซับเชลล์ขึ้นมา ทำให้เราสามารถใช้งานตัวแปรเชลล์ใน Parent Shell ได้ทันที ไม่ต้องทำการ export ตัวแปรมาก่อน

สำหรับการสั่งงานในแบบข้อ 4 จะสามารถอธิบายได้ดังในรูป (C) วิธีการสั่งงานแบบนี้ Parent Shell จะถูกทำลายไปและระบบจะนำเอาเชลล์สคริปต์ มาแทนที่โพรเซสของ Parent Shell ที่ถูกทำลายไป ดังนั้นเมื่อเชลล์สคริปต์ได้ทำงานทีละคำสั่งจนจบแล้วก็จะกลับไปสู่ล็อกอินโดยอัตโนมัติ

ให้ทดลองสั่งงานเชลล์สคริปต์ "ex1" โดยใช้อีกสองวิธีที่เหลือ และลองตรวจสอบผลลัพธ์ ที่ได้

การใช้ตัวแปรระบุอาร์กิวเมนต์ของเชลล์ และตัวแปรอื่นๆ

ในการใช้งานโปรแกรมหรือยูทิลิตี้ในยูนิกซ์ ส่วนใหญ่จะมีการระบุออปชั่นและพารามิเตอร์อื่นๆให้กับโปรแกรมหรือยูทิลิตี้นั้นๆ ผ่านทางคอมมานด์ไลน์ได้ด้วยซึ่งจะทำให้สามารถใช้งานโปรแกรมหรือยูทิลิตี้นั้นๆได้หลายรูปแบบตามความต้องการ ทั้งออปชั่นและพารามิเตอร์ถือว่าเป็นอาร์กิวเมนต์ของโปรแกรมทั้งสิ้น เพียงแต่ข้อแตกต่างกันก็คือ ออปชั่นจะเป็นอาร์กิวเมนต์ที่มีเครื่องหมายลบ หรือ dash "-" นำหน้า ส่วนพารามิเตอร์จะไม่ มีเครื่องหมายลบนำหน้า
ตัวอย่างของอาร์กิวเมนต์ เช่น
$ ls -al /usr/bin 
ในที่นี้คำสั่งหรือโปรแกรม "ls" จะมีอาร์กิวเมนต์อยู่สองตัวคือ "-al" และ "/usr/bin" โดยที่ "-al" จะเป็นออปชั่น และ "/usr/bin" จะเป็นพารามิเตอร์ ซึ่งอ้างถึงไดเรกทอรีที่ต้องการทราบรายชื่อไฟล์ที่มีอยู่ ในโปรแกรมเชลล์ก็มีตัวแปรที่เกี่ยวข้องกับอาร์กิวเมนต์เช่นกัน เราสามารถระบุอาร์กิวเมนต์ผ่านทางคอมมานด์ไลน์และนำเอาอาร์กิวเมนต์ต่างๆมาใช้ประโยชน์ ในโปรแกรมเชลล์ ตัวแปรที่เกี่ยวข้องกับอาร์กิวเมนต์ และตัวแปรอื่นๆมีดังต่อไปนี้
$0 :ชื่อของเชลล์สคริปต์ หรือชื่อโปรแกรมเชลล์ที่ถูกเรียกทำงาน
$1-$9 :Positional Parameter ที่แทนอาร์กิวเมนต์ของเชลล์ ซึ่งจะอ้างถึงอาร์กิวเมนต์ได้สูงสุด 9 ตัว แต่ก็มีวิธีที่จะจัดการกับอาร์กิวเมนต์ที่มีจำนวนมากกว่า 9 ตัวได้ ดังจะได้อธิบายต่อไป
$* : รวมอาร์กิวเมนต์ทุกตัวเข้าเป็นสตริงเดียวกัน "$1 $2 $3...$9"
$@ : อ้างถึงอาร์กิวเมนต์ทุกตัว แต่ละอาร์กิวเมนต์ถือเป็นสตริงหนึ่งสตริง
$# : จำนวนอาร์กิวเมนต์
$- : ออปชั่นของเชลล์
$$ : หมายเลขประจำโพรเซส (PID) ของโปรแกรมเชลล์
$! : หมายเลขประจำโพรเซส (PID) ของโปรแกรมที่ทำงานแบบหลังฉากตัวล่าสุด
$? : ค่า return code ของคำสั่ง exit
โปรแกรมต่อไปนี้จะแสดงวิธีการใช้งานตัวแปรที่เกี่ยวข้องกับอาร์กิวเมนต์ โดยที่แสดงชื่อของโปรแกรม และอาร์กิวเมนต์ตัวที่หนึ่งจนถึงตัวที่สาม และจำนวนของอาร์กิวเมนต์ทั้งหมด
echo "Name of shell script is" $0
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
echo "No. of argument is" $#
echo "Arguments are" $*
echo "Arguments are" $@
ให้บันทึกไฟล์ข้างต้นในชื่อของ "ex2" จากนั้นก็สั่งให้โปรแกรมทำงานซึ่งสามารถทำได้สองวิธีดังต่อไปนี้
  1. $ sh ex2 a b c

  2. วิธีนี้ไม่ต้องเปลี่ยนโหมดของไฟล์ให้เป็นแบบทำงานได้ (executable) สามารถสั่งงานได้เลย และใส่อาร์กิวเมนต์ตามหลังอีกสามตัวคือ a, b และ c
  3. $ ex2 a b c

  4. วิธีนี้จะต้องเปลี่ยนโหมดของไฟล์ให้เป็นแบบทำงานได้ (executable) เสียก่อน และต้องทำการระบุ search path ให้ถึงไดเรกทอรีที่บรรจุโปรแกรมเชลล์นี้ด้วย และใส่อาร์กิวเมนต์ตามหลังอีกสามตัวคือ a, b และ c
ผลลัพธ์ที่ได้จากการสั่งงานจะเป็นดังต่อไปนี้
Name of shell script is ex2
Parameter 1 is a
Parameter 2 is b
Parameter 3 is c
No. of argument is 3
Arguments are a b c
Arguments are a b c
จะเห็นว่าเราสามารถใช้ตัวแปรที่ได้อธิบายไว้ข้างต้น อ้างอิงกับอาร์กิวเมนต์ของเชลล์ได้ดังนี้คือ

สำหรับ $* และ $@ จะเห็นว่าพิมพ์ผลลัพธ์ออกมาในรูปแบบเดียวกันก็คือ "a b c" แต่การใช้งานในลักษณะอื่นจะเห็นข้อแตกต่างระหว่างตัวแปรทั้งสองรูปแบบ

การเลื่อนตำแหน่งของอาร์กิวเมนต์

เราสามารถเลื่อนตำแหน่งของอาร์กิวเมนต์ได้โดยการใช้คำสั่ง "shift" เมื่อใช้คำสั่งนี้แล้ว Positional Parameter แต่ละตัวจะเปลี่ยนไปเป็นการอ้างถึงอาร์กิวเมนต์ตัวถัดไป ให้ทำการแก้ไขโปรแกรม "ex2" ให้เป็นไปในลักษณะดังต่อไปนี้
echo "Name of shell script is" $0
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
echo "No. of argument is" $#
echo "Arguments are" $*
echo "Arguments are" $@

#Now we will shift arguments 
shift
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
คราวนี้ให้สั่งโปรแกรมทำงานโดยเพิ่มอาร์กิวเมนต์เข้าไปเป็นสี่ตัวดังต่อไปนี้
$ ex2 a b c d
เมื่อสั่งให้โปรแกรมทำงานจะได้ผลลัพธ์ดังต่อไปนี้
Name of shell script is ex2
Parameter 1 is a
Parameter 2 is b
Parameter 3 is c
No. of argument is 4
Arguments are a b c d
Arguments are a b c d
Parameter 1 is b
Parameter 2 is c
Parameter 3 is d
บรรทัดที่มีเครื่องหมาย "#" จะเป็นการระบุว่าบรรทัดนี้เป็นหมายเหตุ (comment) เชลล์จะไม่ทำการตีความบรรทัดนี้ คำสั่งตั้งแต่บรรทัดนี้เป็นต้นไปจะเป็นคำสั่งใหม่ที่จะต้องพิมพ์เพิ่มลงไป ซึ่งจะแสดงให้เห็นถึงการใช้คำสั่ง "shift" คราวนี้โปรแกรมจะนับอาร์กิวเมนต์ได้ทั้งหมดสี่ตัว และการอ้าง Positional Parameter กับอาร์กิวเมนต์ก่อนและหลังคำสั่ง "shift" จะเป็นดังต่อไปนี้ ก่อน shift
$1 จะอ้างถึงอาร์กิวเมนต์ตัวที่หนึ่ง = "a"
$2 จะอ้างถึงอาร์กิวเมนต์ตัวที่สอง = "b"
$3 จะอ้างถึงอาร์กิวเมนต์ตัวที่สาม = "c"
หลัง shift
$1 จะอ้างถึงอาร์กิวเมนต์ตัวที่สอง = "b"
$2 จะอ้างถึงอาร์กิวเมนต์ตัวที่สาม = "c"
$3 จะอ้างถึงอาร์กิวเมนต์ตัวที่สี่ = "d"
ให้ทดลองใช้คำสั่ง "echo" กับตัวแปรอื่นๆที่ไม่เกี่ยวข้องกับอาร์กิวเมนต์

การใช้ set command

เราสามารถทำการคงค่าของอาร์กิวเมนต์ในเชลล์ได้ โดยการใช้คำสั่ง "set" ซึ่งเมื่อใช้คำสั่งนี้แล้วค่าของอาร์กิวเมนต์ต่างๆจะถูกกำหนดโดยปริยายโดยคำสั่ง "set" และไม่สามารถจะเปลี่ยนแปลงค่าของอาร์กิวเมนต์นั้นได้อีก จากโปรแกรม ex2 เราจะมีการเพิ่มคำสั่ง "set" ไปที่ตอนต้นสุดของโปรแกรม
set PARA1 PARA2 PARA3 PARA4
echo "Name of shell script is" $0
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
echo "No. of argument is" $#
echo "Arguments are" $*
echo "Arguments are" $@

Now shift
shift
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
ลองสั่งให้โปรแกรมทำงานโดยมีอาร์กิวเมนต์ตามท้ายสี่ตัวคือ "a", "b", "c" และ "d" แบบเดิมดู
$ ex2 a b c d
จะได้ผลลัพธ์ดังต่อไปนี้
Name of shell script is ex2
Parameter 1 is PARA1
Parameter 2 is PARA2
Parameter 3 is PARA3
No. of argument is 4
Arguments are PARA1 PARA2 PARA3 PARA4
Arguments are PARA1 PARA2 PARA3 PARA4
Parameter 1 is PARA2
Parameter 2 is PARA3
Parameter 3 is PARA4
จะเห็นว่าโปรแกรมเชลล์จะไม่สนใจอาร์กิวเมนต์ที่ใส่ไปที่คอมมานด์ไลน์ โดยจะหันไปใช้อาร์กิวเมนต์จากคำสั่ง set แทน ดังต่อไปนี้
ก่อน shift
$1 จะอ้างถึงอาร์กิวเมนต์ตัวที่หนึ่ง = "PARA1"
$2 จะอ้างถึงอาร์กิวเมนต์ตัวที่สอง = "PARA2"
$3 จะอ้างถึงอาร์กิวเมนต์ตัวที่สาม = "PARA3"

หลัง shift
$1 จะอ้างถึงอาร์กิวเมนต์ตัวที่สอง = "PARA2"
$2 จะอ้างถึงอาร์กิวเมนต์ตัวที่สาม = "PARA3"
$3 จะอ้างถึงอาร์กิวเมนต์ตัวที่สี่ = "PARA4"

การใช้ตัวแปรเชลล์แบบมีเงื่อนไข

  1. ${varname:-word}
  2. รูปแบบการใช้งานแบบนี้ คือ หาก varname มีค่าก็จะส่งค่า (return) varname กลับไปให้ แต่ถ้าไม่มีค่า (หรือเป็น null) ก็จะส่งค่า word กลับไปให้ ดังตัวอย่าง
    $ STUDENT=${NAME:-"Peter"}
    $ echo $STUDENT
    Peter
    $ NAME="John"
    $ STUDENT=${NAME:-"Peter"}
    $ echo $STUDENT
    John
    
    จากตัวอย่างจะเห็นว่าในตอนแรกไม่ได้กำหนดค่าให้กับตัวแปร NAME ดังนั้นจากรูปแบบคำสั่ง จึงมีการส่งค่า "Peter" ไปให้กับตัวแปร STUDENT เมื่อทำการ echo $STUDENT จึงเห็นเป็นค่า "Peter" แต่เมื่อทำการกำหนดค่าให้ NAME มีค่าเป็น "John" แล้วตัวแปร STUDENT จึงถูกกำหนดค่าเป็น "John" ตามไปด้วย
  3. ${varname:=word}
  4. รูปแบบนี้คือถ้าหาก varname มีค่าก็จะส่งค่าของ varname กลับไปให้ แต่ถ้า varname ไม่มีค่าก็จะมีการทำงานสองขั้นตอน คือทำการกำหนดค่า word ให้กับ varname และส่งค่า word กลับไปให้ด้วย ดังตัวอย่าง
    $ STUDENT=${NAME:="Peter"}
    $ echo $STUDENT
    Peter
    $ echo $NAME
    Peter
    $ NAME="John"
    $ STUDENT=${NAME="Peter"}
    $ echo $STUDENT
    John
    
    จากตัวอย่างจะเห็นว่าหากตัวแปร NAME ไม่มีค่าแล้ว ตัวแปร NAME ก็จะถูกกำหนดค่าให้เป็น "Peter" และรวมทั้งมีการส่งค่า "Peter" ไปให้กับตัวแปร STUDENT ด้วย (ซึ่งจะต่างกับแบบแรกที่ไม่มีการกำหนดค่าให้กับตัวแปร NAME, ยังคงไม่มีค่า) แต่ถ้าหากตัวแปร NAME มีค่าอยู่แล้ว (เป็น "John") ก็จะคือค่าของตัวแปร NAME ไปให้เหมือนกัน
  5. ${varname:?message}
  6. วิธีการกำหนดตัวแปรแบบนี้ หาก varname ไม่มีค่าแล้วเชลล์จะทำการแจ้งว่ามีข้อผิดพลาดเกิดขึ้น โดยทำการแจ้งข้อความตามที่ได้กำหนดไว้ใน message แต่ถ้า varname มีค่าก็จะส่งค่าของ varname ให้ตามปกติ ดังตัวอย่าง
    $ STUDENT=${NAME:?"Not assigned"}
    bash: NAME: Not assigned
    $ NAME="John"
    $ STUDENT=${NAME:?"Not assigend"}
    $ Echo $STUDENT
    John
    
    จากตัวอย่างจะเห็นว่าเมื่อไม่ได้ทำการกำหนดค่าให้กับตัวแปร NAME แล้ว เชลล์จะทำการแจ้งตัวแปรที่มีข้อผิดพลาดขึ้นมา (ในที่นี้คือ NAME) และตามด้วยข้อความผิดพลาดที่เราได้กำหนดไว้ (ในที่นี้คือ "Not assigned") แต่ทั้งตัวแปร STUDENT และ NAME ก็จะยังคงไม่ได้มีการกำหนดค่าไว้ตามเดิม (เป็น null) แต่หาก NAME ถูกกำหนดค่าเป็น "John" ก็จะทำการส่งค่า "John" ให้กับตัวแปร STUDENT
  7. ${varname:+word}
  8. วิธีการกำหนดตัวแปรแบบสุดท้ายจะแตกต่างจากสามวิธีข้างต้น กล่าวคือถ้า varname มีค่าและไม่เป็น null ก็จะส่งค่า word ไปให้ แต่ถ้าไม่มีค่า ก็จะส่งค่า null ไปให้สิ่งที่แตกต่างจากสามแบบข้างต้นก็คือ มีการส่งค่าไปให้หาก varname ถูกกำหนดค่ามา
    $ STUDENT=${NAME:+1}
    $ echo $STUDENT
    
    $ NAME="John"
    $ STUDENT=${NAME:+"exists"}
    $ echo $STUDENT
    exists
    
    จากตัวอย่างจะเห็นว่าถ้า NAME ไม่มีค่า STUDENT ก็จะไม่มีค่า (เป็น null) ตามไปด้วยแต่เมื่อมีการกำหนดค่า "John" ให้กับตัวแปร NAME ก็จะมีการส่งค่าของ "exist" ไปให้กับตัวแปร STUDENT

รูปแบบการกำหนดตัวแปรแบบมีเงื่อนไขทั้งสี่แบบข้างต้น สามารถนำมาใช้ในการตรวจสอบและจัดการกับตัวแปรที่ไม่ได้มีการกำหนดค่าตามที่ต้องการได้

การกำหนดออปชั่น และการเรียกเชลล์โดยมีออปชั่น

โดยทั่วไปแล้วเราสามารถกำหนด ออปชั่นให้กับเชลล์ได้ ซึ่งออปชั่นที่กำหนดให้มีหลายแบบ เช่นกำหนดให้เชลล์พิมพ์คำสั่งในลักษณะของการดีบักเชลล์สคริปต์ออกมาให้ หรือออปชั่นในลักษณะไม่ให้มีการตีความตัวอักษรพิเศษ (*,[]) เป็นต้น วิธีการกำหนดออปชั่นของเชลล์สามารถกำหนดโดยใช้ผ่านคำสั่ง set ได้ ดังตัวอย่างต่อไปนี้จะเป็นการกำหนดให้เชลล์ทำการพิมพ์ข้อความแบบดีบัก เพื่อให้เราสามารถตรวจสอบหาข้อผิดพลาดของเชลล์สคริปต์ได้โดยง่่าย ให้ทำการเพิ่มคำสั่ง "set -x" ไปที่บรรทัดแรกของโปรแกรม "ex2"
set -x
set PARA1 PARA2 PARA3 PARA4
echo "Name of shell script is" $0
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
echo "No. of argument is" $#
echo "Arguments are" $*
echo "Arguments are" $@

Now shift
shift
echo "Parameter 1 is" $1
echo "Parameter 2 is" $2
echo "Parameter 3 is" $3
และทำการสั่งงานโปรแกรม ex2 จะได้ผลลัพธต่อไปนี้
$ sh ex2
+ set PARA1 PARA2 PARA3 PARA4
+ echo Name of shell script is ex2
Name of shell script is ex2
+ echo Parameter 1 is PARA1
Parameter 1 is PARA1
+ echo Parameter 2 is PARA2
Parameter 2 is PARA2
+ echo Parameter 3 is PARA3
Parameter 3 is PARA3
+ echo No. of argument is 4
No. of argument is 4
+ echo Arguments are PARA1 PARA2 PARA3 PARA4
Arguments are PARA1 PARA2 PARA3 PARA4
+ echo Arguments are PARA1 PARA2 PARA3 PARA4
Arguments are PARA1 PARA2 PARA3 PARA4
+ shift
+ echo Parameter 1 is PARA2
Parameter 1 is PARA2
+ echo Parameter 2 is PARA3
Parameter 2 is PARA3
+ echo Parameter 3 is PARA4
Parameter 3 is PARA4
จะเห็นว่านอกจากผลลัพธ์ที่ได้จากคำสั่งในแต่ละบรรทัดของเชลล์สคริปต์แล้ว เชลล์ยังทำการพิมพ์ คำสั่งที่มีการใช้งานก่อนที่จะเกิดผลลัพธ์นั้นๆขึ้น (บรรทัดที่ขึ้นต้นด้วย "+") ซึ่งจะทำการแทนค่าของตัวแปรให้เรียบร้อย ด้วยวิธีนี้จะทำให้เราสามารถทำการตรวจสอบข้อผิดพลาดของเชลล์สคริปต์ได้ ออปชั่นอื่นๆ


HTML developed by Kaiwal Development Team (kaiwal@geocities.com)