web-dev-qa-db-ja.com

Ruby文字列をファイルシステムに対して安全にする方法は?

ファイル名としてユーザーエントリがあります。もちろん、これは良い考えではないので、[a-z][A-Z][0-9]_および-を除くすべてを削除したいと思います。

例えば:

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

なるはず

my_document_is_____very_interesting___thisIs_Nice445_doc.pdf

そして理想的には

my_document_is_very_interesting_thisIs_Nice445_doc.pdf

これを行うための素敵でエレガントな方法はありますか?

44
marcgg

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
28
miku

古いソリューションとは異なるソリューションを提案したいと思います。古いものは非推奨returningを使用していることに注意してください。ちなみに、それはとにかくRailsに固有であり、質問で(タグとしてのみ)Rails)を明示的に言及していませんでした。また、既存のソリューションは失敗します.doc.pdf_doc.pdfにエンコードします(もちろん、アンダースコアは1つに縮小されません)。

これが私の解決策です:

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

変換に関する詳細をすべて指定していません。したがって、私は次のことを前提としています。

  • ファイル名の拡張子は最大で1つにする必要があります。つまり、ファイル名にはピリオドを最大で1つ含める必要があります。
  • 後続期間は拡張の開始をマークしません
  • 先頭のピリオドは拡張の開始を示しません
  • AZaz09および-を超える文字のシーケンス単一の_に折りたたむ必要があります(つまり、アンダースコア自体は許可されていない文字と見なされ、文字列'$%__°#''_'からではなく'___'になります'$%''__''°#'

この複雑な部分は、ファイル名をメイン部分と拡張子に分割するところです。正規表現を使用して、最後のピリオドを検索します。最後のピリオドの後にピリオド以外の何かが続きます。これにより、文字列内で同じ基準に一致する後続のピリオドがなくなります。ただし、文字列の最初の文字ではないことを確認するために、文字の前に置く必要があります。

関数をテストした結果:

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

これはあなたが要求したものだと思います。これが素敵で十分エレガントであることを願っています。

62

Railsを使用する場合は、String#parameterizeも使用できます。これは特にそのためのものではありませんが、満足のいく結果が得られます。

"my§document$is°°   very&interesting___thisIs%Nice445.doc.pdf".parameterize
17
albandiguer

Railsでは、_ ActiveStorage :: Filename からsanitizeを使用できる場合もあります:

ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"
5
morgler

Railsの場合、ファイル拡張子を保持したいが、残りの文字にparameterizeを使用したい:

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

実装の詳細とアイデアはソースを参照してください: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
1
Blair Anderson

特に奇妙なUnicode文字をASCIIに置き換えることに興味がある場合は、役立つライブラリがあります: nidecode

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

あなたの目標がすべてのオペレーティングシステムで使用するのに「安全」なファイル名を生成することだけである(そしてすべての非ASCII文字を削除しない)場合は、 zar gemをお勧めします。元の質問で指定されたすべてのことを行うわけではありませんが、生成されたファイル名は安全に使用できます(ファイル名に安全なUnicode文字はそのままにしておきます)。

Zaru.sanitize! "  what\ēver//wëird:user:înput:"
# => "whatēverwëirduserînput"
Zaru.sanitize! "my§docu*ment$is°°   very&interes:ting___thisIs%Nice445.doc.pdf" 
# => "my§document$is°° very&interesting___thisIs%Nice445.doc.pdf"
0
David