指定された文字列が Windows で正当/有効なファイル名であるかどうかを確認するにはどうすればよいですか?
-
09-06-2019 - |
質問
アプリケーションにバッチ ファイルの名前変更機能を組み込みたいと考えています。ユーザーは宛先ファイル名のパターンを入力できます。(パターン内のいくつかのワイルドカードを置き換えた後) それが Windows で正当なファイル名になるかどうかを確認する必要があります。次のような正規表現を使用しようとしました [a-zA-Z0-9_]+
ただし、さまざまな言語の各国固有の文字はあまり含まれていません (例:ウムラウトなど)。このようなチェックを行う最善の方法は何でしょうか?
解決
無効な文字のリストは次から取得できます。 Path.GetInvalidPathChars
そして GetInvalidFileNameChars
.
更新: 見る スティーブ・クーパーの提案 これらを正規表現で使用する方法について説明します。
UPD2: MSDN の備考セクションによると、「このメソッドから返される配列には、ファイル名とディレクトリ名で無効な文字の完全なセットが含まれることが保証されていない」ことに注意してください。 sixlettevalables が提供する回答 さらに詳しく説明します。
他のヒント
から MSDNの「ファイルまたはディレクトリの命名」 Windows での正当なファイル名に関する一般的な規則は次のとおりです。
現在のコード ページ (Unicode/ANSI 127 以降) では、次を除く任意の文字を使用できます。
<
>
:
"
/
\
|
?
*
- 整数表現が 0 ~ 31 (ASCII スペース未満) の文字
- ターゲット ファイル システムで許可されていないその他の文字 (末尾のピリオドやスペースなど)
- 任意の DOS 名:CON、PRN、AUX、NUL、COM0、COM1、COM2、COM3、COM4、COM5、COM6、COM7、COM8、COM9、LPT0、LPT1、LPT2、LPT3、LPT4、LPT5、LPT6、LPT7、LPT8、LPT9 (および避けてください) AUX.txtなど)
- ファイル名はすべてピリオドです
オプションでチェックすべき項目:
- ファイル パス (ファイル名を含む) は 260 文字を超えることはできません (
\?\
プレフィックス) - 32,000 文字を超える Unicode ファイル パス (ファイル名を含む) を使用する場合
\?\
(プレフィックスによりディレクトリ コンポーネントが拡張され、32,000 の制限をオーバーフローする可能性があることに注意してください)
のために 3.5 より前の .Net Framework これは機能するはずです:
正規表現の一致により、ある程度の方法が得られるはずです。以下は、 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 Framework 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 がその名前を気に入っているかどうかを知りたければ、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))
}
マイクロソフトウィンドウズ:Windows カーネルは、1 ~ 31 の範囲の文字 (つまり、0x01 ~ 0x1F) および文字「 * :」の使用を禁止しています。< > ?\ |。NTFS では、各パス コンポーネント (ディレクトリまたはファイル名) の長さは 255 文字、パスの長さは最大約 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: ul.txt または \?\D:\aux\con)。(実際、拡張子が提供されている場合は、CLOCK$ を使用できます。) これらの制限は Windows にのみ適用されます。たとえば、Linux では「 * : 」の使用が許可されています。< > ?| NTFでも。
使用可能なすべての文字を明示的に含めるのではなく、正規表現を実行して不正な文字の存在をチェックし、エラーを報告することもできます。理想的には、アプリケーションはユーザーの希望通りに正確にファイル名を付け、エラーが発生した場合にのみ問題を解決する必要があります。
これを使用して、例外をスローせずにファイル名内の無効な文字を削除します。
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を使ってファイルが属するディレクトリを作成してからファイルを作成してみると良いと思います。
こうすることで、パスに有効な Windows 文字のみが含まれているかどうかだけでなく、実際にこのプロセスで書き込めるパスを表しているかどうかもわかります。
他の回答を補完するために、考慮すべき追加のエッジケースをいくつか紹介します。
名前に「[」または「]」文字が含まれるファイルにブックを保存すると、Excel で問題が発生する可能性があります。見る http://support.microsoft.com/kb/215205 詳細については。
Sharepoint にはさらに一連の制限があります。見る http://support.microsoft.com/kb/905231 詳細については。
から MSDN, 、許可されない文字のリストは次のとおりです。
以下を除く、Unicode 文字や拡張文字セット (128 ~ 255) の文字を含む、現在のコード ページ内のほとんどすべての文字を名前に使用します。
- 次の予約文字は使用できません。< > :「/\|?*
- 整数表現が 0 ~ 31 の範囲にある文字は許可されません。
- ターゲット ファイル システムで許可されていないその他の文字。
宛先ファイルシステムも重要です。
NTFSでは、特定のディレクトリに作成できないファイルがあります。例えば。ルートでの $Boot
これはすでに回答されている質問ですが、「その他のオプション」のために、理想的ではない質問を次に示します。
(一般に、フロー制御として例外を使用することは「悪いこと」であるため、理想的ではありません)
public static bool IsLegalFilename(string name)
{
try
{
var fileInfo = new FileInfo(name);
return true;
}
catch
{
return false;
}
}
この状況では正規表現は過剰です。使用できます String.IndexOfAny()
と組み合わせた方法 Path.GetInvalidPathChars()
そして Path.GetInvalidFileNameChars()
.
また、両方とも注意してください Path.GetInvalidXXX()
メソッドは内部配列のクローンを作成し、そのクローンを返します。したがって、これを何度も (何千回も) 実行する場合は、無効な chars 配列のコピーをキャッシュして再利用できます。
ファイル名が長すぎて 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"));
}
ファイル名/パスを保持する文字列に無効な文字が含まれているかどうかを確認したいだけの場合、私が見つけた最も速い方法は次の方法です。 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()
わずか約 850 ミリ秒です。
使用する Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")
6秒くらいです。
より複雑な正規表現は、他のオプション (たとえば、 Path
クラスを使用してファイル名を取得し、内部検証にジョブを実行させます (おそらく例外処理のオーバーヘッドが原因です)。
確かに、100 万個のファイル名を検証する必要があることはそれほど頻繁ではないため、これらのメソッドのほとんどは 1 回の反復で問題ありません。ただし、無効な文字だけを探している場合には、それでもかなり効率的で効果的です。
私の試み:
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 を返します。もちろん、ファイルを作成する必要がありますが、それが最も安全な方法だと思います。作成されたディレクトリは削除しないことにも注意してください。
最初の方法を使用して基本的な検証を行った後、パスが使用されるときに例外を慎重に処理することもできます。
Path.GetFullPath() を使用することをお勧めします。
string tagetFileFullNameToBeChecked;
try
{
Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
// invalid chars found
}
このアイデアはある人からもらいました。- 誰だか分からない。面倒な作業は 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;
}
このチェック
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 でテストしました。
文字列内の不正な文字を検証するための 1 つのライナー:
public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");
Windows のファイル名にはかなり制限がないため、実際には制限がない可能性もあります。 それ 大きな問題です。Windows で禁止されている文字は次のとおりです。
\ / : * ? " < > |
これらの文字が存在するかどうかを確認する式を簡単に作成できます。ただし、より良い解決策は、ユーザーが望むようにファイルに名前を付けてみて、ファイル名が定着しない場合に警告することです。