2009-12-21 2 views
37

Ho voci utente come nomi di file. Naturalmente questa non è una buona idea, quindi voglio lasciare tutto eccetto [a-z], [A-Z], [0-9], _ e -.Come rendere sicura una stringa di Ruby per un filesystem?

Per esempio:

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

dovrebbe diventare

my_document_is_____very_interesting___thisIs_nice445_doc.pdf 

e quindi idealmente

my_document_is_very_interesting_thisIs_nice445_doc.pdf 

C'è un modo piacevole ed elegante per fare questo?

+1

È una bella domanda. Vorrei avere una risposta stdlib –

risposta

24

Da 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

Grazie per il link! A proposito, nell'articolo che hai linkato, il poster dice che questa funzione ha un problema. – marcgg

+1

thx, corretto .. – miku

+3

il 'name.gsub! (/ [^ 0-9A-Za-z. \ -] /, '_')' è l'unica parte che ho usato dopo 5 anni: D – Aleks

53

vorrei suggerire una soluzione che si differenzia da quello vecchio. Si noti che quello vecchio utilizza il deprecatoreturning. A proposito, è comunque lo specifico per Rails, e non hai menzionato esplicitamente Rails nella tua domanda (solo come tag). Inoltre, la soluzione esistente non riesce a codificare .doc.pdf in _doc.pdf, come richiesto. E, naturalmente, non collassa i underscore in uno solo.

Ecco la mia soluzione:

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 

Non hai specificato tutti i dettagli circa la conversione. Così, sto facendo le seguenti ipotesi:

  • Ci dovrebbe essere al massimo un'estensione del file, il che significa che ci dovrebbe essere al massimo un periodo nel nome del file
  • Trailing periodi non segnare l'inizio di un estensione
  • periodi leading non segnano l'inizio di un'estensione
  • Qualsiasi sequenza di caratteri oltre A - Z, a - z, 0 - 9 e - dovrebbero essere ridotte in una singola _ (ad es. sottolineatura stesso considerato come un carattere non consentito, e la stringa '$%__°#' diventerebbe '_' - anziché '___' dalle parti '$%', '__' e '°#')

La parte complicata di questo è dove ho diviso il nome del file nella parte principale ed estensione. Con l'aiuto di un'espressione regolare, sto cercando l'ultimo periodo, che è seguito da qualcosa di diverso da un punto, in modo che non ci siano periodi successivi che corrispondono agli stessi criteri nella stringa. Deve, tuttavia, essere preceduto da qualche carattere per assicurarsi che non sia il primo carattere nella stringa.

I miei risultati da testare la funzione:

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

che credo sia quello che avete richiesto. Spero che questo sia abbastanza carino ed elegante.

+0

Grazie! questo ha aiutato :) – Surya

+0

Ottenere una "sequenza non definita (? ...) ..." quando tento di utilizzare il codice. Qualche limitazione con la versione rubino? –

+0

@ JP. Scusate per la risposta estremamente tardiva, e probabilmente lo avrete già capito da soli. Non l'ho provato, ma credo che i look-behind (che è quello che indica il punto interrogativo) siano apparsi in Ruby 1.9. Quindi sì, ci sono dei limiti. Vedi per esempio http://stackoverflow.com/q/7605615/1117365 –

15

Se si utilizzano Rails, è possibile utilizzare anche Parametro String #. Questo non è particolarmente adatto a questo, ma otterrai un risultato soddisfacente.

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

Questo isn' t tecnicamente accurato perché rimuoverà anche il carattere decimale, che è in qualche modo essenziale nel preservare le estensioni. Fortunatamente, il codice dietro la parametrizzazione è [relativamente semplice] (http://apidock.com/rails/ActiveSupport/Inflector/parameterize) e può essere implementato con poche chiamate 'gsub'. –

0

Per Rails ho trovato me stesso vogliono mantenere tutte le estensioni dei file, ma utilizzando parameterize per il resto dei personaggi:

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

dettagli di implementazione e idee vedere il fonte: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

C'è una libreria che può essere utile, specialmente se sei interessato a sostituire strani Un caratteri icode con ASCII: unidecode.

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