アプリケーションにバッチファイル名変更機能を含めたい。ユーザーは宛先ファイル名のパターンを入力でき、(パターン内のいくつかのワイルドカードを置き換えた後)Windowsで有効なファイル名になるかどうかを確認する必要があります。私は[a-zA-Z0-9_]+
のような正規表現を使用しようとしましたが、さまざまな言語(ウムラウトなど)から多くの国固有の文字が含まれていません。そのようなチェックを行う最良の方法は何ですか?
Path.GetInvalidPathChars
および GetInvalidFileNameChars
から無効な文字のリストを取得できます。
UPD:正規表現でこれらを使用する方法については Steve Cooperの提案 を参照してください。
UPD2:MSDNの備考セクションによると、「このメソッドから返される配列は、ファイル名とディレクトリ名。」 sixlettervaliablesによって提供される答え はさらに詳しく説明します。
MSDNの「ファイルまたはディレクトリの命名」 から、Windowsでの有効なファイル名の一般的な規則を以下に示します。
現在のコードページ(127を超えるUnicode/ANSI)では、次の文字を除く任意の文字を使用できます。
<
>
:
"
/
\
|
?
*
チェックするいくつかのオプション事項:
\?\
プレフィックスを使用しない)\?\
を使用する場合、32,000文字を超えるUnicodeファイルパス(ファイル名を含む)(プレフィックスがディレクトリコンポーネントを展開し、32,000の制限をオーバーフローさせる可能性があることに注意してください)。Net Frameworks for 3.5の場合、これは動作するはずです:
正規表現の一致により、何らかの方法が得られます。以下は、System.IO.Path.InvalidPathChars
定数を使用したスニペットです。
bool IsValidFilename(string testName)
{
Regex containsABadCharacter = new Regex("["
+ Regex.Escape(System.IO.Path.InvalidPathChars) + "]");
if (containsABadCharacter.IsMatch(testName)) { return false; };
// other checks for UNC, drive-path format, etc
return true;
}
。Net Frameworks for 3.0の場合、これは機能するはずです:
http://msdn.Microsoft.com/en-us/library/system.io.path.getinvalidpathchars(v = vs.90).aspx
正規表現の一致により、何らかの方法が得られます。以下は、System.IO.Path.GetInvalidPathChars()
定数を使用したスニペットです。
bool IsValidFilename(string testName)
{
Regex containsABadCharacter = new Regex("["
+ Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]");
if (containsABadCharacter.IsMatch(testName)) { return false; };
// other checks for UNC, drive-path format, etc
return true;
}
それがわかったら、c:\my\drive
と\\server\share\dir\file.ext
などの異なる形式も確認する必要があります。
使用してみて、エラーをトラップしてください。許可されるセットは、ファイルシステム間、またはWindowsの異なるバージョン間で変更される場合があります。つまり、Windowsがその名前を気に入っているかどうかを知りたい場合は、名前を渡して教えてください。
このクラスは、ファイル名とパスを消去します。のように使用します
var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');
コードは次のとおりです。
/// <summary>
/// Cleans paths of invalid characters.
/// </summary>
public static class PathSanitizer
{
/// <summary>
/// The set of invalid filename characters, kept sorted for fast binary search
/// </summary>
private readonly static char[] invalidFilenameChars;
/// <summary>
/// The set of invalid path characters, kept sorted for fast binary search
/// </summary>
private readonly static char[] invalidPathChars;
static PathSanitizer()
{
// set up the two arrays -- sorted once for speed.
invalidFilenameChars = System.IO.Path.GetInvalidFileNameChars();
invalidPathChars = System.IO.Path.GetInvalidPathChars();
Array.Sort(invalidFilenameChars);
Array.Sort(invalidPathChars);
}
/// <summary>
/// Cleans a filename of invalid characters
/// </summary>
/// <param name="input">the string to clean</param>
/// <param name="errorChar">the character which replaces bad characters</param>
/// <returns></returns>
public static string SanitizeFilename(string input, char errorChar)
{
return Sanitize(input, invalidFilenameChars, errorChar);
}
/// <summary>
/// Cleans a path of invalid characters
/// </summary>
/// <param name="input">the string to clean</param>
/// <param name="errorChar">the character which replaces bad characters</param>
/// <returns></returns>
public static string SanitizePath(string input, char errorChar)
{
return Sanitize(input, invalidPathChars, errorChar);
}
/// <summary>
/// Cleans a string of invalid characters.
/// </summary>
/// <param name="input"></param>
/// <param name="invalidChars"></param>
/// <param name="errorChar"></param>
/// <returns></returns>
private static string Sanitize(string input, char[] invalidChars, char errorChar)
{
// null always sanitizes to null
if (input == null) { return null; }
StringBuilder result = new StringBuilder();
foreach (var characterToTest in input)
{
// we binary search for the character in the invalid set. This should be lightning fast.
if (Array.BinarySearch(invalidChars, characterToTest) >= 0)
{
// we found the character in the array of
result.Append(errorChar);
}
else
{
// the character was not found in invalid, so it is valid.
result.Append(characterToTest);
}
}
// we're done.
return result.ToString();
}
}
これは私が使用するものです:
public static bool IsValidFileName(this string expression, bool platformIndependent)
{
string sPattern = @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$";
if (platformIndependent)
{
sPattern = @"^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?))\\)*[^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?$";
}
return (Regex.IsMatch(expression, sPattern, RegexOptions.CultureInvariant));
}
最初のパターンは、Windowsプラットフォーム専用の無効/不正なファイル名と文字を含む正規表現を作成します。 2番目のものは同じことを行いますが、名前がすべてのプラットフォームで有効であることを保証します。
念頭に置いておくべき1つの重要なケースは、私が最初にそのことに気付いたときに驚いたことです。Windowsでは、ファイル名にスペース文字を使用できます。たとえば、以下はすべてWindowsで有効な明確なファイル名です(引用符を除く)。
"file.txt"
" file.txt"
" file.txt"
これからの1つのポイント:ファイル名文字列から先頭/末尾の空白を削除するコードを書くときは注意してください。
ユージンカッツの答えを簡単に:
bool IsFileNameCorrect(string fileName){
return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}
または
bool IsFileNameCorrect(string fileName){
return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
Microsoft Windows:Windowsカーネルでは、1〜31の範囲の文字(つまり、0x01-0x1F)と "*:<>?\ |の文字を使用できません。NTFSでは、各パスコンポーネント(ディレクトリまたはファイル名)最大約32767文字のパス、Windowsカーネルは最大259文字のパスのみをサポートし、さらにWindowsはMS-DOSデバイス名AUX、CLOCK $、COM1、COM2、COM3、COM4、COM5、COM6、 COM7、COM8、COM9、CON、LPT1、LPT2、LPT3、LPT4、LPT5、LPT6、LPT7、LPT8、LPT9、NUL、PRN、およびこれらの名前と拡張子(AUX.txtなど)長いUNCパス(例:\。\ C:\ nul.txtまたは\?\ D:\ aux\con)(実際、拡張機能が提供されている場合はCLOCK $を使用できます。)これらの制限はWindowsにのみ適用されます-たとえば、Linuxでは「*:<>?\| NTFSでも。
可能なすべての文字を明示的に含めるのではなく、正規表現を実行して不正な文字の存在を確認し、エラーを報告することができます。理想的には、アプリケーションはユーザーの希望どおりにファイルに名前を付け、エラーに遭遇した場合にのみファウルを鳴らします。
私はこれを使用して、例外をスローせずにファイル名の無効な文字を取り除きます:
private static readonly Regex InvalidFileRegex = new Regex(
string.Format("[{0}]", Regex.Escape(@"<>:""/\|?*")));
public static string SanitizeFileName(string fileName)
{
return InvalidFileRegex.Replace(fileName, string.Empty);
}
また、CON、PRN、AUX、NUL、COM#、および他のいくつかは、任意の拡張子を持つディレクトリ内の正当なファイル名になることはありません。
問題は、パス名が有効なWindowsパスかどうか、または有効かどうかを判断しようとしていることですコードが実行されているシステム上?私は後者がより重要だと思うので、個人的には、おそらくフルパスを分解し、_mkdirを使用してファイルが属するディレクトリを作成し、ファイルを作成しようとします。
これにより、パスに有効なウィンドウ文字のみが含まれているかどうかだけでなく、実際にこのプロセスで書き込むことができるパスを表しているかどうかがわかります。
他の答えを補足するために、考慮したい追加のEdgeケースをいくつか紹介します。
名前に「[」または「]」文字が含まれるファイルにワークブックを保存すると、Excelで問題が発生する可能性があります。詳細については、 http://support.Microsoft.com/kb/215205 を参照してください。
Sharepointには、追加の一連の制限があります。詳細については、 http://support.Microsoft.com/kb/905231 を参照してください。
MSDN から、許可されていない文字のリストを以下に示します。
Unicode文字や拡張文字セット(128〜255)の文字を含む、現在のコードページのほとんどすべての文字を名前に使用します。ただし、次の場合を除きます。
- 次の予約文字は使用できません:<>: "/\|?*
- 整数表現が0〜31の範囲にある文字は使用できません。
- ターゲットファイルシステムで許可されていないその他の文字。
この状況では、正規表現は過剰です。 String.IndexOfAny()
メソッドは、Path.GetInvalidPathChars()
およびPath.GetInvalidFileNameChars()
と組み合わせて使用できます。
また、両方のPath.GetInvalidXXX()
メソッドが内部配列を複製し、複製を返すことに注意してください。したがって、これを何度も(数千回、数千回)実行する場合は、無効なchars配列のコピーをキャッシュして再利用できます。
また、宛先ファイルシステムも重要です。
NTFSでは、特定のディレクトリに一部のファイルを作成できません。例えば。ルートでの$ Boot
これはすでに回答済みの質問ですが、「その他のオプション」のためだけに、理想的ではない質問を次に示します。
(例外をフロー制御として使用することは一般に「悪いこと」であるため、理想的ではありません)
public static bool IsLegalFilename(string name)
{
try
{
var fileInfo = new FileInfo(name);
return true;
}
catch
{
return false;
}
}
ファイル名/パスを保持する文字列に無効な文字が含まれているかどうかだけを確認しようとしている場合、私が見つけた最速の方法は、Split()
を使用してファイル名を部分の配列に分割することです無効な文字。結果が1の配列のみの場合、無効な文字はありません。 :-)
var nameToTest = "Best file name \"ever\".txt";
bool isInvalidName = nameToTest.Split(System.IO.Path.GetInvalidFileNameChars()).Length > 1;
var pathToTest = "C:\\My Folder <secrets>\\";
bool isInvalidPath = pathToTest.Split(System.IO.Path.GetInvalidPathChars()).Length > 1;
LinqPadで1,000,000回、ファイル名とパス名に対して上記の方法や上記の方法を実行してみました。
Split()
の使用は約850msです。
Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")
の使用は約6秒です。
より複雑な正規表現は、Path
クラスのさまざまなメソッドを使用してファイル名を取得し、内部検証でジョブを実行するなど、他のオプションのいくつかと同様に、かなり悪くなります(ほとんどの場合、例外処理)。
100万のファイル名を検証する必要があることはあまりないので、これらのメソッドのほとんどは1回の反復で問題ありません。ただし、無効な文字のみを探している場合は、依然として非常に効率的かつ効果的です。
これらの回答の多くは、ファイル名が長すぎてWindows 10以前の環境で実行されている場合は機能しません。同様に、ピリオドで何をしたいかを考えてください-先行または後続を許可することは技術的には有効ですが、ファイルをそれぞれ見たり削除したりするのが難しい場合は問題を引き起こす可能性があります。
これは、有効なファイル名を確認するために作成した検証属性です。
public class ValidFileNameAttribute : ValidationAttribute
{
public ValidFileNameAttribute()
{
RequireExtension = true;
ErrorMessage = "{0} is an Invalid Filename";
MaxLength = 255; //superseeded in modern windows environments
}
public override bool IsValid(object value)
{
//http://stackoverflow.com/questions/422090/in-c-sharp-check-that-filename-is-possibly-valid-not-that-it-exists
var fileName = (string)value;
if (string.IsNullOrEmpty(fileName)) { return true; }
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ||
(!AllowHidden && fileName[0] == '.') ||
fileName[fileName.Length - 1]== '.' ||
fileName.Length > MaxLength)
{
return false;
}
string extension = Path.GetExtension(fileName);
return (!RequireExtension || extension != string.Empty)
&& (ExtensionList==null || ExtensionList.Contains(extension));
}
private const string _sepChar = ",";
private IEnumerable<string> ExtensionList { get; set; }
public bool AllowHidden { get; set; }
public bool RequireExtension { get; set; }
public int MaxLength { get; set; }
public string AllowedExtensions {
get { return string.Join(_sepChar, ExtensionList); }
set {
if (string.IsNullOrEmpty(value))
{ ExtensionList = null; }
else {
ExtensionList = value.Split(new char[] { _sepChar[0] })
.Select(s => s[0] == '.' ? s : ('.' + s))
.ToList();
}
} }
public override bool RequiresValidationContext => false;
}
そしてテスト
[TestMethod]
public void TestFilenameAttribute()
{
var rxa = new ValidFileNameAttribute();
Assert.IsFalse(rxa.IsValid("pptx."));
Assert.IsFalse(rxa.IsValid("pp.tx."));
Assert.IsFalse(rxa.IsValid("."));
Assert.IsFalse(rxa.IsValid(".pp.tx"));
Assert.IsFalse(rxa.IsValid(".pptx"));
Assert.IsFalse(rxa.IsValid("pptx"));
Assert.IsFalse(rxa.IsValid("a/abc.pptx"));
Assert.IsFalse(rxa.IsValid("a\\abc.pptx"));
Assert.IsFalse(rxa.IsValid("c:abc.pptx"));
Assert.IsFalse(rxa.IsValid("c<abc.pptx"));
Assert.IsTrue(rxa.IsValid("abc.pptx"));
rxa = new ValidFileNameAttribute { AllowedExtensions = ".pptx" };
Assert.IsFalse(rxa.IsValid("abc.docx"));
Assert.IsTrue(rxa.IsValid("abc.pptx"));
}
私の試み:
using System.IO;
static class PathUtils
{
public static string IsValidFullPath([NotNull] string fullPath)
{
if (string.IsNullOrWhiteSpace(fullPath))
return "Path is null, empty or white space.";
bool pathContainsInvalidChars = fullPath.IndexOfAny(Path.GetInvalidPathChars()) != -1;
if (pathContainsInvalidChars)
return "Path contains invalid characters.";
string fileName = Path.GetFileName(fullPath);
if (fileName == "")
return "Path must contain a file name.";
bool fileNameContainsInvalidChars = fileName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1;
if (fileNameContainsInvalidChars)
return "File name contains invalid characters.";
if (!Path.IsPathRooted(fullPath))
return "The path must be absolute.";
return "";
}
}
Path.GetInvalidPathChars
は、ファイル名とディレクトリ名で無効な文字の完全なセットを返さないため、これは完全ではありません。もちろん、さらに多くの微妙な点があります。
したがって、私はこのメソッドを補完として使用します:
public static bool TestIfFileCanBeCreated([NotNull] string fullPath)
{
if (string.IsNullOrWhiteSpace(fullPath))
throw new ArgumentException("Value cannot be null or whitespace.", "fullPath");
string directoryName = Path.GetDirectoryName(fullPath);
if (directoryName != null) Directory.CreateDirectory(directoryName);
try
{
using (new FileStream(fullPath, FileMode.CreateNew)) { }
File.Delete(fullPath);
return true;
}
catch (IOException)
{
return false;
}
}
ファイルを作成しようとし、例外がある場合はfalseを返します。もちろん、ファイルを作成する必要がありますが、それが最も安全な方法だと思います。また、作成されたディレクトリは削除しないことに注意してください。
また、最初の方法を使用して基本的な検証を行い、パスが使用されるときに例外を慎重に処理することもできます。
私の意見では、この質問に対する唯一の適切な答えは、パスを使用して、OSとファイルシステムにそれを検証させることです。それ以外の場合は、OSとファイルシステムがすでに使用しているすべての検証ルールを再実装するだけで(おそらく不十分)、それらのルールが将来変更される場合は、コードを変更して一致させる必要があります。
このチェック
static bool IsValidFileName(string name)
{
return
!string.IsNullOrWhiteSpace(name) &&
name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
!Path.GetFullPath(name).StartsWith(@"\\.\");
}
無効な文字(<>:"/\|?*
およびASCII 0-31)、および予約されたDOSデバイス(CON
、NUL
、COMx
)を含む名前を除外します。 Path.GetFullPath
と一致する先頭のスペースとすべてのドット名を許可します。 (先頭のスペースを含むファイルを作成すると、システム上で成功します)。
.NET Framework 4.7.1を使用し、Windows 7でテスト済み。
私は誰かからこのアイデアを得た。 -誰がわからない。 OSに負担をかけさせます。
public bool IsPathFileNameGood(string fname)
{
bool rc = Constants.Fail;
try
{
this._stream = new StreamWriter(fname, true);
rc = Constants.Pass;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Problem opening file");
rc = Constants.Fail;
}
return rc;
}
文字列内の不正な文字を検証するための1つのライナー:
public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");
Path.GetFullPath()を使用することをお勧めします
string tagetFileFullNameToBeChecked;
try
{
Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
// invalid chars found
}