บทความก่อนหน้าของผมเกี่ยวกับ "การทำความเข้าใจและการเขียนฟังก์ชันใน Shell Scripts" อาจให้คุณมีความเข้าใจพื้นฐานเกี่ยวกับการเขียนฟังก์ชันภายใต้เชลล์สคริปต์ ตอนนี้ถึงเวลาเจาะลึกคุณสมบัติของฟังก์ชัน เช่น การใช้ตัวแปร local และ recursion
ตัวแปร Local (Local Variables)
อะไรทำให้ตัวแปรเป็น local? ขึ้นอยู่กับบล็อกโค้ดเฉพาะที่ตัวแปรถูกประกาศ ตัวแปรที่ประกาศเป็น local จะเข้าถึงได้จากบล็อกโค้ดที่มันปรากฏเท่านั้น คือ ขอบเขต (scope) เป็น local เพื่ออธิบายสิ่งนี้ ให้ดูตัวอย่างด้านล่าง
#!/bin/bash
func( ) {
local i=10
j=20
echo "i from func = $i"
echo "j from func = $j"
}
echo "i outside func = $i"
echo "j outside func = $j"
func
echo "i outside func = $i"
echo "j outside func = $j"
exit 0
เมื่อรันสคริปต์ข้างต้น ผลลัพธ์จะเป็น
i outside func = j outside func = i from func = 10 j from func = 20 i outside func = j outside func = 20
สิ่งนี้เกิดเพราะฟังก์ชัน func ยังไม่ถูกเรียกในขณะที่คำสั่ง echo 2 ตัวแรก ทำงาน หลังจากเรียกฟังก์ชัน func คำสั่ง echo 2 ตัวเดิม ให้ผลลัพธ์ต่างออกไป ตัวแปร j ที่ประกาศภายใน func และไม่ใช่ local สามารถเข้าถึงได้หลังจากนั้น
ดังนั้นค่าของ j กลายเป็น 20 ตัวแปร local i เป็นอย่างไร? เนื่องจากขอบเขตอยู่ภายในฟังก์ชัน func ค่า 10 จึงเข้าถึงไม่ได้จากภายนอก โปรดสังเกตว่าตัวแปร j ที่ประกาศปกติภายใน func เป็น global โดยค่าเริ่มต้น
ตอนนี้คุณคุ้นเคยกับตัวแปร local และวิธีใช้ภายในบล็อกฟังก์ชันแล้ว มาไปที่ส่วนน่าสนใจที่สุดภายใต้ฟังก์ชัน คือ recursion
Recursion คืออะไร?
ฟังก์ชันที่เรียกตัวเอง โดยทั่วไปเรียกว่า recursion procedure หรือสามารถนิยามได้ว่า การแสดงอัลกอริทึมโดยใช้เวอร์ชันที่ง่ายกว่าของอัลกอริทึมเดียวกันนั้นพิจารณาตัวอย่างการหาแฟกทอเรียลของตัวเลข เรารู้ว่า n! = 1 x 2 x 3 x ... x (n-1) x n ดังนั้นเราสามารถเขียนความสัมพันธ์ซ้ำ (recurrence relation) ได้ดังนี้
n! = (n-1)! x n
ดังนั้นเราสามารถเรียกฟังก์ชันเดียวกันแบบ recursion และใช้ค่าที่คืนจากแต่ละการเรียก คูณกับผลลัพธ์ก่อนหน้าได้ คือ
5! = 4! x 5 4! = 3! x 4 3! = 2! x 3 2! = 1! x 2 1! = 0! x 1
Recursion ด้วยตัวแปร Local
ที่นี่เราลองเขียนสคริปต์หาแฟกทอเรียลของตัวเลขโดยใช้ตัวแปร local และ recursion
#!/bin/bash
fact( ) {
local num=$1
if [ $num -eq 0 ]; then
ret=1
else
temp=$((num-1))
fact $temp
ret=$((num*$?))
fi
return $ret
}
fact 5
echo "Factorial of 5 = $?"
exit 0
num เป็นตัวแปร local ใช้เก็บค่า n-1 ทุกครั้งที่เรียก ที่นี่เงื่อนไขฐานตรวจสอบว่าตัวเลขเท่ากับศูนย์หรือไม่ (เพราะ 0! = 1 และแฟกทอเรียลไม่ได้นิยามสำหรับตัวเลขลบ) เมื่อถึงเงื่อนไขฐาน มันคืนค่า 1 ให้ผู้เรียก ตอนนี้ num = 1 และ ret = 1 x 1
ในลحظةนี้ มันคืน 1 ให้ผู้เรียก ตอนนี้ num = 2 และ ret = 2 x 1 และต่อไปจนกว่า num = 5 ค่าที่คืนจะเป็น 24 และผลลัพธ์สุดท้ายคือ ret = 5 x 24 ผลลัพธ์สุดท้าย 120 ถูกส่งผ่านไปยังคำสั่งเรียกแรกและแสดงออกมา
มีปัญหาหนึ่งในสคริปต์ข้างต้น ตามที่อธิบายในบทความก่อนหน้า ฟังก์ชันไม่สามารถคืนจำนวนเต็มขนาดใหญ่ได้ ดังนั้นจึงเหลือให้ผู้ใช้หาวิธีแก้ไขปัญหานี้เอง
คำถาม เราสามารถทำ recursion ได้โดยไม่ใช้ตัวแปร local ไหม? คำตอบคือ ได้
Recursion โดยไม่ใช้ตัวแปร Local
ดูตัวอย่างต่อไปนี้สำหรับการแสดง ชุดฟีโบนัชชี โดยใช้ recursion ความสัมพันธ์ซ้ำพื้นฐานคือ
fib(0) = 0
fib(1) = 1
else
fib(n) = fib(n-1) + fib(n-2)
Fibonacci series using recursion
#!/bin/bash
fib( ) {
a=$1
if [ $a -lt 2 ]; then
echo $a
else
((--a))
b=$(fib $a)
((--a))
c=$(fib $a)
echo $((b+c))
fi
}
for i in $(seq 0 15)
do
out=$(fib $i)
echo $out
done
exit 0
ไม่ได้ใช้ตัวแปร local ในสคริปต์ข้างต้น หวังว่าคุณจะเข้าใจโฟลว์ของสคริปต์ระหว่างการรัน
ค่า 15 ที่นี่แทนจำนวนพจน์ใน ชุดฟีโบนัชชี ที่จะแสดง สังเกตอะไรพิเศษเกี่ยวกับการรันสคริปต์ข้างต้นไหม มันใช้เวลาสักพักใช่ไหม? Recursion ในสคริปต์ช้ากว่า recursion ในภาษาโปรแกรมเช่น C
ด้วยบทความนี้ ผมวางแผนจบส่วนของฟังก์ชันในเชลล์สคริปติ้ง ติดตาม Tecmint เพื่อรับบทความถัดไปเกี่ยวกับ อาร์เรย์ และอีกมากมาย...