2010-05-13 29 views
0

Je dois enregistrer quelques informations sur certains fichiers. Rien de trop chic, alors j'ai pensé que j'irais avec une simple ligne par fichier texte. Quelque chose comme ceci:Evasion en toute sécurité et relecture d'un chemin de fichier dans ruby ​​

# 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 

Bien sûr, cela ne fonctionne pas parce qu'un nom de fichier peut contenir des espaces (rupture scanf) et les sauts de ligne (rupture IO # chacun).

Le problème de saut de ligne peut être évité en laissant tomber l'utilisation de chacun et aller avec un tas de gets (» «)

while not io.eof? 
    mtime = Time.at(io.gets(" ").to_i) 
    name = io.gets " " 
    hash = io.gets "\n" 
end 

Jongler avec les espaces dans les noms est une autre affaire. Nous devons maintenant nous échapper.
note: J'aime l'espace comme un délimiteur d'enregistrement, mais je n'aurais aucun problème à le changer pour un plus facile à utiliser. Dans le cas des noms de fichiers cependant, le seul qui pourrait aider est ascii nul "\ 0" mais un fichier délimité nul n'est plus vraiment un fichier texte ...

J'ai d'abord eu un mur de texte détaillant les itérations de ma lutte pour faire une bonne fonction d'échappement et sa réciprocité mais c'était juste ennuyeux et pas vraiment utile. Je vais vous donner le résultat final:

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 

Je ne suis pas content du tout avec read_name. Bien trop long et akward, je pense que ça ne devrait pas être si dur.

Tout en essayant de faire ce travail que j'ai essayé de trouver d'autres façons:

  • la bittorrent manière codée/php serialize: le préfixe du nom de fichier avec la longueur du nom puis juste io.read (name_len.to_i). Cela fonctionne mais c'est un vrai pita pour éditer le fichier à la main. À ce stade, nous sommes à mi-chemin d'un format binaire. String # inspect: Celui-ci a l'air expressément fait à cet effet! Sauf qu'il semble que la seule façon de récupérer la valeur est l'évaluation. Je déteste l'idée d'évaluer une chaîne que je n'ai pas générée à partir de données fiables.

Donc. Des avis ? N'y at-il pas de lib qui peut faire tout cela? Est-ce que je manque quelque chose d'évident? Comment feriez-vous cela ?

Répondre

1

Quand vous dites "sauvegarder", voulez-vous dire stocker les informations dans un fichier?

Vous pouvez utiliser le CSV module à partir de la bibliothèque standard Ruby. Cela signifierait que votre délimiteur est une virgule plutôt que de l'espace, mais il traiterait tout ce qui s'échappe et s'échappera pour vous.

  • Si une valeur contient des espaces que la valeur est inclus dans "quotes"

  • Si une valeur contient des guillemets alors un caractère de quote est comme 2 guillemets par exemple"hello" deviendraient """hello"""

Pour écrire les détails dans un fichier:

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 

Pour les relues:

CSV::Reader.parse(File.open('csvout', 'rb')) do |row| 
    p row 
end 
+0

ah csv! N'y avais pas pensé. Probablement parce que je ne suis pas fan du format et de ses règles baroques (et souvent redéfinies). Mais c'est là, et ça fonctionne. Et puisque je choisis l'auteur et le lecteur, je n'aurai aucun problème de compatibilité, n'est-ce pas? Sérieusement, je teste le module et il semble assez solide. Je devrais réécrire toute la logique autour du lecteur mais c'est normal quand on fait ce genre de changement de framework. – user336851

+0

module réussi à gérer tout ce que je l'ai jeté. – user336851

1

CSV, comme mentionné, est un bon choix. Un autre est YAML ("Yaml n'est pas un langage de balisage"), qui peut gérer plus de données arbitraires que CSV. Voici quelques données:

require 'pp' 
require 'yaml' 

h = { 
    :first_name => 'Fred', 
    :last_name => 'Flinstone', 
    :children => ['Bam Bam', 'Pebbles'], 
    :exclamation => 'Yabba Dabba Doo', 
} 

Écrivons les données dans un fichier au format YAML:

File.open('/tmp/foo.yaml', 'w') do |file| 
    file.write h.to_yaml 
end 

Maintenant, nous allons voir ce que l'YAML ressemble:

$ cat /tmp/foo.yaml 
--- 
:exclamation: Yabba Dabba Doo 
:first_name: Fred 
:last_name: Flinstone 
:children: 
- Bam Bam 
- Pebbles 

Et enfin nous allons reconstituer les les données du fichier YAML:

pp YAML.load_file('/tmp/foo.yaml') 
# => {:exclamation=>"Yabba Dabba Doo", 
# => :first_name=>"Fred", 
# => :last_name=>"Flinstone", 
# => :children=>["Bam Bam", "Pebbles"]} 
+0

Hmmm, j'aime le yaml et je l'ai même utilisé une fois dans une situation où json n'allait pas le couper (putain ces cycles de graphique!). Mais ce n'est pas vraiment ce dont j'ai besoin ici. Solution assez pauvre pour une base de données d'une table au format texte. En fait, je ne vois vraiment aucun intérêt à utiliser yaml pour des données non hiérarchiques. – user336851