الهروب بأمان وقراءة مسار الملف في روبي
سؤال
أحتاج إلى حفظ بعض المعلومات حول بعض الملفات. لا شيء يتوهم للغاية ، لذا اعتقدت أنني سأذهب مع سطر واحد بسيط لكل ملف نصي. شيء من هذا القبيل :
# write
io.print "%i %s %s\n" % [File.mtime(fname), fname, Digest::SHA1.file(fname).hexdigest]
# read
io.each do |line|
mtime, name, hash = line.scanf "%i %s %s"
end
بالطبع هذا لا يعمل لأن اسم الملف يمكن أن يحتوي على مسافات (كسر SCANF) وكسر الأسطر (كسر IO#لكل منهما).
يمكن تجنب مشكلة كسر الخط عن طريق إسقاط استخدام كل منها والذهاب مع مجموعة من GETS ('')
while not io.eof?
mtime = Time.at(io.gets(" ").to_i)
name = io.gets " "
hash = io.gets "\n"
end
التعامل مع المساحات في الأسماء مسألة أخرى. الآن نحن بحاجة إلى القيام ببعض الهروب.
ملاحظة: أحب الفضاء كحدد سجل ، لكنني لم أواجه أي مشكلة في تغييره لأسهل للاستخدام. في حالة أسماء الملفات ، فإن الشخص الوحيد الذي يمكن أن يساعد هو ASCII NUL " 0" ولكن لم يعد ملف محدد NUL هو ملف نصي حقًا ...
كان لدي في البداية جدارًا من النص الذي يوضح تكرارات كفاحي لجعل وظيفة هروب صحيحة وتبادلها ، لكنها كانت مملة وليست مفيدة حقًا. سأعطيك النتيجة النهائية فقط:
def write_name(io, val)
io << val.gsub(/([\\ ])/, "\\\\\\1") # yes that' 6 backslashes !
end
def read_name(io)
name, continued = "", true
while continued
continued = false
name += io.gets(' ').gsub(/\\(.)/) do |c|
if c=="\\\\"
"\\"
elsif c=="\\ "
continued=true
" "
else
raise "unexpected backslash escape : %p (%s %i)" % [c, io.path, io.pos]
end
end
end
return name.chomp(' ')
end
لست سعيدًا على الإطلاق مع read_name. طريقة طويلة جدًا و akward ، أشعر أنه لا ينبغي أن يكون بهذه الصعوبة.
أثناء محاولة القيام بهذا العمل ، حاولت الخروج بطرق أخرى:
طريقة BitTorrent المشفرة / PHP Serialize: بادئة اسم الملف بطول الاسم ثم فقط io.read (name_len.to_i). إنه يعمل ولكنه pita حقيقي لتحرير الملف باليد. في هذه المرحلة ، نحن في منتصف الطريق إلى تنسيق ثنائي.
سلسلة#فحص: يبدو هذا واحد مصنوعًا صراحة لهذا الغرض! إلا أنه يبدو أن الطريقة الوحيدة لاستعادة القيمة هي من خلال Eval. أنا أكره فكرة تقييم سلسلة لم أقم بإنشائها من البيانات الموثوقة.
لذا. آراء؟ أليس هناك بعض lib التي يمكن أن تفعل كل هذا؟ هل أفتقد شيئًا واضحًا؟ كيف يمكنك أن تفعل ذلك ؟
المحلول
عندما تقول "حفظ" هل تقصد تخزين المعلومات في ملف؟
يمكنك استخدام وحدة CSV من مكتبة روبي القياسية. هذا يعني أن محددك هو فاصلة بدلاً من الفضاء ولكنه سيتعامل مع كل الهروب والتشجيع بالنسبة لك.
إذا كانت القيمة تحتوي على مسافات يتم إرفاقها
"quotes"
إذا كانت القيمة تحتوي على علامات اقتباس ، فسيتم هروب حرف اقتباس كحرفتين اقتباس على سبيل المثال
"hello"
قد يصبح"""hello"""
لكتابة التفاصيل إلى ملف:
require 'csv'
outfile = File.open('csvout', 'wb')
CSV::Writer.generate(outfile) do |csv|
csv << [File.mtime(fname), fname, Digest::SHA1.file(fname).hexdigest]
end
outfile.close
لقراءتها مرة أخرى:
CSV::Reader.parse(File.open('csvout', 'rb')) do |row|
p row
end
نصائح أخرى
CSV ، كما ذكرنا ، هو خيار جيد. آخر هو Yaml ("Yaml ليس لغة ترميز") ، والتي يمكن أن تتعامل مع بيانات أكثر تعسفية أكثر من CSV. هذه بعض البيانات:
require 'pp'
require 'yaml'
h = {
:first_name => 'Fred',
:last_name => 'Flinstone',
:children => ['Bam Bam', 'Pebbles'],
:exclamation => 'Yabba Dabba Doo',
}
دعنا نكتب البيانات إلى ملف بتنسيق YAML:
File.open('/tmp/foo.yaml', 'w') do |file|
file.write h.to_yaml
end
الآن دعونا نرى كيف يبدو Yaml:
$ cat /tmp/foo.yaml
---
:exclamation: Yabba Dabba Doo
:first_name: Fred
:last_name: Flinstone
:children:
- Bam Bam
- Pebbles
وأخيراً ، دعنا نعيد إعادة تهيئة البيانات من ملف YAML:
pp YAML.load_file('/tmp/foo.yaml')
# => {:exclamation=>"Yabba Dabba Doo",
# => :first_name=>"Fred",
# => :last_name=>"Flinstone",
# => :children=>["Bam Bam", "Pebbles"]}