Input/output with files

ในการเขียนโปรแกรมคอมพิวเตอร์ ในขณะที่โปรแกรมทำงานข้อมูลทั้งหมดนั้นถูกเก็บอยู่ในหน่วยความจำ เมื่อโปรแกรมทำงานเสร็จสิ้นหรือโปรแกรมถูกปิดลงข้อมูลเหล่านั้นก็จะหายไป อย่างไรก็ตาม เราสามารถบันทึกข้อมูลในโปรแกรมลงบนไฟล์ได้ การบันทึกข้อมูลลงบนไฟล์นั้นทำให้ข้อมูลถูกบันทึกลงดิสอย่างถาวร และเราสามารถอ่านข้อมูลเหล่านั้นมาใช้ในภายหลังได้

ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับการอ่านข้อมูลจากไฟล์และการเขียนข้อมูลลงไปบนไฟล์ในภาษา Ruby ในภาษา Ruby นั้นมีคลาสและเมธอดมาตรฐานเป็นจำนวนมากที่ให้เราสามารถทำงานกับไฟล์ได้ เช่น คลาส File และ IO เป็นต้น

การเปิดและปิดไฟล์

ก่อนที่เราจะอ่านข้อมูลจากไฟล์หรือเขียนข้อมูลลงไปบนไฟล์ สิ่งแรกที่เราจะต้องทำก็คือเปิดไฟล์ขึ้นมาก่อน ในภาษา Ruby เราสามารถเปิดไฟล์ด้วยเมธอด open ซึ่งเป็นเมธอดที่อยู่ในคลาส File นี่เป็นรูปแบบการเปิดไฟล์ด้วยเมธอดดังกล่าว

file = File.open(file_name, mode)

จากรูปแบบการเปิดไฟล์ file_name นั้นเป็นชื่อของไฟล์ที่เราต้องการเปิด และ mode นั้นเป็น String ที่ระบุโหมดการเปิดเพื่อบอกว่าเราต้องการเปิดไฟล์เพื่ออ่านหรือเขียน หรือทั้งสองอย่าง นี่เป็นรายการของโหมดทั้งหมดสำหรับการเปิดไฟล์

โหมด คำอธิบาย
r เปิดไฟล์เพื่ออ่านเท่านั้น เริ่มที่จุดเริ่มต้นของไฟล์
r+ เปิดไฟล์เพื่ออ่านและเขียน เริ่มที่จุดเริ่มต้นของไฟล์
w เปิดไฟล์เพื่อเขียนเท่านั้น สร้างไฟล์ใหม่ หรือถ้ามีไฟล์อยู่แล้วลบข้อมูลเดิมออก
w+ เปิดไฟล์เพื่ออ่านและเขียน สร้างไฟล์ใหม่ หรือถ้ามีไฟล์อยู่แล้วลบข้อมูลเดิมออก
a เปิดไฟล์เพื่อเขียนต่อท้าย สร้างไฟล์ใหม่ถ้าหากยังไม่มี
a+ เปิดไฟล์เพื่อเขียนต่อท้ายและอ่าน สร้างไฟล์ใหม่ถ้าหากยังไม่มี
b เปิดไฟล์ในโหมดไบนารีไฟล์ โหมดนี้จะใช้ร่วมกับโหมดด้านบน

ค่าเริ่มต้นของการเปิดไฟล์นั้นจะถูกเปิดในโหมด Text ไฟล์เสมอ ยกเว้นแต่ว่าคุณระบุโหมดเป็น b เพื่อเปิดไฟล์ในโหมดไบนารี มาดูตัวอย่างของการเปิดไฟล์ในรูปแบบต่างๆ

f1 = File.open("file1.txt", "r")
f2 = File.open("file2.txt", "w+")
f3 = File.open("data", "wb")

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

ในคำสั่งแรกในตัวแปร f1 นั้นเป็นการเปิดไฟล์ในรูปแบบ Text สำหรับอ่านข้อมูล ตัวแปร f2 เปิดไฟล์ในรูปแบบ Text สำหรับอ่านและเขียน และสุดท้ายตัวแปร f3 เป็นการเปิดไฟล์ในโหมดไบนารีสำหรับเขียนข้อมูล ในการเปิดไฟล์ในโหมดไบนารี โหมด b นั้นต้องอยู่หลังจากโหมดปกติเสมอ

File.open("myfile.txt") # Current directory
File.open("../myfile.txt")  # In parent directory
File.open("D:/Project/ruby/files/myfile.txt")   # Absolute path

สำหรับการระบุชื่อของไฟล์เพื่อเปิดนั้น คุณสามารกำหนดแบบอ้างอิงจากไฟล์เดอร์ปัจจุบันหรือที่อยู่แบบเต็มของไฟล์ก็ได้ ดังตัวอย่างด้านบน

การอ่านข้อมูลจากไฟล์

หลังจากที่เราได้แนะนำเมธอดสำหรับการเปิดไฟล์ไปแล้ว ต่อไปมาดูตัวอย่างการอ่านไฟล์ในภาษา Ruby กัน เราจะเขียนโปรแกรมสำหรับอ่านไฟล์จาก myfile.txt ซึ่งเป็น Text ไฟล์ และนำข้อมูลที่อ่านได้มาแสดงผลออกหน้าจอ และนี่เป็นตัวอย่างข้อมูลในไฟล์ของเรา

myfile.txt
More and more I find myself wondering
if it's all worth fighting for.
For a feature without fear...
Yeah, it's worth it.

และนี่เป็นโค้ดของโปรแกรมสำหรับอ่าน Text ไฟล์ในภาษา Ruby

reading_file.rb
file = File.open("myfile.txt", "r")

while line = file.gets
    puts line
end

file.close

ในตัวอย่างนั้นเป็นโค้ดสำหรับอ่านไฟล์ในภาษา Ruby ในตอนแรกเราได้เปิดไฟล์ myfile.txt ด้วยเมธอด open และกำหนดโหมดของการเปิดเป็น r ซึ่งหมายความว่าเราต้องการเปิดไฟล์นี้เพื่ออ่านข้อมูล

while line = file.gets

หลังจากนั้นเราใช้เมธอด gets เพื่ออ่านข้อมูลจากไฟล์ที่ละบรรทัด เมธอดนี้จะอ่านข้อมูลทีละบรรทัดและเก็บค่าที่อ่านได้ไว้ในตัวแปร line และเราได้ใช้คำสั่ง while เพื่อวนอ่านไฟล์จนกว่าจะถึงจุดจบของไฟล์ (End of file หรือ EOF) และเมื่อถึงจุดจบของไฟล์ค่าที่ได้จากเมธอด gets จะเป็น nil และเราได้แสดงข้อมูลที่อ่านมาได้ออกทางหน้าจอ

file.close

ในตอนท้ายเราได้ปิดไฟล์ด้วยเมธอด close เพื่อให้ไฟล์ระบบปฏิบัติการสามารถนำไปใช้กับโปรแกรมอื่นๆ ได้ต่อไป สิ่งที่สำคัญในการทำงานกับไฟล์ก็คือเราต้องปิดไฟล์เสมอเมื่อใช้งานเสร็จ

การเขียนข้อมูลลงไปบนไฟล์

หลังจากที่เราได้อ่านข้อมูลจากไฟล์เรียบร้อยแล้ว ต่อไปมาดูการเขียนข้อมูลลงไปบนไฟล์กันบ้าง การเขียนข้อมูลลงบนไฟล์นั้นจะช่วยให้เราสามารถบันทึกข้อมูลไว้ในไฟล์ และสามารถอ่านข้อมูลเหล่านั้นขึ้นมาใช้ในภายหลัง

ตัวอย่างของการเขียนไฟล์ก็คือเมื่อคุณพิมพ์งานที่โปรแกรมอย่าง Microsoft Word คุณสามารถบันทึกไฟล์งานของคุณเพื่อเปิดในภายหลังได้ หรือแม้กระทั่งเมื่อตอนที่คุณเล่นเกม คุณสามารถบันทึกสถานะของเกมที่คุณเล่น ทำให้คุณสามารถกลับมาเล่นต่อจุดเดิมได้

ดังนั้นในตัวอย่างของเราจะเป็นการเขียนข้อความง่ายๆ ลงไปบนไฟล์ นี่เป็นตัวอย่างการเขียนข้อมูลลงบน Text ไฟล์ในภาษา Ruby

writing_file.rb
file = File.open("myfile2.txt", "w")

file.puts("Thus the heavens and the earth,")
file.puts("and all the host of them, were finished.")
file.puts("And on the seventh day God ended His work")

file.close

ในตัวอย่าง เราได้เปิดไฟล์ myfile2.txt ในโหมดสำหรับการเขียนข้อมูลลงไปบนไฟล์ ในการเปิดไฟล์ด้วยโหมดนี้ ถ้าหากมีไฟล์อยู่แล้วข้อมูลเดิมจะถูกลบออกและแทนที่ด้วยข้อมูลใหม่

file.puts("Thus the heavens and the earth,")
file.puts("and all the host of them, were finished.")
file.puts("And on the seventh day God ended His work")

หลังจากนั้นเราเขียนข้อมูลลงบนไฟล์ด้วยเมธอด puts สำหรับการเขียนข้อมูลลงบนไฟล์ด้วยเมธอดนี้นั้น มันจะเพิ่มการขึ้นบรรทัดใหม่ (\n) ให้อัตโนมัติ ซึ่งหมายความว่าเราได้เขียนข้อมูลลงบนไฟล์ทีละบรรทัดนั่นเอง

file.close

และเมื่อเขียนไฟล์เสร็จแล้ว อย่าลืมที่จะปิดไฟล์ด้วยเมธอด close เสมอ หลังจากนั้นเปิดดูไฟล์ที่เราเพิ่งจะเขียนไป นี่จะเป็นข้อมูลที่อยู่ในไฟล์ myfile2.txt

myfile2.txt
Thus the heavens and the earth,
and all the host of them, were finished.
And on the seventh day God ended His work

การเขียนข้อมูลต่อท้ายไฟล์เดิม

ในตัวอย่างก่อนหน้านั้นเราได้เปิดไฟล์ด้วยโหมด w นั่นหมายความว่าทุกครั้งที่โปรแกรมเปิดไฟล์ขึ้นมา มันจะลบข้อมูลเดิมเพื่อเขียนไฟล์ใหม่เสมอ ในบางครั้ง คุณอาจจะต้องการเขียนข้อมูลเพื่อต่อท้ายไฟล์เดิม โดยที่ข้อมูลเดิมยังอยู่ แน่นอนว่าคุณสามารถทำเช่นนั้นได้ด้วยการเปิดไฟล์ด้วยโหมดเขียนต่อท้ายนั่นเอง

appending_file.rb
print("Enter your note: ")
note = gets.chomp

file = File.open("mynote.txt", "a")
file.puts(note)
file.close

ในตัวอย่าง เป็นโปรแกรมบันทึกโน้ตหรือสิ่งที่เราอยากจะทำ ในโปรแกรมนั้นเราต้องการบันทึกสิ่งใหม่เพิ่มเข้าไปเรื่อยๆ โดยที่ข้อมูลเดิมที่เคยบันทึกก็ยังอยู่ ดังนั้นเราจึงเปิดไฟล์ด้วยโหมด a เพื่อเขียนข้อมูลต่อท้ายไฟล์เดิม และไฟล์ใหม่จะถูกสร้างถ้าหากมันไม่มีอยู่

ทุกอย่างนั้นยังคงเหมือนเดิม เราได้เขียนข้อมูลลงบนไฟล์ด้วยเมธอด puts และปิดไฟล์ด้วยเมธอด close หลังจากที่เขียนข้อมูลเสร็จ

Enter your note: Hello World
Enter your note: Learning Ruby
Enter your note: Climbing in the weekend

นี่เป็นผลลัพธ์การทำงานของโปรแกรม เราได้รันโปรแกรมสามครั้งและกรอกโน้ตที่เราต้องการบันทึกลงไปบนไฟล์ ตอนนี้ให้คุณลองเปิดดูไฟล์ mynote.txt เพื่อดูข้อมูลที่เรามี

การลบและการเปลี่ยนชื่อไฟล์

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

File.rename("old_name", "new_name")
File.delete("myfile")

ในตัวอย่าง เมธอด rename ใช้สำหรับเปลี่ยนชื่อไฟล์เดิมให้เป็นชื่อใหม่ ในตัวอย่างเราได้เปลี่ยนชื่อไฟล์ old_name ให้เป็น new_name ต่อมาเมธอด delete นั้นใช้สำหรับลบไฟล์ออกไปจากคอมพิวเตอร์ของคุณ โดยระบุชื่อไฟล์ที่ต้องการลบให้กับเมธอด

ในการใช้งานเมธอดทั้งสองนั้นไฟล์จะต้องมีอยู่จริง ไม่เช่นนั้นจะเกิดข้อผิดพลาด Errno::ENOENT ขึ้น อย่างไรก็ตาม เราสามารถใช้เมธอด exist? เพื่อตรวจสอบว่ามีไฟล์อยู่ได้ นี่เป็นตัวอย่าง

delete_file.rb
print("Enter file name to delete: ")
file_name = gets.chomp

if File.exist?(file_name)
    File.delete(file_name)
    puts "#{file_name} has deleted" 
else
    puts "File #{file_name} does not exist"
end

ในตัวอย่าง โปรแกรมได้ถามให้กรอกชื่อไฟล์ที่ต้องการลบผ่านทางคีย์บอร์ด ก่อนลบไฟล์เราได้ตรวจสอบว่าไฟล์มีอยู่หรือไม่ด้วยเมธอด exist? ซึ่งเมธอดนี้ส่งค่ากลับเป็น Boolean

Enter file name to delete: myfile.txt
myfile.txt has been deleted

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

การจัดการข้อผิดพลาดเกี่ยวกับไฟล์

ในการทำงานกับไฟล์นั้นข้อผิดพลาดบางอย่างอาจเกิดขึ้นได้ ยกตัวอย่างเช่น การเปิดไฟล์ที่ไม่มีอยู่ การอ่านไฟล์ที่อ่านจบแล้ว หรือแม้กระทั่งการเขียนลงบนไฟล์ที่ปิดไปแล้ว ไม่ว่าจะด้วยเหตุผลใดก็ตาม ข้อผิดพลาดเหล่านี้สามารถทำให้โปรแกรมของเราหยุดทำงานได้

ในกรณีนี้ เราสามารถใช้คำสั่ง rescue เพื่อตรวจสอบข้อผิดพลาดเหล่านั้นได้ถ้าหากมันเกิดขึ้น หลังจากนั้นเราสามารถจัดการกับข้อผิดพลาดที่เกิดขึ้นให้เหมาะสมกับโปรแกรมของเรา นี่เป็นตัวอย่าง

handing_error.rb
begin

    file = File.open("myfile.txt", "r")
    s = file.read
    file.gets     #=> nil
    file.readline #=> EOFError: end of file reached

rescue EOFError => e
    puts "No more line to read"
    puts e.inspect
rescue => e
    # Standard error
    puts e.inspect
end

ในตัวอย่างนั้นเป็นโปรแกรมสำหรับอ่านข้อมูลจากไฟล์ myfile.txt ที่เป็นไฟล์จากตัวอย่างแรกในบทนี้ ในตอนนี้ เราได้ใช้เมธอด read ในการอ่านไฟล์ซึ่งเมธอดนี้อ่านข้อมูลของไฟล์ทั้งหมดเพียงครั้งเดียวมาเก็บไว้ในตัวแปร s

file.gets     #=> nil

ต่อมาเราได้อ่านไฟล์อีกครั้งด้วยเมธอด gets เนื่องจากข้อมูลในไฟล์ถูกอ่านหมดแล้ว ข้อมูลที่ได้จากเมธอดจึงเป็น nil นั่นหมายความตัวชี้ไฟล์อยู่ที่ตำแหน่งสุดท้ายของไฟล์ หรือไม่มีข้อมูลเหลือให้อ่านแล้ว

file.readline #=> EOFError: end of file reached

หลังจากนั้นเราพยายามอ่านไฟล์อีกครั้งด้วยเมธอด readline ในคำสั่งนี้นั้นทำให้เกิดข้อผิดพลาด EOFError ขึ้นนั่นเป็นเพราะว่าสำหรับเมธอดนี้ถ้าหากเราพยายามอ่านไฟล์ที่อ่านเสร็จแล้ว มันจะส่งข้อผิดพลาดดังกล่าวขึ้นมา ซึ่งแตกต่างจากเมธอด gets ที่จะส่งค่ากลับเป็น nil แทน

begin
...
# Do something that may raise error
rescue
...
# Handle here
end

ดังนั้นในการที่จะป้องกันไม่ให้โปรแกรมหยุดทำงาน เราได้คำสั่ง begin และ rescue เข้ามาช่วย ซึ่งใโค้ดที่คาดว่าจะเกิดข้อผิดพลาดขึ้นจะเขียนไว้ในบล็อคของคำสั่ง begin และเมื่อเกิดข้อผิดพลาดขึ้น โปรแกรมจะข้ามมาทำงานในบล็อคของคำสั่ง rescue ที่ตรงกับประเภทของข้อผิดพลาดที่กำหนดไว้ในบล็อคทันที

No more line to read
#<EOFError: end of file reached>

และนี่เป็นผลลัพธ์การทำงานของโปรแกรม ที่เกิดข้อผิดพลาดขึ้นในกรณีที่เราอ่านไฟล์จากไฟล์ที่อ่านเสร็จ (End of file หรือ EOF) แล้ว

มาดูอีกตัวอย่างสำหรับการเปิดไฟล์ที่ไม่มีอยู่ ซึ่งสามารถทำให้เกิดข้อผิดพลากได้เช่นกัน

handing_error2.rb
begin

    file = File.open("notfound.txt", "r")
    while line = file.gets
        puts line
    end
    file.close

rescue Errno::ENOENT => e
    # File does not exist
    puts e.message
rescue => e
    # Standard error
    puts e.message
end

ในตัวอย่าง สมมติว่าโปรแกรมของเราพยายามเปิดไฟล์ที่ไม่มีอยู่ ถ้าหากเป็นเช่นนั้นโปรแกรมจะเกิดข้อผิดพลาด Errno::ENOENT ขึ้น

rescue Errno::ENOENT => e
    # File does not exist
    puts e.message

ดังนั้นเราได้ใช้บล็อคคำสั่ง rescue เพื่อในการตรวจจับข้อผิดพลาดที่อาจจะเกิดขึ้น และภายในบล็อคของคำสั่ง rescue เราสามารถแสดงข้อความบางอย่างเพื่อบอกกับผู้ใช้งานได้ ในตัวอย่าง เราได้แสดงข้อมูลของข้อผิดพลาดด้วยเมธอด message จากออบเจ็ค Errno::ENOENT

No such file or directory @ rb_sysopen - notfound.txt

นี่เป็นผลลัพธ์การทำงานของโปรแกรม หลังจากที่เราพยายามเปิดไฟล์ที่ไม่มีอยู่

ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการทำงานกับไฟล์ในภาษา Ruby เราได้พูดถึงการอ่านข้อมูลจากไฟล์และการเขียนข้อมูลลงบนไฟล์ พร้อมทั้งการเขียนข้อมูลต่อท้ายไฟล์เดิมในกรณีที่เราต้องการให้ข้อมูลเดิมยังอยู่ นอกจากนี้ เราได้พูดถึงการจัดการข้อผิดพลาดที่อาจจะเกิดขึ้นเมื่อทำงานกับไฟล์โดยการใช้คำสั่ง begin และ rescue