I have a C# application which reads text files with lines such as:
c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm
c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4
What I need are two values from each lines in such a way:
From line 1:
filename variable valued as 'myfile2.cfm' and filepath variable as "" [empty]
From line 2:
filename variable valued as 'my file1.blahh4' and filepath as 'includes' [also be \includes\subfolder]
I have tried code like indexof and substring but no success so far. I think some RegEx should help? Basically, the slashes will be constantly 3 before file or folder names begin.
Thanks!
You can use Path which parses paths.
Please see the following code for more details:
var path = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm";
var pathFileName = Path.GetFileName(path); // "myfile2.cfm"
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16";
var pathDirectory = Path.GetDirectoryName(path).Replace(baseDirectory, ""); // ""
Edit See the code below which sets the paths to LowerInvariant in order to ensure the replace works as expected.
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16".ToLowerInvariant();
var paths = new string[] {
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4"
};
var sanitizedPaths = new List<Tuple<string,string>>();
foreach(var path in paths.Select(p => (p ?? String.Empty).ToLowerInvariant()))
{
var fileName = Path.GetFileName(path);
var directory = Path.GetDirectoryName(path).Replace(baseDirectory, String.Empty);
sanitizedPaths.Add(new Tuple<string, string>(fileName, directory));
}
// sanitizedPaths[0] -> "myfile2.cfm" | ""
// sanitizedPaths[1] -> "my file1.blahh4" | "\includes"
Edit 2 Using Uri and based on the fact your base directory is always 3 segments, the following should do:
var paths = new string[] {
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\subFolder\other file.extension"
};
var sanitizedPaths = new List<Tuple<string, string>>();
foreach (var path in paths.Select(p => (p ?? String.Empty).ToLowerInvariant()))
{
var uri = new Uri(path);
var pathWithoutBaseDirectory = String.Join("/", uri.Segments.Skip(4));
var fileName = Path.GetFileName(pathWithoutBaseDirectory);
var directory = Path.GetDirectoryName(pathWithoutBaseDirectory);
sanitizedPaths.Add(new Tuple<string, string>(fileName, directory));
}
Use Path class to get file names and file directories:
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\";
var files = new[]
{
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4"
};
And here goes LINQ query
var query = from file in files
let directoryName = Path.GetDirectoryName(file)
select new
{
filename = Path.GetFileName(file),
filepath = directoryName.StartsWith(baseDirectory)
? directoryName.Substring(baseDirectory.Length) : ""
};
Output:
[
{
filename: "myfile2.cfm",
filepath: ""
},
{
filename: "my file1.blahh4",
filepath: "includes"
}
]
What I would do is use the FileInfo class in a recursive loop to get a list of all files. You can separate the path and filename using the FileInfo. Then using the string length of the base folder you are acting on and substring that from the path of each of the files.
Something like this?
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
RelativePaths p = new RelativePaths(#"u:\test");
foreach (var str in p.MyFiles)
{
Console.WriteLine(str);
}
Console.ReadKey();
}
}
class MyFileInfo
{
public MyFileInfo(string path, string filename)
{
Path = path;
Filename = filename;
}
public string Path { get; private set; }
public string Filename { get; private set; }
public override string ToString() => $"{Path}, {Filename}";
}
class RelativePaths
{
List<MyFileInfo> myPaths = new List<MyFileInfo>();
public RelativePaths(string startingPath = #"U:\test")
{
DirectoryInfo dir = new DirectoryInfo(startingPath);
PathSeparator(dir.FullName, dir);
}
public MyFileInfo[] MyFiles => myPaths.ToArray();
public void PathSeparator(string originalPath, DirectoryInfo dir)
{
// Files in dir
foreach (var file in dir.GetFiles())
{
myPaths.Add(new MyFileInfo(file.DirectoryName.Substring(originalPath.Length),
file.Name));
}
foreach (var folder in dir.GetDirectories())
{
PathSeparator(originalPath, folder);
}
}
}
}
Original paths:
u:\test\subfolder
u:\test\testfile1.txt
u:\test\subfolder\fileinsub1.txt
u:\test\subfolder\subfolder2
u:\test\subfolder\subfolder2\two deep.txt
Where result is:
, testfile1.txt
\subfolder, fileinsub1.txt
\subfolder\subfolder2, two deep.txt
Note that the results are in a list of type MyFileInfo so you can just use the data output however you see fit. This is only and example. You may want to create a static class with an extension method instead but using the recursive method to look in each folder is the concept that might be helpful. Note you can copy the source code directly into a new console project and run it in Visual Studio just change the starting path.
Try this out:
var s1 =#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm";
var s2 =#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4";
var filename = Path.GetFileName(s1);
var p1 = Path.GetDirectoryName(s1);
if (!p1.ToLowerInvariant().Contains(#"\includes"))
p1 = "";
var p2 = Path.GetDirectoryName(s2);
if (!p2.ToLowerInvariant().Contains(#"\includes"))
p2 = "";
Related
I need someone to point me in the right direction.
Goal:
Return a list of Folder Names in a path that contain a string in their name. For example: The Path has a Directory named Pictures_New and Videos_New. The string I am searching with is "Pictures_" and "Videos_".
It all works with one string parameter being passed as a search string. My problem is getting it to work with multiple filters. I know it is easily done with file names and extensions.
This is being passed to GetFolders():
string[] filterStrings = { "Pictures_", "Videos_" }
Rest of my code:
public IEnumerable<string> GetFolders(string path, string[] filterStrings, SearchOption searchOption = SearchOption.AllDirectories)
{
IEnumerable<string> folders = Directory.EnumerateDirectories(path, "Pictures_*.*", searchOption);
var resultFolders = new List<string>();
if(filterStrings.Length > 0)
{
foreach (var foldername in folders)
{
string folderName = Path.GetFileName(Path.GetDirectoryName(foldername));
if (string.IsNullOrEmpty(folderName) || Array.IndexOf(filterStrings, "*" + folderName) < 0)
{
// This leaves us only with the Directory names. No paths.
var b = (foldername.Substring(foldername.LastIndexOf(#"\") + 1));
resultFolders.Add(b);
}
}
}
return resultFolders;
}
You can use Linq SelectMany to parse your list of filters and return a list of the results with Directory.GetDirectories();
It will of course return all the Sub Directories that match the filter. Use just "*".
public IEnumerable<string> GetFolders(string path, string[] filterStrings, SearchOption searchOption = SearchOption.AllDirectories)
{
List<string> resultFolders = filterStrings
.SelectMany(flt => Directory.GetDirectories(path, flt, searchOption))
.ToList();
return resultFolders;
}
try:
var patterns = new[] { "Pictures_*", "Videos_*" };
var dirsFound = new List<string>();
foreach (var dir in patterns.Select(pattern => Directory.GetDirectories(#"my path", pattern).ToArray()))
{
dirsFound.AddRange(dir);
}
Looks like you're not looping through each of your filter strings:
var folders = new List<string>();
foreach (var filterString in filterStrings)
{
folders.AddRange(Directory.EnumerateDirectories(path, filterString, searchOption););
}
I need to search a folder containing csv files. The records i'm interested in have 3 fields: Rec, Country and Year. My job is to search the files and see if any of the files has records for more then a single year. Below the code i have so far:
// Get each individual file from the folder.
string startFolder = #"C:\MyFileFolder\";
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);
var queryMatchingFiles =
from file in fileList
where (file.Extension == ".dat" || file.Extension == ".csv")
select file;
Then i'm came up with this code to read year field from each file and find those where year count is more than 1(The count part was not successfully implemented)
public void GetFileData(string filesname, char sep)
{
using (StreamReader reader = new StreamReader(filesname))
{
var recs = (from line in reader.Lines(sep.ToString())
let parts = line.Split(sep)
select parts[2]);
}
below a sample file:
REC,IE,2014
REC,DE,2014
REC,FR,2015
Now i'am struggling to combine these 2 ideas to solve my problem in a single query. The query should list those files that have record for more than a year.
Thanks in advance
Something along these lines:
string startFolder = #"C:\MyFileFolder\";
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);
var fileData =
from file in fileList
where (file.Extension == ".dat" || file.Extension == ".csv")
select GetFileData(file, ',')
;
public string GetFileData(string filesname, char sep)
{
using (StreamReader reader = new StreamReader(filesname))
{
var recs = (from line in reader.Lines(sep.ToString())
let parts = line.Split(sep)
select parts[2]);
var multipleyears = recs.Distinct().Count();
if(multipleyears > 1)
return filename;
}
}
Not on my develop machine, so this might not compile "as is", but here's a direction
var lines = // file.readalllines();
var years = from line in lines
let parts = line.Split(new [] {','})
select parts[2]);
var distinct_years = years.Distinct();
if (distinct_years >1 )
// this file has several years
"My job is to search the files and see if any of the files has records
for more then a single year."
This specifies that you want a Boolean result, one that says if any of the files has those records.
For fun I'll extend it a little bit more:
My job is to get the collection of files where any of the records is about more than a single year.
You were almost there. Let's first declare a class with the records in your file:
public class MyRecord
{
public string Rec { get; set; }
public string CountryCode { get; set; }
public int Year { get; set; }
}
I'll make an extension method of the class FileInfo that will read the file and returns the sequence of MyRecords that is in it.
For extension methods see MSDN Extension Methods (C# Programming Guide)
public static class FileInfoExtension
{
public static IEnumerable<MyRecord> ReadMyRecords(this FileInfo file, char separator)
{
var records = new List<MyRecord>();
using (var reader = new StreamReader(file.FullName))
{
var lineToProcess = reader.ReadLine();
while (lineToProcess != null)
{
var splitLines = lineToProcess.Split(new char[] { separator }, 3);
if (splitLines.Length < 3) throw new InvalidDataException();
var record = new MyRecord()
{
Rec = splitLines[0],
CountryCode = splitLines[1],
Year = Int32.Parse(splitLines[2]),
};
records.Add(record);
lineToProcess = reader.ReadLine();
}
}
return records;
}
}
I could have used string instead of FileInfo, but IMHO a string is something completely different than a filename.
After the above you can write the following:
string startFolder = #"C:\MyFileFolder\";
var directoryInfo = new DirectoryInfo(startFolder);
var allFiles = directoryInfo.EnumerateFiles("*.*", SearchOption.AllDirectories);
var sequenceOfFileRecordCollections = allFiles.ReadMyRecords(',');
So now you have per file a sequence of the MyRecords in the file. You want to know which files have more than one year, Let's add another extension method to class FileInfoExtension:
public static bool IsMultiYear(this FileInfo file, char separator)
{
// read the file, only return true if there are any records,
// and if any record has a different year than the first record
var myRecords = file.ReadMyRecords(separator);
if (myRecords.Any())
{
int firstYear = myRecords.First().Year;
return myRecords.Any(record => record.Year != firstYear);
}
else
{
return false;
}
}
The sequence of file that have more than one year in it is:
allFiles.Where(file => file.IsMultiYear(',');
Put everything in one line:
var allFilesWithMultiYear = new DirectoryInfo(#"C:\MyFileFolder\")
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.Where(file => file.IsMultiYear(',');
By creating two fairly simple extension methods your problem became one highly readable statement.
I am trying to do a foreach loop where it would select each string in my array and add it to the path which is a string and stores the path for the image, this is what I have so far:
string[] Images = new string[] { "Star_00001.png", "Star_00002.png", "Star_00003.png" };
string Path = "Assets/Images/";
if (LevelUp)
{
foreach()
{
}
}
As you can see I want the foreach loop to go through each string in the Images array, for each string in the Images array I want it to be added to the path string so the end result for example would be "Assets/Images/Star_00001.png"
Does anyone know how I should do this?
A foreach loop has the syntax:
foreach(T ident in collection) {
with T the type of the elements, ident the name of the variable and collection an object that supports the IEnumerable interface.
So you can implement:
string[] images = new string[] { "Star_00001.png", "Star_00002.png", "Star_00003.png" };
string path = "Assets/Images/";
if (LevelUp) {
foreach(string file in images) {
string thepath = Path.Combine(path,file);
//do something with file or thepath, like
Console.WriteLine(thepath);
}
}
A final note is that C#'s consensus is that variables start with a lowercase and type names with an uppercase.
It is not recommended to generate file paths by using string concatenation. The recommended way is to use Path.Combine which is provided in System.IO. Consider the example below:
string[] Images = new string[] { "Star_00001.png", "Star_00002.png", "Star_00003.png" };
string path = "Assets/Images/";
if (LevelUp)
{
foreach (string s in Images)
{
// You can store result in an array or sth depending on what you
// are trying to achieve
string result = Path.Combine(path, s);
}
}
string[] Images = new string[] { "Star_00001.png", "Star_00002.png", "Star_00003.png" };
string path = "Assets/Images/";
if (LevelUp)
Images = Images.Select(image => Path.Combine(path, image)).ToArray();
I am trying to sort two folders in to a patched folder, finding which file is new in the new folder and marking it as new, so i can transfer that file only. i dont care about dates or hash changes. just what file is in the new folder that is not in the old folder.
somehow the line
pf.NFile = !( oldPatch.FindAll(s => s.Equals(f)).Count() == 0);
is always returning false. is there something wrong with my logic of cross checking?
List<string> newPatch = DirectorySearch(_newFolder);
List<string> oldPatch = DirectorySearch(_oldFolder);
foreach (string f in newPatch)
{
string filename = Path.GetFileName(f);
string Dir = (Path.GetDirectoryName(f).Replace(_newFolder, "") + #"\");
PatchFile pf = new PatchFile();
pf.Dir = Dir;
pf.FName = filename;
pf.NFile = !( oldPatch.FindAll(s => s.Equals(f)).Count() == 0);
nPatch.Files.Add(pf);
}
foreach (string f in oldPatch)
{
string filename = Path.GetFileName(f);
string Dir = (Path.GetDirectoryName(f).Replace(_oldFolder, "") + #"\");
PatchFile pf = new PatchFile();
pf.Dir = Dir;
pf.FName = filename;
if (!nPatch.Files.Exists(item => item.Dir == pf.Dir &&
item.FName == pf.FName))
{
nPatch.removeFiles.Add(pf);
}
}
I don't have the classes you are using (like DirectorySearch and PatchFile), so i can't compile your code, but IMO the line _oldPatch.FindAll(... doesn't return anything because you are comparing the full path (c:\oldpatch\filea.txt is not c:\newpatch\filea.txt) and not the file name only. IMO your algorithm could be simplified, something like this pseudocode (using List.Contains instead of List.FindAll):
var _newFolder = "d:\\temp\\xml\\b";
var _oldFolder = "d:\\temp\\xml\\a";
List<FileInfo> missing = new List<FileInfo>();
List<FileInfo> nPatch = new List<FileInfo>();
List<FileInfo> newPatch = new DirectoryInfo(_newFolder).GetFiles().ToList();
List<FileInfo> oldPatch = new DirectoryInfo(_oldFolder).GetFiles().ToList();
// take all files in new patch
foreach (var f in newPatch)
{
nPatch.Add(f);
}
// search for hits in old patch
foreach (var f in oldPatch)
{
if (!nPatch.Select (p => p.Name.ToLower()).Contains(f.Name.ToLower()))
{
missing.Add(f);
}
}
// new files are in missing
One possible solution with less code would be to select the file names, put them into a list an use the predefined List.Except or if needed List.Intersect methods. This way a solution to which file is in A but not in B could be solved fast like this:
var locationA = "d:\\temp\\xml\\a";
var locationB = "d:\\temp\\xml\\b";
// takes file names from A and B and put them into lists
var filesInA = new DirectoryInfo(locationA).GetFiles().Select (n => n.Name).ToList();
var filesInB = new DirectoryInfo(locationB).GetFiles().Select (n => n.Name).ToList();
// Except retrieves all files that are in A but not in B
foreach (var file in filesInA.Except(filesInB).ToList())
{
Console.WriteLine(file);
}
I have 1.xml, 2.xml, 3.xml in A and 1.xml, 3.xml in B. The output is 2.xml - missing in B.
I am having a list of files from a directory and I want to sort it out by filename.
This is the main code:
var localPath = this.Server.MapPath("~/Content/Img/" + type + "/");
var directory = new DirectoryInfo(localPath);
isDirectory = directory.Exists;
if (isDirectory)
{
foreach (FileInfo f in directory.GetFiles())
{
Picture picture = new Picture();
picture.ImagePath = path;
picture.CreationDate = f.CreationTime;
picture.FileName = f.Name;
listPictures.Add(picture);
}
}
here is the class Picture where all the files are stored:
public class Picture
{
public string ImagePath { get; set; }
public string FileName { get; set; }
public DateTime CreationDate { get; set; }
}
How do you do to sort a files list by order of FileName?
Simply change your for loop :
foreach (FileInfo f in directory.GetFiles().OrderBy(fi=>fi.FileName))
{
}
Alternatively, you can rewrite the whole loop using this code :
var sortedFiles = from fi in directory.GetFiles()
order by fi.FileName
select new Picture { ImagePath = path, CreationDate = f.CreationTime, FileName = f.FileName };
listPictures.AddRange(sortedFiles);
listPictures = listPictures.OrderBy(x => x.FileName).ToList();
You can use lambda expression and/or extension methods. For example:
listPictures.OrderBy(p => p.FileName).ToList();
Note that EnumerateFiles performs lazy loading and can be more efficient for larger directories, so:
dir.EnumerateFiles().OrderBy(f => f.FileName))
You can use LINQ from the beginning:
var files = from f in directory.EnumerateFiles()
let pic = new Picture(){
ImagePath = path;
CreationDate = f.CreationTime;
FileName = f.Name;
}
orderby pic.FileName
select pic;
Note that Directory.EnumerateFiles(path) will be more efficient if only the FileName is used.