Escapar com segurança e ler de volta um caminho de arquivo em rubi
Pergunta
Preciso salvar algumas informações sobre alguns arquivos. Nada muito chique, então eu pensei em ir com uma linha de texto simples por item. Algo assim :
# 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
É claro que isso não funciona porque um nome de arquivo pode conter espaços (quebra de scanf) e quebras de linha (quebrando IO#cada).
O problema da quebra da linha pode ser evitado soltando o uso de cada um e indo com um monte de gets ('')
while not io.eof?
mtime = Time.at(io.gets(" ").to_i)
name = io.gets " "
hash = io.gets "\n"
end
Lidar com espaços nos nomes é outra questão. Agora precisamos escapar.
Nota: eu gosto do espaço como delimitador de registro, mas não teria problema em alterá -lo para um mais fácil de usar. No caso de nomes de arquivos, porém, o único que poderia ajudar é o ASCII NUL " 0", mas um arquivo delimitado nu não é realmente realmente um arquivo de texto ...
Inicialmente, tive uma parede de texto detalhando as iterações da minha luta para fazer uma função de fuga correta e seu recíproco, mas era apenas chato e não é realmente útil. Vou apenas te dar o resultado 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
Não estou feliz com read_name. Muito tempo e Akward, sinto que não deveria ser tão difícil.
Enquanto tentava fazer esse trabalho, tentei inventar outras maneiras:
O BitTorrent codificado / php serializa maneira: prefixo o nome do arquivo com o comprimento do nome e depois apenas io.read (name_len.to_i). Funciona, mas é um verdadeiro pita editar o arquivo manualmente. Neste ponto, estamos no meio do caminho para um formato binário.
String#Inspecione: Este parece feito expressamente feito para esse fim! Exceto que parece que a única maneira de recuperar o valor é através da avaliação. Eu odeio a ideia de avaliar uma string que não gera a partir de dados confiáveis.
Então. Opiniões? Não existe alguma lib que possa fazer tudo isso? Estou perdendo algo óbvio? Como você faria isso ?
Solução
Quando você diz "salvar", você quer dizer armazenar as informações em um arquivo?
Você poderia usar o Módulo CSV Da biblioteca padrão do Ruby. Isso significaria que seu delimitador é vírgula e não espaço, mas lidaria com toda a fuga e descontagem para você.
Se um valor contiver espaços, esse valor está entre
"quotes"
Se um valor contiver cotações, um caractere de cotação é escapado como 2 caracteres de cotação, por exemplo,
"hello"
se tornaria"""hello"""
Para escrever os detalhes em um arquivo:
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
Para lê -los de volta:
CSV::Reader.parse(File.open('csvout', 'rb')) do |row|
p row
end
Outras dicas
O CSV, como mencionado, é uma boa escolha. Outra é Yaml ("Yaml não é uma linguagem de marcação"), que pode lidar com dados mais arbitrários do que o CSV. Aqui estão alguns dados:
require 'pp'
require 'yaml'
h = {
:first_name => 'Fred',
:last_name => 'Flinstone',
:children => ['Bam Bam', 'Pebbles'],
:exclamation => 'Yabba Dabba Doo',
}
Vamos escrever os dados em um arquivo no formato YAML:
File.open('/tmp/foo.yaml', 'w') do |file|
file.write h.to_yaml
end
Agora vamos ver como é o YAML:
$ cat /tmp/foo.yaml
---
:exclamation: Yabba Dabba Doo
:first_name: Fred
:last_name: Flinstone
:children:
- Bam Bam
- Pebbles
E, finalmente, vamos reconstituir os dados do arquivo YAML:
pp YAML.load_file('/tmp/foo.yaml')
# => {:exclamation=>"Yabba Dabba Doo",
# => :first_name=>"Fred",
# => :last_name=>"Flinstone",
# => :children=>["Bam Bam", "Pebbles"]}