2009-12-21 9 views
37

Tengo entradas de usuario como nombres de archivo. Por supuesto, esto no es una buena idea, así que quiero dejar todo excepto [a-z], [A-Z], [0-9], _ y -.¿Cómo hacer que una cadena de Ruby sea segura para un sistema de archivos?

Por ejemplo:

my§document$is°° very&interesting___thisIs%nice445.doc.pdf 

debe convertirse en

my_document_is_____very_interesting___thisIs_nice445_doc.pdf 

y entonces lo ideal

my_document_is_very_interesting_thisIs_nice445_doc.pdf 

¿Existe una manera agradable y elegante para hacer esto?

+1

Es una buena pregunta. Desearía que tuviera una respuesta stdlib –

Respuesta

24

De http://devblog.muziboo.com/2008/06/17/attachment-fu-sanitize-filename-regex-and-unicode-gotcha/:

def sanitize_filename(filename) 
    returning filename.strip do |name| 
    # NOTE: File.basename doesn't work right with Windows paths on Unix 
    # get only the filename, not the whole path 
    name.gsub!(/^.*(\\|\/)/, '') 

    # Strip out the non-ascii character 
    name.gsub!(/[^0-9A-Za-z.\-]/, '_') 
    end 
end 
+0

¡Gracias por el enlace! Por cierto, en el artículo que vinculó, el cartel dice que esta función tiene un problema. – marcgg

+1

thx, corregido ... – miku

+3

el 'name.gsub! (/ [^ 0-9A-Za-z. \ -] /, '_')' es la única parte que he usado después de 5 años: D – Aleks

53

Me gustaría sugerir una solución que se diferencia de la anterior. Tenga en cuenta que el anterior usa obsoletoreturning. Por cierto, es de todos modos específico para Rails, y no mencionó explícitamente Rails en su pregunta (solo como una etiqueta). Además, la solución existente no codifica .doc.pdf en _doc.pdf, como solicitó. Y, por supuesto, no colapsa los guiones bajos en uno.

aquí está mi solución:

def sanitize_filename(filename) 
    # Split the name when finding a period which is preceded by some 
    # character, and is followed by some character other than a period, 
    # if there is no following period that is followed by something 
    # other than a period (yeah, confusing, I know) 
    fn = filename.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m 

    # We now have one or two parts (depending on whether we could find 
    # a suitable period). For each of these parts, replace any unwanted 
    # sequence of characters with an underscore 
    fn.map! { |s| s.gsub /[^a-z0-9\-]+/i, '_' } 

    # Finally, join the parts with a period and return the result 
    return fn.join '.' 
end 

No ha especificado todos los detalles acerca de la conversión. Por lo tanto, estoy haciendo los siguientes supuestos:

  • No debe haber como máximo una extensión de archivo, lo que significa que debe haber como máximo un periodo en el nombre del archivo
  • Arrastrando períodos no marque el inicio de una extensión
  • períodos que anteceden no marcan el inicio de una extensión
  • Cualquier secuencia de caracteres más allá A - Z, a - z, 0 - 9 y - debería colapsarse en un solo _ (es decir subrayada se considera en sí mismo un carácter no permitido, y la cadena '$%__°#' se convertiría en '_' - en lugar de '___' de las partes '$%', '__' y '°#')

Lo complicado de esto es cuando me separé el nombre del archivo en la parte principal y extensión Con la ayuda de una expresión regular, estoy buscando el último período, que es seguido por algo más que un punto, de modo que no haya períodos siguientes que coincidan con los mismos criterios en la cadena. Sin embargo, debe ir precedido de algún carácter para asegurarse de que no sea el primer personaje de la cadena.

Mis resultados de las pruebas de la función:

1.9.3p125 :006 > sanitize_filename 'my§document$is°° very&interesting___thisIs%nice445.doc.pdf' 
=> "my_document_is_very_interesting_thisIs_nice445_doc.pdf" 

que creo que es lo que ha solicitado. Espero que esto sea lo suficientemente lindo y elegante.

+0

¡Gracias! esto ayudó. :) – Surya

+0

Obteniendo una secuencia "indefinida (? ...) ..." cuando intento usar el código. ¿Alguna limitación con la versión de ruby? –

+0

@JP. Perdón por la respuesta extremadamente tardía, y probablemente ya te hayas dado cuenta. No lo he probado, pero creo que las miradas detrás (que es lo que indica el signo de interrogación) aparecieron en Ruby 1.9. Entonces sí, hay limitaciones. Ver por ejemplo http://stackoverflow.com/q/7605615/1117365 –

15

Si utiliza Rails también puede usar String # parameterize. Esto no es especialmente adecuado para eso, pero obtendrá un resultado satisfactorio.

"my§document$is°° very&interesting___thisIs%nice445.doc.pdf".parameterize 
+1

This isn' t técnicamente preciso porque también eliminará el carácter decimal, que es algo esencial para preservar extensiones. Afortunadamente, el código detrás de parametrizar es [relativamente simple] (http://apidock.com/rails/ActiveSupport/Inflector/parameterize) y se puede implementar con solo unas pocas llamadas 'gsub'. –

0

para los carriles me encontré con ganas de mantener ningún tipo de extensiones de archivo, pero utilizando parameterize para el resto de los personajes:

filename = "my§doc$is°° very&itng___thsIs%nie445.doc.pdf" 
cleaned = filename.split(".").map(&:parameterize).join(".") 

Los detalles de implementación y las ideas ver fuente:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/transliterate.rb

def parameterize(string, separator: "-", preserve_case: false) 
    # Turn unwanted chars into the separator. 
    parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) 
    #... some more stuff 
end 
0

Hay una biblioteca que puede ser útil, especialmente si está interesado en reemplazar las extrañas Un caracteres icode con ASCII: unidecode.

irb(main):001:0> require 'unidecoder' 
=> true 
irb(main):004:0> "Grzegżółka".to_ascii 
=> "Grzegzolka" 
Cuestiones relacionadas