เขียนโปรแกรมล้อ pipe ใน shell script
ใน shell script มัน pipe ต่อกันไปได้เรื่อย ๆ แบบนี้
cat input.txt | \
prog1 x | \
prog2 y | \
prog3 z
- cat อ่านไฟล์ input1.txt แล้วก็ส่งออกมาทาง stdout ต่อมาก็ส่งเข้า prog1 ทาง stdin
- prog1 อ่านข้อมูลจาก stdin ที่ cat ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
- prog2 อ่านข้อมูลจาก stdin ที่ prog1 ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
- prog3 อ่านข้อมูลจาก stdin ที่ prog2 ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
- โดยที่ prog1 prog2 prog3 ก็รับค่า x y z เข้ามาทาง argument ด้วย
โปรแกรมแบบนี้จะเขียนใหม่เป็น Ruby ก็จะได้ประมาณนี้ หรือจะใช้ Java ก็จะคล้าย ๆ กัน
p read_file("input.txt").prog1(:x)
.prog2(:y)
.prog3(:z)
พวก prog1 prog2 prog3 กลายเป็นชื่อ method ใน object ที่ดูจาก code ก็ไม่รู้ว่า object อะไร หรือกระทั่งตอนรันก็ไม่รู้ว่า prog1 ส่งข้อมูลอะไรออกมา
ถ้าเป็น shell script ก็ใส่ tee เข้าไปได้เลย
cat input.txt | \
prog1 x | \
prog2 y | \
tee debug.txt | \
prog3 z
แล้วก็ไปเปิดดูไฟล์ debug.txt ทีหลังได้ โดยที่โปรแกรมโดยรวมก็ยังทำงานปกติ
แต่ถ้าเป็น Ruby หรือ Java ก็จะกลายเป็นท่ายาก เพราะว่าใส่ tee เข้าไปใน object ก็ยากเพราะไม่รู้ว่า object ไหน class อะไร ใส่ไปแล้วจะมั่วหรือเปล่า
โปรแกรมมันก็จะออกมาประมาณนี้แทน
tmp = read_file("input.txt").prog1(:x)
.prog2(:y)
write_file(tmp)
p tmp.prog3(:z)
ถ้าลองเปลี่ยนเป็น Clojure มันก็จะประมาณนี้
(-> (slurp "input.txt")
(prog1 :x)
(prog2 :y)
(prog3 :z)
println)
พวก prog1 prog2 prog3 ก็จะเป็นแค่ function ธรรมดาแทนที่จะเป็น method
นอกจากนั้นก็ยังเขียน tee ขึ้นมาง่าย ๆ ได้แบบนี้
(defn tee [data path]
(spit path data)
data)
แล้วก็เอา tee ไปแทรกได้แบบ shell script
(-> (slurp "input.txt")
(prog1 :x)
(prog2 :y)
(tee "debug.txt")
(prog3 :z)
println)
โปรแกรมทั้งหมดผมไม่ได้ลองรันจริง ๆ นะครับ อาจจะเจ๊งได้ จาก blog นี้ผมรู้สึกว่าตอบคำถามตัวเองได้ว่าทำไมเวลาเขียน Clojure หรือ Common Lisp ในโปรแกรมที่ค่อนข้างซับซ้อนแล้วรู้สึกสะดวกกว่า Ruby เอาจริงๆ จะเขียน Ruby หรือ Java ให้คล้าย ๆ Clojure ก็คงทำได้ แต่เขียนไปก็จะถูกเรียกว่าไม่ idiomatic อันนี้ก็เป็นอีกเหตุผลนึงให้เปลี่ยนภาษาเพราะว่าภาษามันพ่วงวัฒนธรรมมาด้วย