# การเขียนโปรแกรมเชลล์ (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. สั่งผ่านเชลล์ ให้พิมพ์คำสั่งดังนี้

    $ sh ex1

วิธีการสั่งงานโปรแกรมแบบนี้ "ex1" จะต้องอนุญาตให้สามารถอ่านไฟล์ได้ (readable) ไม่เช่นนั้นเชลล์จะไม่สามารถทำให้โปรแกรมทำงานได้
2. สั่งโดยตรงจากชื่อโปรแกรม ให้พิมพ์คำสั่ง

    $ 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) ก่อน เนื่องจากจะอธิบายอ้างอิงถึงเรื่องโพรเซสบางส่วนจากหัวข้อดัวกล่าว จะช่วยทำให้เข้าใจเรื่องของโพรเซสได้มากขึ้น

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

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

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

- $0 เป็นชื่อของเชลล์สคริปต์ = "ex2"
- $1 เป็นอาร์กิวเมนต์ตัวแรก = "a"
- $2 เป็นอาร์กิวเมนต์ตัวที่สอง = "b"
- $3 เป็นอาร์กิวเมนต์ตัวที่สาม = "c"
- $# เป็นจำนวนอาร์กิวเมนต์ = 3 (นับ a, b และ c)
- $* เป็นการอ้างอาร์กิวเมนต์ทั้งหมดโดยรวมกันเข้าเป็นหนึ่งสตริง = "a b c"
- $@ เป็นการอ้างอาร์กิวเมนต์ทั้งหมดโดยแต่ละอาร์กิวเมนต์ก็ยังคงเป็นแต่ละสตริง = '"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}
รูปแบบการใช้งานแบบนี้ คือ หาก 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" ตามไปด้วย
2. ${varname:=word}
รูปแบบนี้คือถ้าหาก 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 ไปให้เหมือนกัน
3. ${varname:?message}
วิธีการกำหนดตัวแปรแบบนี้ หาก 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
4. ${varname:+word}
วิธีการกำหนดตัวแปรแบบสุดท้ายจะแตกต่างจากสามวิธีข้างต้น กล่าวคือถ้า 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

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

- -f ไม่แทนที่ชื่อไฟล์ (*,[])
- -u ออกจากเชลล์ทันทีที่พบตัวแปรที่ไม่ได้มีการกำหนดค่า

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