I don't know if it's legit at StackOverflow to post your own answer to a question, but I saw nobody had asked this already. I went looking for a C# Glob and didn't find one, so I wrote one that others might find useful.
- After some google-ling I found what glob is supposed to do. en.wikipedia.org/wiki/Glob_(programming)tuinstoel– tuinstoel2008-12-29 20:41:01 +00:00Commented Dec 29, 2008 at 20:41
- You would have gotten more points if you hadn't made it a community wiki. :-)George Stocker– George Stocker2008-12-30 01:10:36 +00:00Commented Dec 30, 2008 at 1:10
- Why would I have gotten more points? I'm new here ...Mark Maxham– Mark Maxham2009-01-02 03:27:06 +00:00Commented Jan 2, 2009 at 3:27
- Just for reference: Globs look like path***.txtDaniel Little– Daniel Little2014-12-18 22:42:06 +00:00Commented Dec 18, 2014 at 22:42
- @Mark because Community Wiki answers don't award points, every upvote usually gives you 10 points.Daniel Little– Daniel Little2014-12-18 22:42:54 +00:00Commented Dec 18, 2014 at 22:42
Add a comment |
3 Answers
/// <summary> /// return a list of files that matches some wildcard pattern, e.g. /// C:\p4\software\dotnet\tools\*\*.sln to get all tool solution files /// </summary> /// <param name="glob">pattern to match</param> /// <returns>all matching paths</returns> public static IEnumerable<string> Glob(string glob) { foreach (string path in Glob(PathHead(glob) + DirSep, PathTail(glob))) yield return path; } /// <summary> /// uses 'head' and 'tail' -- 'head' has already been pattern-expanded /// and 'tail' has not. /// </summary> /// <param name="head">wildcard-expanded</param> /// <param name="tail">not yet wildcard-expanded</param> /// <returns></returns> public static IEnumerable<string> Glob(string head, string tail) { if (PathTail(tail) == tail) foreach (string path in Directory.GetFiles(head, tail).OrderBy(s => s)) yield return path; else foreach (string dir in Directory.GetDirectories(head, PathHead(tail)).OrderBy(s => s)) foreach (string path in Glob(Path.Combine(head, dir), PathTail(tail))) yield return path; } /// <summary> /// shortcut /// </summary> static char DirSep = Path.DirectorySeparatorChar; /// <summary> /// return the first element of a file path /// </summary> /// <param name="path">file path</param> /// <returns>first logical unit</returns> static string PathHead(string path) { // handle case of \\share\vol\foo\bar -- return \\share\vol as 'head' // because the dir stuff won't let you interrogate a server for its share list // FIXME check behavior on Linux to see if this blows up -- I don't think so if (path.StartsWith("" + DirSep + DirSep)) return path.Substring(0, 2) + path.Substring(2).Split(DirSep)[0] + DirSep + path.Substring(2).Split(DirSep)[1]; return path.Split(DirSep)[0]; } /// <summary> /// return everything but the first element of a file path /// e.g. PathTail("C:\TEMP\foo.txt") = "TEMP\foo.txt" /// </summary> /// <param name="path">file path</param> /// <returns>all but the first logical unit</returns> static string PathTail(string path) { if (!path.Contains(DirSep)) return path; return path.Substring(1 + PathHead(path).Length); } 4 Comments
jturcotte
Bug? I had to replace "Path.Combine(head, dir)" with "dir" since Directory.GetDirectories already returns the full path. This caused a bug with paths like "..\SomeDir*.dll" since "..\" were duplicated by Combine
Ben
This doesn't seem to work if you pass a string like
* to the Glob function. Are there some assumptions being made as to the sort of wildcard string it can handle? An absolute path maybe?AdrianHHH
Method
Glob splits the argument into two pieces at a DirSep. The code fails if there is no Dirsep. Adding the following statement to the beginning of method PathHead appears to work: if (! path.Contains(DirSep)) {return ".";}.AdrianHHH
@Ben The assumption seems to be that the string contains a
DirSep. With the change in my previous comment the code works for me.You can use the "dir" (aka "Get-ChildItem") powershell cmdlet from C#.
(I'm not saying whether you should.)
You have to add this reference to your project file (".csproj" or ".vcproj") manually:
<Reference Include="System.Management.Automation" /> See here for more details on how to use cmdlets from C#: http://www.devx.com/tips/Tip/42716
Here a working program:
using System; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Collections.ObjectModel; namespace CsWildcard { class Program { static IEnumerable<string> CmdletDirGlobbing(string basePath, string glob){ Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); // cd to basePath if(basePath != null){ Pipeline cdPipeline = runspace.CreatePipeline(); Command cdCommand = new Command("cd"); cdCommand.Parameters.Add("Path", basePath); cdPipeline.Commands.Add(cdCommand); cdPipeline.Invoke(); // run the cmdlet } // run the "dir" cmdlet (e.g. "dir C:\*\*\*.txt" ) Pipeline dirPipeline = runspace.CreatePipeline(); Command dirCommand = new Command("dir"); dirCommand.Parameters.Add("Path", glob); dirPipeline.Commands.Add(dirCommand); Collection<PSObject> dirOutput = dirPipeline.Invoke(); // for each found file foreach (PSObject psObject in dirOutput) { PSMemberInfoCollection<PSPropertyInfo> a = psObject.Properties; // look for the full path ("FullName") foreach (PSPropertyInfo psPropertyInfo in psObject.Properties) { if (psPropertyInfo.Name == "FullName") { yield return psPropertyInfo.Value.ToString(); // yield it } } } } static void Main(string[] args) { foreach(string path in CmdletDirGlobbing(null,"C:\\*\\*\\*.txt")){ System.Console.WriteLine(path); } foreach (string path in CmdletDirGlobbing("C:\\", "*\\*\\*.exe")) { System.Console.WriteLine(path); } Console.ReadKey(); } } } Comments
It is easy to implement with DotNet.Glob
Example:
public static class Glob { public static IEnumerable<FileInfo> Exec(DirectoryInfo dir, string glob) { var matcher = DotNet.Globbing.Glob.Parse(glob); return dir.EnumerateAllFiles() .Where(f => matcher.IsMatch(f.FullName)); } public static IEnumerable<FileInfo> EnumerateAllFiles(this DirectoryInfo dir) { foreach(var f in dir.EnumerateFiles()) { yield return f; } foreach(var sub in dir.EnumerateDirectories()) { foreach(var f in EnumerateAllFiles(sub)) { yield return f; } } } } 4 Comments
jjxtra
Where do you get the DirectoryInfo from? If I just have a string glob, I don't want to have to write the logic to pull out the base dir...
sergeyt
You can have own version where the base dir is just a current directory of running process. Also example can be easily extended to support multiple base directories.
vee
'DirectoryInfo' does not contain a definition for 'EnumerateAllFiles' error.
sergeyt
@vee EnumerateAllFiles extension method is defined in the Glob class in my snippet. You can change it to non-extension version. I am not sure why you are getting this error.