パスとファイル名から不正な文字を削除する方法は?
質問
単純な文字列から不正なパスおよびファイル文字を削除するための堅牢で簡単な方法が必要です。以下のコードを使用しましたが、何もしないようです。何が欠けていますか?
using System;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";
illegal = illegal.Trim(Path.GetInvalidFileNameChars());
illegal = illegal.Trim(Path.GetInvalidPathChars());
Console.WriteLine(illegal);
Console.ReadLine();
}
}
}
解決
代わりにこのようなものを試してください;
string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
foreach (char c in invalid)
{
illegal = illegal.Replace(c.ToString(), "");
}
しかし、私はコメントに同意する必要があります。違法なパスを正当ではあるがおそらく意図しないパスに変更しようとするのではなく、おそらく違法なパスのソースに対処しようとします。
編集:または、正規表現を使用した潜在的に「より良い」ソリューション。
string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");
それでも、質問が求められます。なぜ最初にこれを行うのか。
他のヒント
public string GetSafeFilename(string filename)
{
return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));
}
Linqを使用してファイル名をクリーンアップします。これを簡単に拡張して、有効なパスを確認することもできます。
private static string CleanFileName(string fileName)
{
return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}
更新
一部のコメントは、このメソッドが機能していないことを示しているため、DotNetFiddleスニペットへのリンクを含めて、メソッドを検証できるようにしました。
次のようにLinqを使用して、不正な文字を削除できます。
var invalidChars = Path.GetInvalidFileNameChars();
var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();
編集
これは、コメントに記載されている必要な編集でどのように見えるかです:
var invalidChars = Path.GetInvalidFileNameChars();
string invalidCharsRemoved = new string(stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray());
これらはすべて優れたソリューションですが、それらはすべてPath.GetInvalidFileNameChars
に依存しています。これは、あなたが思うほど信頼性に欠ける場合があります。 Path.GetInvalidPathChars
のMSDNドキュメントで次のコメントに注意してください。
このメソッドから返される配列は、ファイル名とディレクトリ名で無効な文字の完全なセットを含むことを保証しません。無効な文字の完全なセットは、ファイルシステムによって異なります。たとえば、Windowsベースのデスクトッププラットフォームでは、無効なパス文字にはASCII / Unicode文字1〜31、引用符(<!> quot;)、より小さい(<!> lt;)、より大きい(<! > gt;)、パイプ(|)、バックスペース(\ b)、null(\ 0)、タブ(\ t)。
<=> メソッドを使用した場合の方が優れています。まったく同じコメントが含まれています。
ファイル名の場合:
string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars()));
フルパスの場合:
string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars()));
これをセキュリティ機能として使用する場合は、すべてのパスを展開し、ユーザーが指定したパスが実際にユーザーがアクセスする必要があるディレクトリの子であることを確認するのがより堅牢なアプローチです。
初心者の場合、トリムは先頭から文字を削除するか、文字列の終わり。第二に、攻撃的な文字を本当に削除したいかどうかを評価するか、高速で失敗してファイル名が無効であることをユーザーに知らせる必要があります。私の選択は後者ですが、私の答えは少なくとも物事を正しい方法と間違った方法で行う方法を示す必要があります:
方法を示すStackOverflowの質問指定された文字列が有効なファイル名であるかどうかを確認します。この質問の正規表現を使用して、正規表現で置換された文字を削除できることに注意してください(本当にこれが必要な場合)。
これを実現するために正規表現を使用します。まず、動的に正規表現を作成します。
string regex = string.Format(
"[{0}]",
Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
その後、removeInvalidChars.Replaceを呼び出して、検索と置換を行います。これは明らかにパス文字もカバーするように拡張できます。
ユーザー入力から不正な文字を削除する最良の方法は、Regexクラスを使用して不正な文字を置換するか、コードビハインドでメソッドを作成するか、RegularExpressionコントロールを使用してクライアント側で検証することです。
public string RemoveSpecialCharacters(string str)
{
return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}
または
<asp:RegularExpressionValidator ID="regxFolderName"
runat="server"
ErrorMessage="Enter folder name with a-z A-Z0-9_"
ControlToValidate="txtFolderName"
Display="Dynamic"
ValidationExpression="^[a-zA-Z0-9_]*$"
ForeColor="Red">
私はジェフ・イェーツのアイデアを絶対に好みます。少し変更すれば完璧に機能します:
string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
改善点は、自動生成された正規表現をエスケープすることです。
.NET 3以降に役立つはずのコードスニペットです。
using System.IO;
using System.Text.RegularExpressions;
public static class PathValidation
{
private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);
private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);
private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);
private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);
public static bool ValidatePath(string path)
{
return pathValidator.IsMatch(path);
}
public static bool ValidateFileName(string fileName)
{
return fileNameValidator.IsMatch(fileName);
}
public static string CleanPath(string path)
{
return pathCleaner.Replace(path, "");
}
public static string CleanFileName(string fileName)
{
return fileNameCleaner.Replace(fileName, "");
}
}
上記のほとんどの解決策は、パスとファイル名の両方で不正な文字を組み合わせています(両方の呼び出しが現在同じ文字セットを返している場合でも)。最初にpath + filenameをpathとfilenameに分割し、適切なセットをそれらのいずれかに適用してから、2つを再度結合します。
wvd_vegt
無効な文字を単一の文字で削除または置換すると、衝突が発生する可能性があります:
<abc -> abc
>abc -> abc
これを回避する簡単な方法を次に示します。
public static string ReplaceInvalidFileNameChars(string s)
{
char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
foreach (char c in invalidFileNameChars)
s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
return s;
}
結果:
<abc -> [1]abc
>abc -> [2]abc
例外をスローします。
if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
{
throw new ArgumentException();
}
このモンスターを楽しみのために書いたので、往復できます:
public static class FileUtility
{
private const char PrefixChar = '%';
private static readonly int MaxLength;
private static readonly Dictionary<char,char[]> Illegals;
static FileUtility()
{
List<char> illegal = new List<char> { PrefixChar };
illegal.AddRange(Path.GetInvalidFileNameChars());
MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
}
public static string FilenameEncode(string s)
{
var builder = new StringBuilder();
char[] replacement;
using (var reader = new StringReader(s))
{
while (true)
{
int read = reader.Read();
if (read == -1)
break;
char c = (char)read;
if(Illegals.TryGetValue(c,out replacement))
{
builder.Append(PrefixChar);
builder.Append(replacement);
}
else
{
builder.Append(c);
}
}
}
return builder.ToString();
}
public static string FilenameDecode(string s)
{
var builder = new StringBuilder();
char[] buffer = new char[MaxLength];
using (var reader = new StringReader(s))
{
while (true)
{
int read = reader.Read();
if (read == -1)
break;
char c = (char)read;
if (c == PrefixChar)
{
reader.Read(buffer, 0, MaxLength);
var encoded =(char) ParseCharArray(buffer);
builder.Append(encoded);
}
else
{
builder.Append(c);
}
}
}
return builder.ToString();
}
public static int ParseCharArray(char[] buffer)
{
int result = 0;
foreach (char t in buffer)
{
int digit = t - '0';
if ((digit < 0) || (digit > 9))
{
throw new ArgumentException("Input string was not in the correct format");
}
result *= 10;
result += digit;
}
return result;
}
}
すべての不良文字をチェックするのではなく、正規表現を使用して、許可される文字を指定することを検証する方がはるかに簡単だと思います。 これらのリンクをご覧ください: http://www.c-sharpcorner.com/UploadFile/prasad_1/ RegExpPSD12062005021717AM / RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/ oreilly / windows / news / csharp_0101.html
また、<!> quot; regular expression editor <!> quot; sを検索すると、非常に役立ちます。 C#でコードを出力するものもあります。
これはO(n)のようで、文字列にあまり多くのメモリを費やしません:
private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());
public static string RemoveInvalidFileNameChars(string name)
{
if (!name.Any(c => invalidFileNameChars.Contains(c))) {
return name;
}
return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
}
ここで答えをスキャンすると、すべて**は無効なファイル名文字のchar配列を使用することを含むようです。
確かに、これはマイクロ最適化の可能性があります-しかし、有効なファイル名であるために多数の値を確認しようとしている人のために、無効な文字のハッシュセットを構築するとパフォーマンスが著しく向上することに注意してください。
過去、ハッシュセット(またはディクショナリ)がリストの繰り返し処理をどれほど速く行っているかに驚かされました(衝撃を受けました)。文字列の場合、それはとてつもなく低い数です(メモリから約5〜7アイテム)。他のほとんどの単純なデータ(オブジェクト参照、数値など)では、魔法のクロスオーバーは約20アイテムのようです。
Path.InvalidFileNameChars <!> quot; list <!> quot;には40個の無効な文字があります。本日検索を行ったところ、StackOverflowには非常に優れたベンチマークがあり、40個のアイテムの配列/リストの半分以上の時間がかかることを示しています:https://stackoverflow.com/a/10762995/949129
パスのサニタイズに使用するヘルパークラスを次に示します。なぜファンシー交換オプションが含まれていたのか今は忘れていますが、かわいいボーナスとしてそこにあります。
追加のボーナスメソッド<!> quot; IsValidLocalPath <!> quot;も:)
(**正規表現を使用しないもの)
public static class PathExtensions
{
private static HashSet<char> _invalidFilenameChars;
private static HashSet<char> InvalidFilenameChars
{
get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
}
/// <summary>Replaces characters in <c>text</c> that are not allowed in file names with the
/// specified replacement character.</summary>
/// <param name="text">Text to make into a valid filename. The same string is returned if
/// it is valid already.</param>
/// <param name="replacement">Replacement character, or NULL to remove bad characters.</param>
/// <param name="fancyReplacements">TRUE to replace quotes and slashes with the non-ASCII characters ” and ⁄.</param>
/// <returns>A string that can be used as a filename. If the output string would otherwise be empty, "_" is returned.</returns>
public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
{
StringBuilder sb = new StringBuilder(text.Length);
HashSet<char> invalids = InvalidFilenameChars;
bool changed = false;
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (invalids.Contains(c))
{
changed = true;
char repl = replacement ?? '\0';
if (fancyReplacements)
{
if (c == '"') repl = '”'; // U+201D right double quotation mark
else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
else if (c == '/') repl = '⁄'; // U+2044 fraction slash
}
if (repl != '\0')
sb.Append(repl);
}
else
sb.Append(c);
}
if (sb.Length == 0)
return "_";
return changed ? sb.ToString() : text;
}
/// <summary>
/// Returns TRUE if the specified path is a valid, local filesystem path.
/// </summary>
/// <param name="pathString"></param>
/// <returns></returns>
public static bool IsValidLocalPath(this string pathString)
{
// From solution at https://stackoverflow.com/a/11636052/949129
Uri pathUri;
Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
return isValidUri && pathUri != null && pathUri.IsLoopback;
}
}
public static class StringExtensions
{
public static string RemoveUnnecessary(this string source)
{
string result = string.Empty;
string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
result = reg.Replace(source, "");
return result;
}
}
メソッドを明確に使用できます。
ファイル名には、Path.GetInvalidPathChars()
、+
、および#
記号の文字、およびその他の特定の名前を含めることはできません。すべてのチェックを1つのクラスに結合しました:
public static class FileNameExtensions
{
private static readonly Lazy<string[]> InvalidFileNameChars =
new Lazy<string[]>(() => Path.GetInvalidPathChars()
.Union(Path.GetInvalidFileNameChars()
.Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());
private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
{
@"aux",
@"con",
@"clock$",
@"nul",
@"prn",
@"com1",
@"com2",
@"com3",
@"com4",
@"com5",
@"com6",
@"com7",
@"com8",
@"com9",
@"lpt1",
@"lpt2",
@"lpt3",
@"lpt4",
@"lpt5",
@"lpt6",
@"lpt7",
@"lpt8",
@"lpt9"
};
public static bool IsValidFileName(string fileName)
{
return !string.IsNullOrWhiteSpace(fileName)
&& fileName.All(o => !IsInvalidFileNameChar(o))
&& !IsProhibitedName(fileName);
}
public static bool IsProhibitedName(string fileName)
{
return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
}
private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
{
if (value == null)
{
return null;
}
return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
(sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
}
public static bool IsInvalidFileNameChar(char value)
{
return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
}
public static string GetValidFileName([NotNull] this string value)
{
return GetValidFileName(value, @"_");
}
public static string GetValidFileName([NotNull] this string value, string replacementValue)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException(@"value should be non empty", nameof(value));
}
if (IsProhibitedName(value))
{
return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value;
}
return ReplaceInvalidFileNameSymbols(value, replacementValue);
}
public static string GetFileNameError(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return CommonResources.SelectReportNameError;
}
if (IsProhibitedName(fileName))
{
return CommonResources.FileNameIsProhibited;
}
var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();
if(invalidChars.Length > 0)
{
return string.Format(CultureInfo.CurrentCulture,
invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
}
return string.Empty;
}
}
メソッドGetValidFileName
は、不正なデータをすべて_
に置き換えます。
Windowsファイルの名前付けで不正な文字から文字列をクリーンアップする1つのライナー:
public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
public static bool IsValidFilename(string testName)
{
return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
これは望みどおりになり、衝突を避けます
static string SanitiseFilename(string key)
{
var invalidChars = Path.GetInvalidFileNameChars();
var sb = new StringBuilder();
foreach (var c in key)
{
var invalidCharIndex = -1;
for (var i = 0; i < invalidChars.Length; i++)
{
if (c == invalidChars[i])
{
invalidCharIndex = i;
}
}
if (invalidCharIndex > -1)
{
sb.Append("_").Append(invalidCharIndex);
continue;
}
if (c == '_')
{
sb.Append("__");
continue;
}
sb.Append(c);
}
return sb.ToString();
}
質問はすでに完全には答えられていないと思います... 答えは、クリーンなファイル名またはパスのみを記述しています...両方ではありません。ここに私の解決策があります:
private static string CleanPath(string path)
{
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
List<string> split = path.Split('\\').ToList();
string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
returnValue = returnValue.TrimEnd('\\');
return returnValue;
}
いくつかの提案を組み合わせた拡張メソッドを作成しました:
- ハッシュセットに不正な文字を保持する
- ascii 127より下の文字のフィルタリング。Path.GetInvalidFileNameCharsには、0〜255のasciiコードで可能な無効な文字がすべて含まれているわけではありません。こちらおよび MSDN
- 置換文字を定義する可能性
出典:
public static class FileNameCorrector
{
private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());
public static string ToValidFileName(this string name, char replacement = '\0')
{
var builder = new StringBuilder();
foreach (var cur in name)
{
if (cur > 31 && cur < 128 && !invalid.Contains(cur))
{
builder.Append(cur);
}
else if (replacement != '\0')
{
builder.Append(replacement);
}
}
return builder.ToString();
}
}
または、あなたにできること
[YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();