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

เชี่ยวชาญ Bash Loops: คู่มือ Step-by-step ประมวลผลไฟล์อย่างมีประสิทธิภาพ

เหตุผลทั่วไปที่อยากเรียน Unix Shell คือปลดล็อกพลัง Batch Processing หากต้องการกระทำชุดหนึ่งกับหลายไฟล์ วิธีหนึ่งคือสร้างคำสั่ง Iterate ไปยังไฟล์เหล่านั้น ในภาษาโปรแกรมเรียกว่า Execution Control ตัวอย่างทั่วไปคือ for Loop

for Loop คือสูตรรายละเอียดว่าต้องการให้คอมพิวเตอร์ทำอะไร สำหรับ Data Object แต่ละตัว (เช่น ไฟล์) ที่ระบุ

Classic For Loop แบบคลาสสิก

Loop ง่ายสำหรับลองคือ วิเคราะห์ชุดไฟล์ อาจไม่มีประโยชน์ด้วยตัวเอง แต่วิธีปลอดภאותพิสูจน์ว่าจัดการไฟล์แต่ละตัวใน Directory ได้แยกกัน สร้าง Test Environment เรียบง่าย: สร้าง Directory วาง Copy ไฟล์บางไฟล์เข้าไป ไฟล์อะไรก็ได้ ต่อไปต้องการ Graphic Files (JPEG, PNG) สร้างโฟลเดอร์และ Copy ไฟล์ได้ด้วย File Manager หรือ Terminal:

$ mkdir example
$ cp ~/Pictures/vacation/*.{png,jpg} example

Change Directory ไปโฟลเดอร์ใหม่ List ไฟล์ยืนยัน Environment:

$ cd example
$ ls -1
cat.jpg
design_maori.png
otago.jpg
waterfall.png

Syntax Loop ผ่านไฟล์แต่ละตัว: สร้าง Variable (f for file) Define Data Set ที่ Variable จะ Cycle ผ่าน กรณีนี้ ทุกไฟล์ใน Current Directory ด้วย Wildcard * (Match ทุกอย่าง) จบ Clause ด้วย Semicolon (;):

$ for f in * ;

ขึ้นอยู่กับความชอบ กด Return ได้ Shell ไม่ Execute จน Loop สมบูรณ์ Syntax

ต่อไป Define สิ่งที่เกิดขึ้นแต่ละ Iteration ใช้ file Command ดูข้อมูลเล็กน้อยของแต่ละไฟล์ แทนด้วย Variable f (เติม $ หน้าเพื่อบอก Shell ให้แทนค่า):

do file $f ;

จบ Clause อีก Semicolon ปิด Loop:

done

กด Return เริ่ม Shell Cycle ทุกอย่างใน Current Directory for Loop Assign ไฟล์แต่ละตัวให้ Variable f รันคำสั่ง:

$ for f in * ; do
> file $f ;
> done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced

เขียนบรรทัดเดียวได้:

$ for f in *; do file $f; done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced

ทั้ง Multi-line และ Single-line เท่ากันสำหรับ Shell ผลลัพธ์เหมือนกัน

ตัวอย่างปฏิบัติจริง

ตัวอย่าง Loop 有用ในการคำนวณประจำวัน สมมติมีรูปวันหยุดเยอะ ส่งให้เพื่อน ไฟล์ใหญ่เกิน Email และ Upload ไป Photo-sharing ไม่อาสิ หน่อยเวอร์ชันเว็บขนาดเล็ก แต่มี 100 รูป ไม่อยากลดทีละรูป

ติดตั้ง ImageMagick ด้วย Package Manager บน Linux, BSD, Mac Fedora/RHEL:

$ sudo dnf install ImageMagick

Ubuntu/Debian:

$ sudo apt install ImageMagick

BSD ใช้ ports หรือ pkgsrc Mac ใช้ Homebrew หรือ MacPorts

ติดตั้ง ImageMagick แล้วมีคำสั่งใหม่จัดการรูป

สร้าง Destination Directory สำหรับไฟล์ที่จะสร้าง:

$ mkdir tmp

ลดขนาดรูปเหลือ 33% ด้วย Loop:

$ for f in * ; do convert $f -scale 33% tmp/$f ; done

ดูในโฟลเดอร์ tmp จะเห็นรูปที่ Scale แล้ว

ใช้คำสั่งจำนวนมากใน Loop ได้ หากต้องการ Action ซับซ้อนกับ Batch Files วาง Workflow ทั้งหมดระหว่าง do และ done ของ for Loop เช่น Copy รูปที่ Process แล้วไป Shared Photo Directory บน Web Host และลบไฟล์ Local:

$ for f in * ; do 
convert $f -scale 33% tmp/$f
scp -i seth_web tmp/$f seth@example.com:~/public_html
trash tmp/$f ;
done

แต่ละไฟล์ที่ Loop Process รัน 3 คำสั่งอัตโนมัติ ประมวลผล 10 รูป ประหยัด 30 คำสั่ง และอย่างน้อย 30 นาที

จำกัด Loop

Loop ไม่จำเป็นต้องดูทุกไฟล์ อาจ Process แค่ JPEG ใน Example Directory:

$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done
$ ls -m tmp
cat.jpg, otago.jpg

หรือแทนไฟล์ ต้อง Repeat Action จำนวนครั้งเฉพาะ for Loop Variable Define โดย Data ที่ให้มา สร้าง Loop Iterate ตัวเลขแทนไฟล์:

$ for n in {0..4}; do echo $n ; done
0
1
2
3
4

Loop เพิ่มเติม

ตอนนี้รู้พอสร้าง Loop เอง จนกว่าจะคล่อง ใช้กับ Copies ของไฟล์ที่ต้องการ Process และใช้คำสั่งที่มี Safeguards Built-in ป้องกัน Clobber Data และผิดพลาดที่ซ่อมไม่ได้ เช่น Rename Directory เต็มไปด้วยชื่อเดียวกัน ทับซ้อนกัน

หัวข้อ For Loop ขั้นสูง อ่านต่อ

ไม่ใช่ทุก Shell เป็น Bash

Keyword for Built-in ใน Bash Shell Shell คล้ายๆ ใช้ Keyword และ Syntax เดียวกัน แต่ Shell บางตัว เช่น tcsh ใช้ Keyword ต่างหาก เช่น foreach

ใน tcsh Syntax คล้ายกันในจิตวิญญาณ แต่ Strict กว่า Bash ในโค้ดด้านล่าง อย่าพิมพ์ String foreach? บรรทัด 2 และ 3 มันคือ Secondary Prompt แจ้งว่ายัง Build Loop อยู่

$ foreach f (*)
foreach? file $f
foreach? end
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced

ใน tcsh foreach และ end ต้องอยู่บรรทัดเดียวแยกกัน จึงไม่สร้าง for Loop บรรทัดเดียวได้เหมือน Bash และ Shell คล้ายๆ

For Loops กับคำสั่ง find

ทฤษฎีแล้ว อาจหา Shell ที่ไม่มี for Loop Function หรือชอบคำสั่งอื่นที่มี Features เพิ่ม

คำสั่ง find เป็นอีกวิธี Implement Functionality ของ for Loop มีหลายวิธี Define Scope ไฟล์ที่ Include ใน Loop และ Options สำหรับ Parallel Processing

find ช่วยหาไฟล์บน Hard Drives Syntax เรียบง่าย: ระบุ Path ที่ต้องการค้นหา find หาไฟล์และ Directories ทั้งหมด:

$ find . 
.
./cat.jpg
./design_maori.png
./otago.jpg
./waterfall.png

Filter ผลลัพธ์ด้วยส่วนหนึ่งของชื่อ:

$ find . -name "*jpg"
./cat.jpg
./otago.jpg

สิ่งดีของ find คือ ไฟล์แต่ละตัวที่หาได้ ส่งเข้า Loop ได้ด้วย Flag -exec เช่น Scale Down แค่ PNG Photos ใน Example Directory:

$ find . -name "*png" -exec convert {} -scale 33% tmp/{} \;
$ ls -m tmp
design_maori.png, waterfall.png

ใน Clause -exec ตัวอักษร Bracket {} แทน Item ที่ find กำลัง Process (ไฟล์ที่ลงท้าย PNG ที่เจอ ทีละตัว) Clause -exec ต้องจบด้วย Semicolon แต่ Bash มักพยายามใช้ Semicolon เอง "Escape" Semicolon ด้วย Backslash (\;) ให้ find รู้ว่า Semicolon นั้นเป็น Terminator ของมัน

find ทำงานดีมาก และบางครั้งดีเกินไป เช่น Reuse หา PNG สำหรับ Photo Process อื่น จะได้ Error:

$ find . -name "*png" -exec convert {} -flip -flop tmp/{} \; 
convert: unable to open image `tmp/./tmp/design_maori.png':
No such file or directory @ error/blob.c/OpenBlob/2643.
...

find หา PNG ทั้งหมด ไม่ใช่แค่ Current Directory (.) แต่รวมที่ Process ไปแล้วใน Subdirectory tmp บางกรณีต้องการ find ค้นหา Current Directory รวม Subdirectories ทั้งหมด (และ Subdirectories ของ Subdirectories) เป็น Recursive Processing Tool แรง โดยเฉพาะ File Structures ซับซ้อน (Directories ศิลปิน มี Directories อัลบั้ม เต็มเพลง) แต่จำกัดได้ด้วย Option -maxdepth

หา PNG เฉพาะ Current Directory (ไม่นับ Subdirectories):

$ find . -maxdepth 1 -name "*png"

หาและ Process ไฟล์ใน Current Directory บวก Subdirectory เพิ่ม 1 Level เพิ่ม Max Depth 1:

$ find . -maxdepth 2 -name "*png"

Default คือ Descend เข้า Subdirectories ทั้งหมด

Loop เพื่อความสนุกและประโยชน์

ยิ่งใช้ Loops ยิ่งประหยัดเวลาและความพยายาม และรับมือ Task ใหญ่ขึ้น คุณเป็น User คนเดียว แต่ Loop ที่คิดดี ทำให้ Computer ทำงานหนัก

ควรจัดการ Loop เหมือนคำสั่งอื่น เก็บไว้ใกล้มือ ตอนต้อง Repeat Action เดียวหรือสองบนหลายไฟล์ แต่ก็เป็น Gateway สู่ Programming จริง หากต้องทำ Task ซับซ้อนกับไฟล์จำนวนมาก ใช้เวลาวางแผน Workflow หากทำได้บนไฟล์เดียว การห่อ Process ที่ Repeat ได้ใน for Loop ค่อนข้างง่าย "Programming" ที่ต้องการคือเข้าใจ Variables ทำงานยังไง และจัดระเบียบแยก Unprocessed และ Processed ด้วย Practice เล็กน้อย คุณจะจาก Linux User กลายเป็น Linux User ที่เขียน Loop ได้ ไปทำให้ Computer ทำงานให้คุณ!