Voglio includere una funzionalità di ridenominazione di file batch nella mia applicazione. Un utente può digitare un modello di nome del file di destinazione e (dopo aver sostituito alcuni caratteri jolly nel modello) ho bisogno di controllare se sta per essere un nome file legale sotto Windows. Ho provato a utilizzare espressioni regolari come [a-zA-Z0-9_]+
ma non include molti caratteri specifici nazionali di varie lingue (ad esempio umlaut e così via). Qual è il modo migliore per fare un simile controllo?
È possibile ottenere un elenco di caratteri non validi da Path.GetInvalidPathChars
e GetInvalidFileNameChars
.
UPD: Vedi Suggerimento di Steve Cooper su come usarli in un'espressione regolare.
UPD2: Si noti che in base alla sezione Note in MSDN "Non è garantito che la matrice restituita da questo metodo contenga il set completo di caratteri non validi nei nomi di file e directory." La risposta fornita da sixlettervaliables entra in maggiori dettagli.
Da MSDN "Denominazione di un file o di una directory" ecco le convenzioni generali su quale sia il nome di un file legale sotto Windows:
È possibile utilizzare qualsiasi carattere nella codepage corrente (Unicode/ANSI sopra 127), eccetto:
<
>
:
"
/
\
|
?
*
Alcune cose facoltative da verificare:
\?\
)\?\
(si noti che il prefisso può espandere i componenti della directory e causare un overflow del limite di 32.000)Per .Net Framework precedenti a 3.5 questo dovrebbe funzionare:
La corrispondenza delle espressioni regolari dovrebbe farti un po 'di strada. Ecco uno snippet che utilizza la costante 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;
}
Per .Net Framework dopo 3.0 questo dovrebbe funzionare:
http://msdn.Microsoft.com/en-us/library/system.io.path.getinvalidpathchars(v=vs.90).aspx
La corrispondenza delle espressioni regolari dovrebbe farti un po 'di strada. Ecco uno snippet che utilizza la costante 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;
}
Una volta che lo sai, dovresti anche cercare diversi formati, ad esempio c:\my\drive
e \\server\share\dir\file.ext
Prova ad usarlo e intercettalo per l'errore. Il set consentito può cambiare tra i file system o tra diverse versioni di Windows. In altre parole, se vuoi sapere se a Windows piace il nome, passa il nome e lascia che te lo dica.
Questa classe pulisce nomi di file e percorsi; usalo come
var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');
Ecco il codice;
/// <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();
}
}
Questo è quello che uso:
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));
}
Il primo modello crea un'espressione regolare che contiene solo nomi e caratteri di file non validi/non validi per le piattaforme Windows. Il secondo fa lo stesso, ma garantisce che il nome sia legale per qualsiasi piattaforma.
Un caso da tenere a mente, che mi ha sorpreso quando l'ho scoperto per la prima volta: Windows consente di portare i caratteri spaziali nei nomi dei file! Ad esempio, i seguenti sono tutti nomi di file legali e distinti su Windows (meno le virgolette):
"file.txt"
" file.txt"
" file.txt"
Un take away da questo: Usare cautela quando si scrive codice che taglia gli spazi bianchi iniziali/finali da una stringa di nome file.
Semplificando la risposta di Eugene Katz:
bool IsFileNameCorrect(string fileName){
return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}
O
bool IsFileNameCorrect(string fileName){
return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
Microsoft Windows: il kernel di Windows vieta l'uso di caratteri nell'intervallo 1-31 (cioè 0x01-0x1F) e caratteri "*: <>?\|. Sebbene NTFS consenta che ogni componente del percorso (directory o nome file) sia lungo 255 caratteri e percorsi fino a circa 32767 caratteri, il kernel di Windows supporta solo i percorsi lunghi fino a 259 caratteri. Inoltre, Windows vieta l'uso dei nomi dei dispositivi MS-DOS AUX, CLOCK $, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL e PRN, così come questi nomi con qualsiasi estensione (ad esempio, AUX.txt), tranne quando si utilizza Percorsi UNC lunghi (ad es. \.\C:\nul.txt o \?\D:\aux\con). (In effetti, CLOCK $ può essere utilizzato se viene fornita un'estensione.) Queste restrizioni si applicano solo a Windows - Linux, per esempio, consente l'uso di "*: <>?\| anche in NTFS.
Piuttosto che includere esplicitamente tutti i possibili caratteri, è possibile eseguire un'espressione regolare per verificare la presenza di caratteri non validi e quindi segnalare un errore. Idealmente la tua applicazione dovrebbe nominare i file esattamente come l'utente desidera, e solo piangere se incappa in un errore.
Lo uso per eliminare caratteri non validi nei nomi di file senza generare eccezioni:
private static readonly Regex InvalidFileRegex = new Regex(
string.Format("[{0}]", Regex.Escape(@"<>:""/\|?*")));
public static string SanitizeFileName(string fileName)
{
return InvalidFileRegex.Replace(fileName, string.Empty);
}
Anche CON, PRN, AUX, NUL, COM # e pochi altri non sono mai nomi di file legali in nessuna directory con alcuna estensione.
La domanda è se stai cercando di determinare se il nome di un percorso è un percorso legale di Windows, o se è legale sul sistema in cui è in esecuzione il codice. ? Penso che quest'ultimo sia più importante, quindi personalmente, probabilmente decomporrò il percorso completo e proverei a usare _mkdir per creare la directory a cui appartiene il file, quindi provare a creare il file.
In questo modo sai non solo se il percorso contiene solo caratteri Windows validi, ma se in realtà rappresenta un percorso che può essere scritto da questo processo.
Per completare le altre risposte, ecco un paio di casi Edge aggiuntivi che potresti voler prendere in considerazione.
Excel può avere problemi se si salva una cartella di lavoro in un file il cui nome contiene i caratteri "[" o "]". Vedi http://support.Microsoft.com/kb/215205 per i dettagli.
Sharepoint ha un'intera serie aggiuntiva di restrizioni. Vedi http://support.Microsoft.com/kb/905231 per i dettagli.
Da MSDN , ecco un elenco di caratteri che non sono consentiti:
Utilizzare quasi tutti i caratteri nella codepage corrente per un nome, inclusi caratteri Unicode e caratteri nel set di caratteri estesi (128-255), ad eccezione dei seguenti:
- I seguenti caratteri riservati non sono consentiti: <>: "/\|? *
- I caratteri le cui rappresentazioni dei numeri interi sono nell'intervallo compreso tra zero e 31 non sono consentiti.
- Qualsiasi altro carattere che il file system di destinazione non consente.
Le espressioni regolari sono eccessive per questa situazione. È possibile utilizzare il metodo String.IndexOfAny()
in combinazione con Path.GetInvalidPathChars()
e Path.GetInvalidFileNameChars()
.
Si noti inoltre che entrambi i metodi Path.GetInvalidXXX()
clonano un array interno e restituiscono il clone. Quindi se lo farai molto (migliaia e migliaia di volte) puoi mettere in cache una copia della matrice di caratteri non valida per il riutilizzo.
Anche il file system di destinazione è importante.
Sotto NTFS, alcuni file non possono essere creati in directory specifiche E.G. $ Avvia in root
Questa è una domanda già risposta, ma solo per il gusto di "Altre opzioni", eccone una non ideale:
(non ideale, perché usare le eccezioni come controllo del flusso è una "brutta cosa", generalmente)
public static bool IsLegalFilename(string name)
{
try
{
var fileInfo = new FileInfo(name);
return true;
}
catch
{
return false;
}
}
Se stai solo cercando di verificare se una stringa che contiene il tuo nome/percorso del file ha caratteri non validi, il metodo più veloce che ho trovato è usare Split()
per suddividere il nome del file in una serie di parti ovunque ci sia un carattere non valido. Se il risultato è solo un array di 1, non ci sono caratteri non validi. :-)
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;
Ho provato a eseguire questo e altri metodi sopra menzionati su un nome file/percorso 1.000.000 volte in LinqPad.
Usare Split()
è solo ~ 850ms.
L'utilizzo di Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")
è di circa 6 secondi.
Le espressioni regolari più complicate sono MOLTO peggiori, come alcune delle altre opzioni, come l'uso dei vari metodi nella classe Path
per ottenere il nome del file e lasciare che la loro validazione interna faccia il lavoro (molto probabilmente a causa dell'overhead della gestione delle eccezioni).
Certo, spesso non è necessario convalidare 1 milione di nomi di file, quindi una singola iterazione va bene per la maggior parte di questi metodi. Ma è ancora abbastanza efficiente ed efficace se stai cercando caratteri non validi.
molte di queste risposte non funzioneranno se il nome del file è troppo lungo e in esecuzione su un ambiente pre Windows 10. Allo stesso modo, pensa a cosa vuoi fare con i punti - il fatto che il lead o il trailing sia tecnicamente valido, ma può creare problemi se non vuoi che il file sia difficile da vedere o cancellare rispettivamente.
Questo è un attributo di convalida che ho creato per verificare un nome file valido.
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;
}
e i test
[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"));
}
Il mio tentativo:
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 "";
}
}
Questo non è perfetto perché Path.GetInvalidPathChars
non restituisce il set completo di caratteri che non sono validi nei nomi di file e directory e ovviamente ci sono molte altre sottigliezze.
Quindi uso questo metodo come complemento:
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;
}
}
Prova a creare il file e restituisce false se c'è un'eccezione. Certo, ho bisogno di creare il file ma penso che sia il modo più sicuro per farlo. Si noti inoltre che non sto eliminando le directory che sono state create.
È inoltre possibile utilizzare il primo metodo per eseguire la convalida di base e quindi gestire attentamente le eccezioni quando viene utilizzato il percorso.
Questo controllo
static bool IsValidFileName(string name)
{
return
!string.IsNullOrWhiteSpace(name) &&
name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
!Path.GetFullPath(name).StartsWith(@"\\.\");
}
filtra i nomi con caratteri non validi (<>:"/\|?*
e ASCII 0-31), così come i dispositivi DOS riservati (CON
, NUL
, COMx
). Permette spazi iniziali e nomi all-dot, coerenti con Path.GetFullPath
. (Creare file con spazi iniziali ha successo sul mio sistema).
Usato .NET Framework 4.7.1, testato su Windows 7.
Ho avuto questa idea da qualcuno. - Non so chi. Lascia che il sistema operativo faccia il grande lavoro.
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;
}
Suggerisco di usare solo Path.GetFullPath ()
string tagetFileFullNameToBeChecked;
try
{
Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
// invalid chars found
}