คำบรรยายพิเศษสำหรับสัปดาห์ที่ 11 -- ISBN-CheckDigit.txt เลขตรวจสอบ (check digit) ------------------------ เลขตรวจสอบ คือการเพิ่มข้อมูลตัวเลขเพื่อให้สามารถตรวจจับความผิดพลาด (error detection) เช่นเดียวกับ parity bit และ CRC ที่ใช้ในการสื่อสารข้อมูล ต่างกันที่การสื่อสารข้อมูลความผิดพลาดเกิดจากสัญญาณรบกวนรูปแบบต่างๆ ส่วนเลขตรวจสอบใช้ตรวจจับความผิดพลาดที่เกิดขึ้นจากมนุษย์ เลขมาตรฐานสากลประจำหนังสือ ------------------------ หอสมุดแห่งชาติ กำหนดความหมายของ "เลขมาตรฐานสากลประจำหนังสือ" (International Standard Book Number, ISBN) ไว้ดังนี้ (https://www.nlt.go.th/th/บริการ/เลขมาตรฐานสากลประจำหนังสือ-isbn) "เลขมาตรฐานสากลประจำหนังสือ คือเลขรหัสสากลที่กำหนดขึ้นใช้สำหรับสิ่งพิมพ์ประเภทหนังสือทั่ว ๆ ไป มีความมุ่งหมายเพื่อให้เป็นเอกลักษณ์ของหนังสือแต่ละชื่อเรื่อง เพื่อความสะดวก ถูกต้องในการควบคุมข้อมูลสิ่งพิมพ์ในด้านการสั่งซื้อ การแลกเปลี่ยน การบริการ เมื่อกำหนดให้หนังสือไปแล้ว ห้ามนำกลับมาใช้ซ้ำอีกโดยเด็ดขาด" -- เลขมาตรฐานสากลประจำหนังสือ เมื่อเริ่มใช้งานในปี 1967 เป็นตัวเลข 10 หลัก โดย 9 หลักแรก เป็นตัวเลขระหว่าง 0 - 9, ส่วนหลักสุดท้ายเป็นเลขตรวจสอบที่มีค่าระหว่าง 0 - 9 และอักษร X มีการใช้มานานกว่า 30 ปี ก่อนจะมีการกำหนดให้เป็นเลขมาตรฐาน 13 หลัก เมื่อวันที่ 1 มกราคม 2007 เลขมาตรฐานใหม่นี้เรียกว่า ISBN-13 ซึ่งตัวเลขทุกหลักมีค่าระหว่าง 0 - 9 และเรียกเลขประจำหนังสือ 10 หลักแบบเดิมว่า ISBN-10 เลขตรวจสอบสำหรับ ISBN-10 คำนวณจากการคูณเลขประจำหนังสือทั้ง 9 หลัก ด้วย "น้ำหนัก" ที่มีค่าลดหลั่นตามลำดับจาก 10 - 2 และนำผลคูณที่ได้มารวมกัน หารด้วย 11 เพื่อหาเศษที่เหลือ ซึ่งมีค่าระหว่าง 0 - 10 และเพื่อให้สะดวกในการตรวจสอบความถูกต้องของเลขมาตรฐานสากลประจำหนังสือทั้งสิบหลัก จึงกำหนดให้เลขตรงจสอบเป็น inverse ของเศษจากการหารที่ได้ d[10] = 11 - { 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] (mod 11) } ในกรณีที่นิพจน์ใน {...} มีค่าเป็น 0 จะทำให้ d[10] มีค่าเป็น 11 เป็นตัวเลขสองหลักซึ่งไม่ถูกต้อง จึงต้อง mod อีกครั้งหนึ่ง d[10] = d[10] (mod 11) และในกรณีที่เลขตรวจสอบ, d[10], ที่คำนวณได้ มีค่าเป็น 10 ให้แทนด้วย "X" (10 ในระบบเลขโรมัน) เพื่อให้เลขตรวจสอบเป็นหลักเดียว สำหรับการตรวจสอบว่าเลขมาตรฐานสากลประจำหนังสือ เช่น 0-7167-8233-2 ว่าถูกต้องหรือไม่ ทำได้ดังนี้ p = 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] + d[10] (mod 11) หาก p = 0 แสดงว่าเป็นเลขตรวจสอบที่ถูกต้อง การคำนวณเลขตรวจสอบ ISBN-10 -------------------------- เลือกตัวอย่าง ISBN-10 ที่มีการใช้งานจริง และมีเลขตรวจสอบต่างกัน มาจำนวนหนึ่ง เช่น 0-937175-73-0 POSIX Programmer's Guide 0-201-33133-0 The Open Process Specification 0-201-44124-1 Introduction to Automata Theory, Languages, and Computation 0-7167-8233-2 Foundations of Computer Science 0-87692-596-4 The C Programming Language 0-13-030263-5 Logic and its Applications 0-201-10194-7 Compilers; Principles, Techniques, and Tools 0-13-131509-9 The Standard C Library 0-07-115468-X Introduction to Languages and Theory of Computation 0-201-12078-X The C++ Programming Language จากตัวอย่างจะเห็นได้ว่าเลขมาตรฐานทั้ง 10 หลักแบ่งออกเป็น 4 กลุ่ม แต่ละกลุ่มแยกจากกันด้วยเครื่องหมายขีด, "-", เพียงแต่จำนวนตัวเลขในแต่ละกลุ่มไม่เท่ากัน ทำให้ตำแหน่งของ "-" ไม่แน่นอน จึงต้องใช้วิธีกำจัดออกโดยใช้คำสั่ง tr (translate หรือ transliterate) โดยใช้ตัวเลือก -d และกำหนดเซตของอักขระที่ต้องการกำจัดเป็น "-" โดยรับข้อมูลจากคำสั่ง echo ผ่าน pipe เช่น $ echo 0-7167-8233 | tr -d "-" 071678233 ข้อดีของวิธีการนี้คือหากใช้ป้อนเฉพาะตัวเลข ไม่มีเครื่องหมาย "-", บรรทัดคำสั่งนี้ก็ยังคงทำงานได้เช่นเดิม $ echo 071678233 | tr -d "-" 071678233 เมื่อกำจัด "-" ออกแล้ว ต้องแยกตัวเลขแต่ละหลักออกเพื่อนำไปคูณกับ "น้ำหนัก" ประจำหลัก การแยกที่น่าจะดีที่สุดแบบหนึ่งคือ แยกออกเป็นรายการของตัวเลข (list of digits) ซึ่งทำได้โดยใช้คำสั่ง fold --------------------------------------------------------------------------------- fold - ตัดแบ่งบรรทัดข้อความ เป็นบรรทัดใหม่ที่มีตามความกว้างที่กำหนด หากกำหนดความกว้างเป็น 1 โดยใช้ตัวเลือก "-w 1" จะได้ผลลัพธ์เป็นบรรทัดที่มีตัวอักษร บรรทัดละ 1 ตัว เปรียบเหมือนพับกระดาษ WWW ตัวอักษรในแต่ละบรรทัด จะแยกออกเป็นส่วนๆ แต่ละส่วนมีจำนวนตัวอักษร เท่าๆ กัน ใช้ได้กับตัวอักษรแบบ fixed spacing แต่ใช้ไม่ได้สำหรับ proportional spacing เช่น $ echo 071678233 | tr -d "-" | fold -w 1 0 7 ... 3 คำสั่ง fold จะแยกสายอักขระออกเป็นอักขระบรรทัดละตัว หรือแต่ละบรรทัดอยู่ในรูปของ <ตัวเลขหนึ่งหลัก><รหัสขั้นบรรทัดใหม่> # หรือ --------------------------------------------------------------------------------- การสร้างรายการของตัวเลขด้งคำสั่ง fold มีผลให้สมาชิกแต่ละตัวของ "รายการ" (list) แยกกันด้วย แต่ไม่ว่าเครื่องหมายวรรคตอนจะเป็น หรือ วรรค ก็สามารถใช้งานเป็น "รายการ" ควบคุมการทำงานของโครงสร้าง for ได้ นำผลลัพธ์ของ pipeline ไปกำหนดให้กับตัวแปร list โดยการทำ command substitution, $(...) $ list=$(echo 071678233 | tr -d "-" | fold -w 1) เมื่อแสดงค่าในตัวแปร $list ด้วยคำสั่ง echo จะได้ผล เช่น $ echo $list 0 7 1 6 7 8 2 3 3 ซึ่งเป็นไปตามปกติ คือ echo ที่จะคั่นสมาชิกแต่ละตัวด้วยอักขระตัวแรกในตัวแปร IFS (Internal Field Separator) ซึ่งมีค่าโดยปริยายเป็น "วรรค" ไม่ใช้เครื่องหมายวรรคตอนเดิมที่ใช้อยู่ หากต้องการใช้รายการพร้อมด้วยเครื่องหมายวรรคตอนดั้งเดิม ต้องกำกับตัวแปร $list ด้วยเครื่องหมายคำพูด เช่น $ echo "$list" 0 7 ... 3 จากตัวแปร $list ที่ได้ นำมาใช้เป็น "รายการ" สำหรับการทำซ้ำด้วย for ได้ดังนี้ for i in $list; do ....... done ซึ่งจะมีผลให้ในแต่ละรอบ ค่าของตัวแปร i ที่ใช้เป็นดัชนีของ loop จะมีค่าตามคัวเลขในรายการจากซ้ายไปขวาคือ 0, 7, 1, 6, 7, 8, 2, 3, 3 ตามลำดับ ดังนั้นค่าของ $i ในแต่ละรอบจึงเป็นตัวแทนของตัวเลขแต่ละหลัก สามารถนำมาคูณด้วย "น้ำหนัก" ประจำหลัก เพื่อใช้หาผลบวกสะสมได้ ตามการคำนวณดังนี้ d[10] = 11 - { 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] (mod 11) } d[10] = 11 - { 10*0 + 9*7 + 8*1 + 7*6 + 6*7 + 5*8 + 4*2 + 3*3 + 2*3 (mod 11) } d[10] = 11 - { 0 + 63 + 8 + 42 + 42 + 40 + 8 + 9 + 6 (mod 11) } d[10] = 11 - 218 (mod 11) d[10] = 11 - 9 d[10] = 2 ก่อนที่จะเริ่มการคำนวณ "ผลบวกสะสม" (sum) มีค่าเป็น 0 ส่วน "น้ำหนัก" (weight) ที่ใช้เป็นตัวคูณ หลักแรกมีค่า 10 จากนั้นลดลงรอบละ 1 ตามลำดับ จนมีค่าเป็น 2 ที่หลักสุดท้าย หรือ ค่าเริ่มต้นของ "น้ำหนัก" = 10, และ ค่าที่เปลี่ยนไปในแต่ละรอบคือ ลดลง 1 สำหรับค่าสุดท้ายของน้ำหนัก ไม่เป็นประเด็นที่ต้องสนใจ เพราะจะถูกกำหนดด้วยจำนวนการทำซ้ำ ซึ่งได้แก่จำนวนสมาชิกของรายการโดยอัตโนมัติ เขียนโปรแกรมด้วยคำสั่งลำลองได้ดังนี้ ขั้นตอนวิธี: การคำนวณเลขตรวจสอบสำหรับ ISBN-10 ข้อมูลเข้า: เลขมาตรฐานสากลประจำหนังสือ 9 หลัก (จะคั่นด้วย "-" หรือไม่ก็ได้) โดยรับมาจาก command line arguments กำหนดให้เป็นอาร์กิวเมนต๋หมายเลข 1 ข้อมูลออก: เลขตรวจสอบ 1 หลัก ซึ่งอาจเป็น 0 - 9 หรือ X begin สร้างรายการของตัวเลช isbn จากอาร์กิวเมนต์ตัวแรก เก็บในตัวแปร list { $ list=$(echo $1 | tr -d "-" | fold -w 1) } กำหนดค่าเริ่มต้นของ "ผลบวกสะสม" = 0 กำหนดค่าเริ่มต้นของ "น้ำหนัก" = 10 { คำนวณเลขตรวจสอบ, d[10] = 11 - { 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] (mod 11) } { และ d[10] = d[10] (mod 11) } for i in list; do ผลบวกสะสม = ผลบวกสะสม (เดิม) + ตัวเลขประจำหลัก (ตัวแปร i) * น้ำหนัก ลดค่าน้ำหนักลง 1 done เลขตรวจสอบ = 11 - [ผลบวกสะสม (mod 11)] เลขตรวจสอบ = เลขตรวจสอบ (mod 11) { หากเลขตรวจสอบที่คำนวณได้เป็น 10 ให้เปลี่ยนเป็น X } if เลขตรวจสอบ = 10 then เลขตรวจสอบ = X end {if} คืนค่า หรือแสดงค่าของเลขตรวจสอบ end Coding - การเขียนคำสั่งตามขั้นตอนวิธีเป็น bash shell script --------------------------------------------------- $ cat isbn1.sh #!/bin/bash # สร้างรายการของตัวเลช isbn จากอาร์กิวเมนต์ตัวแรก เก็บในตัวแปร list list=$(echo $1 | tr -d "-" | fold -w 1) # กำหนดค่าเริ่มต้นของ "ผลบวกสะสม"และ "น้ำหนัก" sum=0; w=10 # คำนวณเลขตรวจสอบ, d[10] = 11 - { 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] (mod 11) } # และเลขตรวจสอบ = d[10] (mod 11) for i in $list; do sum=$((sum + $i * w)) w=$((w - 1)) done check=$(( (11 - $sum % 11) % 11)) # หากเลขตรวจสอบที่คำนวณได้เป็น 10 ให้เปลี่ยนเป็น X if [ $check -eq 10 ]; then check=X fi # แสดง ISBN-10 ที่สมบูรณ์: อาร์กิวเมนต์ของผู้ใช้-เลขตรวจสอบ echo "$1-$check" ตัวอย่างการทำงาน การคำนวณเลขตรวจสอบของหนังสือ POSIX Programmer's Guide ซึ่งมีเลขสากลประจำหนังสือเป็น 0-937175-73-0 ป้อนเฉพาะตัวเลข 9 หลักแรก โปรแกรมจะคำนวณและแสดงหมายเลข ISBN ที่สมบูรณ์ $ isbn1.sh 0-13-030263 0-937175-73-0 เมื่อทดลองให้โปรแกรมทำงานกับตัวอย่าง ISBN-10 ทั้งหมดที่เลือกมา และได้ผลลัพธ์ที่ถูกต้อง แสดงว่าส่วนที่สำคัญที่สุดของโปรแกรมทำงานถูกต้องสมบูรณ์แล้ว เหลือเพียงตรวจสอบความสมเหตุสมผลของข้อมูลเข้า (data validate) และการกำหนดข่าวสารแสดงความผิดพลาดที่เข้าใจได้ง่ายและตรงประเด็น ให้เป็นไปตามการออกแบบส่วนต่อประสานกับผู้ใช้ (user interface) ที่มีวัตถุประสงค์ให้ใช้งานง่าย ใช้ทักษะส่วนบุคคลน้อย ต้องการการฝึกอบรมให้ใช้งานน้อย โปรแกรม หรือ script จะทำงานได้ต่อเมื่อผู้ใช้ป้อนชุดคัวเลขจำนวน 9 หลัก หากไม่มีข้อมูลดังกล่าว หรือเป็นข้อมูลอื่นที่ไม่ใช่ชุดตัวเลขดังกล่าวนี้ โปรแกรมไม่ควรทำงาน แต่ควรแสดง "คำแนะนำ", "คำเตือน", หรือ "ข่าวสารแสดงความผิดพลาด" ที่เหมาะสม สำหรับการตรวจสอบเท่าที่พอจะเป็นไปได้มีดังนี้ การทดสอบจำนวนอาร์กิวเมนต์ ---------------------- การทดสอบว่าผู้ใช้ป้อนข้อมูลเข้ามาหรือไม่ ทำได้โดยการทดสอบตัวแปร $# ซึ่งแสดงจำนวนอาร์กิวเมนต์ของบรรทัดคำสั่ง หากมีค่าเป็น 1 แสดงว่ามีข้อมูล ในกรณีที่มีค่าเป็น 0 (ไม่มีอาร์กิวเมนต์) หรือมีค่ามากกว่า 1 อาจแสดงว่าผู้ใช้ไม่แน่ใจรูปแบบการงานใช้คำสั่ง จึงควรแสดงคำแนะนำรูปแบบการใช้งานคำสั่ง เช่น <ชื่อโปรแกรม> - calculate check digit for ISBN-10 Usage: <ชื่อโปรแกรม> <9-digit ISBN> และเมื่อมีจำนวนอาร์กิวเมนต์ไม่ถูกต้อง โปรแกรมไม่ควรทำงานต่อ ควรเลิกทำงาน โดยอาจจะกำหนด exit status หรือไม่ก็ได้ ในที่นี้กำหยดเป็น 1 นำงานที่วิเคราะห์ไว้แล้วนี้มาเขียนเป็น script ได้ดังนี้ if [ $# -lt 1 ]; then echo "$(basename $0) - Calculate ISBN-10 check digit" echo "Usage: $(basename $0) <9-digit-isbn>" exit 1 fi การทดสอบสายอักขระว่าง ------------------- เมื่อผ่านการทดสอบแสดงว่ามีอาร์กิวเมนต์ จำนวน 1 ตัว ตามที่โปรแกรมต้องการ ควรจะต้องทดสอบต่อไปว่าเป็นอาร์กิวเมนต์นั้นเป็นสายอักขระว่างหรือไม่ เพราะหากผู้ใช้เรียก script ด้วย $ isbn.sh "" จะผ่านการทดสอบจำนวนอาร์กิวเมนต์ 1 ตัว แต่ทำให้โปรแกรมทำงานผิดพลาด จึงต้องทดสอบว่าอาร์กิวเมนต์นั้นเป็นสายอักขระว่าง (empty string) หรือไม่ หากเป็นควรแสดงข่าวสารแสดงความผิดพลาด ในที่นี้ขอใช้ข่าวสารอย่างง่ายเป็น "Error: invalid ISBN" การทดสอบว่าสายอักขระว่างหรือไม่ ทำได้โดยใช้ตัวเลือก -z ของคำสั่ง test เขียนเป็น script ได้ดังนี้ if [ -z $1 ]; then echo "Error: invalid ISBN" exit 2 fi เมื่อทดสอบได้แล้วว่าอาร์กิวเมนต์ของผู้ใช้ไม่เป็นสายอักขระว่าง ควรได้ดำเนินการกำจัดเครื่องหมายขีด, "-", และเก็บผลลัพธ์ที่ได้ในตัวแปรเพื่อใช้ทดสอบและทำงานต่อไป ในที่นี้กำหนดให้เป็นตัวแปร arg $ arg=$(echo $1 | tr -d "-") งานลำดับต่อไปคือการทดสอบว่าค่าของตัวแปร $arg เป็น string of digits หรือไม่ เพราะหากหากไม่ใช่ ก็ไม่สามารถทำงานต่อได้ การทดสอบ string of digits ------------------------- การทดสอบว่าสายอักขระนั้นเป็น string of digits หรือไม่ อาจทำได้หลายวิธี วิธีการอย่างง่ายเขียนนิพจน์ปรกติเพื่อทดสอบด้วยคำสั่ง grep โดยกำหนดไม่ให้มีการแสดงผลลัพธ์ แต่จะใช้ exit status ของ grep ว่าทำงานสำเร็จหรือไม่ (พบข้อความที่ตรงกับรูปแบบที่กำหนดหรือไม่) โดยใช้ตัวเลือก -q (quiet) เพื่อใช้เป็นเงื่อนไขของโครงสร้าง if นิพจน์ปรกติสำหรับตรวจสอบ string of digits เช่น "071678233" สร้างจากคุณสมบัติคือ ขึ้นต้นด้วยตัวเลข (anchor: ^), ลงท้ายด้วยตัวเลข (anchor: $), และต้องมีตัวเลขอย่างน้อยหนึ่งตัว ([0-9]+) รวมกันเป็นนิพจน์ปรกติ "^[0-9]+$" นิพจน์ปรกตินี้ใช้ทดสอบว่าเป็นข้อมูลที่ได้รับเป็นตัวเลขล้วน แต่เงื่อนไขที่ต้องการคือ หากข้อมูลไม่เป็นตัวเลขล้วนให้แสดงข่าวสารความผิดพลาดและเลิกทำงาน จึงต้องใช้ตัวเลือก -v (inverse) ของ grep เนื่องจากสายอักขระที่จะให้ grep ตรวจสอบเก็บอยู่ในตัวแปร จึงต้องขยายค่าและส่งผ่าน pipeline ไปยัง grep ด้วยคำสั่ง echo และกำหนดให้ใช้ข่าวสารแสดงความผิดพลาดเดียวกับกรณีเป็นสายอีกขระว่าง เขียนเป็น script ได้ดังนี้ if ( echo $arg | egrep -qv "^[0-9]+$" ); then # (...) จัดกลุ่มให้ดูง่าย ไม่จำเป็นต้องมี echo "Error: invalid ISBN" exit 3 fi เมื่อได้ว่าอาร์กิวเมนต์ของผู้ใช้เป็นตัวเลขล้วนแล้ว ควรทดสอบต่อไปว่ามีจำนวนเป็น 9 หลักหรือไม่ หากจำนวนหลักไม่ตรงตามที่กำหนด ข้อมูลเข้าก็ไม่สมเหตุสมผล ให้แสดงข่าวสารความผิดพลาดและเลิกทำงาน การทดสอบจำนวนตัวอักขระ --------------------- การทดสอบความยาวของสายอักขระ ทำได้หลายวิธี วิธีการที่ง่ายและใช้ได้กับเชลล์ทุกแบบคือการนับจำนวนคำในสายอักขระโดยใช้คำสั่ง wc (word count) เช่น $ echo -n "hello" | wc -c 5 ประเด็นที่สำคัญของวิธีการนี้คือ ต้องใช้ตัวเลือก -n มิฉะนั้น echo จะเพิ่ม ที่ท้ายคำ ทำให้ wc นับ นั้นด้วย ทำให้ได้ความยาวมากกว่าความเป็นจริง และต้องใช้ตัวเลือก -c เพื่อให้แสดงผลเฉพาะจำนวนอักขระเท่านั้น และเมื่อต้องการนำผลลัพธ์ของ pipeline: echo -n $arg | wc -c มาใช้ในการเปรียบเทียบ ต้องทำผ่าน command substitution เมื่อกำหนดให้ใช้ข่าวสารแสดงความผิดพลาดเดียวกับกรณีเป็นสายอีกขระว่าง เขียนเป็น script ได้ดังนี้ if [ $(echo -n $1 | wc -c) -ne 9 ]; then echo "Error: invalid ISBN" exit 4 fi ในกรณีที่แน่ใจว่า script มีการใช้งานเฉพาะใน Bourne again shell, สามารถใช้คำสั่งเฉพาะของ bash คือ ${#ชื่อตัวแปร} เพื่อหาจำนวนตัวอักษรสำหรับค่าของตัวแปรนั้นได้ เช่น if [ ${#arg} -ne 9 ]; then # ${#arg} - คำสั่งเฉพาะ bash echo "Error: invalid ISBN" exit 4 fi Logical connectives ------------------- การทดสอบว่าข้อมูลเข้าเป็น string of digits และมีจำนวน 9 หลัก สามารถทำในคำสั่ง if เดียวกันได้ โดยใช้ Logical connective ได้แก่ && (and) และ || (or) และในกรณีที่ต้องการนิเสธ ทำด้วย ! (not) เมื่อต้องใช้งาน Logical operators, ¬, ⋀, และ ⋁ ในภาษาใดภาษาหนึ่ง ต้องคำนึงถึงลำดับการทำงาน (precedence), ลักษณะการทำงาน (associativity) และ short-circuit evaluation หรือการข้ามการทำงานที่เหลือเมื่อรู้คำตอบแล้ว เช่น <เงื่อนไข 1> ⋀ <เงื่อนไข 2> หากเงื่อนไขแรกเป็นเท็จ จะได้คำตอบของเงื่อนไขทั้งสองเป็นเท็จ โดยไม่ต้องทดสอบหรือทำงานตามเงื่อนไข 2 เลย และในทำนองเดียวกัน <เงื่อนไข 1> ⋁ <เงื่อนไข 2> หากเงื่อนไขแรกเป็นจริง จะได้คำตอบของเงื่อนไขทั้งสองเป็นจริงทันที ภาษา C ที่นิสิตเรียนและใช้งานกำหนดลำดับความสำคัญตามลำดับดังนี้คือ !, &&, และ || ในกรณีที่มีลำดับความสำคัญเท่ากัน ทำจากซ้ายไปขวา และเป็น short-circuit evaluation, bash แตกต่างออกไปจากภาษา C และภาษาอื่นๆ เพราะ && และ || มีลำดับการความสำคัญเท่ากัน จึงมีการทำงานจากซ้ายไปขวา และการทดสอบเงื่อนไขเป็น short-circuit evaluation เงื่อนไขทั้งสองเป็นดังนี้ if ( echo $arg | egrep -qv "^[0-9]+$" ); then # (...) จัดกลุ่มให้ดูง่าย ไม่จำเป็นต้องมี echo "Error: invalid ISBN" exit 3 fi if [ $(echo -n $1 | wc -c) -ne 9 ]; then echo "Error: invalid ISBN" exit 4 fi การเชื่อมเงื่อนไขการตรวจสอบ string of digits และการตรวจสอบจำนวนหลัก เป็นเงื่อนไขเดียวกัน เป็นดังนี้มีลักษณะดังนี้ if ( echo $arg | egrep -qv "^[0-9]+$" ) || [ $( echo -n $arg | wc -c) -ne $n ]; then echo "Error: invalid ISBN " exit 3 fi เมื่อนำการตรวจสอบข้อมูล และการจัดการกับความผิดพลาด รวมเข้ากับการตำนวณเลขตรวจสอบ จะได้โปรแกรมที่สมบูรณ์ดังนี้ $ cat isbn2.sh #!/bin/bash # กำหนดค่าคงที่: จำนวหลักของ ISBN = 9 readonly n=9 # ---------------------------------------------------------------------------------- # ความสมเหตุสมผลของข้อมูล และการจัดการกับความผิดพลาด (data validation and error handling) # ---------------------------------------------------------------------------------- # ตรวจสอบจำนวนอาร์กิวเมนต์ if [ $# -lt 1 ]; then echo "$(basename $0) - Calculate ISBN-10 check digit" echo "Usage: $(basename $0) <9-digit ISBN>" exit 1 fi # ตรวจสอบว่าเป็นสายอักขระว่างหรือไม่? if [ -z $1 ]; then echo "Error: invalid ISBN" exit 2 fi # กำจัดเครื่องหมายขีด ออกจากข้อมูลเข้า arg=$(echo $1 | tr -d "-") # ตรวจสอบว่าเป็น string-of-digits และมจำนวนอักขระถูกต้อง if ( echo $arg | egrep -qv "^[0-9]+$" ) || [ $( echo -n $arg | wc -c) -ne $n ]; then echo "Error: invalid ISBN " exit 3 fi # ---------------------------- # คำนวณเลขตรวจสอบสำหรับ ISBN-10 # ---------------------------- # สร้างรายการของตัวเลข ISBN มีสมาชิกบรรทัดละตัว list=$(echo $arg | fold -w1) # กำหนดค่าเริ่มต้นของ "ผลบวกสะสม"และ "น้ำหนัก" sum=0; w=10 # คำนวณเลขตรวจสอบ, d[10] = 11 - { 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] (mod 11) } # และเลขตรวจสอบ = d[10] (mod 11) for i in $list; do sum=$((sum + $i * w)) w=$((w - 1)) done check=$(( (11 - $sum % 11) % 11)) # หากเลขตรวจสอบที่คำนวณได้เป็น 10 ให้เปลี่ยนเป็น X if [ $check -eq 10 ]; then check=X fi # แสดง ISBN-10 ที่สมบูรณ์: อาร์กิวเมนต์ของผู้ใช้-เลขตรวจสอบ echo "$1-$check" แบบฝึกหัด -------- จงเขียนโปรแกรมสำหรับตรวจสอบความถูกต้องของเลขมาตรฐานสากลประจำหนังสือ (ISBN-10) จากสูตร p = 10*d[1] + 9*d[2] + 8*d[3] + ... + 2*d[9] + d[10] (mod 11) หาก p = 0 แสดงว่าเป็นเลขมาตรฐานสากลประจำหนังสือตรวจสอบที่สมเหตุสมผล