如何在每个目录或子目录中找到包含特定字符串的最新文件



我有一个服务器端日志文件夹,其中包含数百个日志,根据日志来自的机器,其中大多数都在子目录中。任务是提取每个目录中包含特定字符串的最新文件的名称(并非所有文件都有这个字符串(,以便可以对每台机器进行分析。我在下面列出了我的尝试,但它看起来相当笨拙和冗长,我想知道是否有一种更容易/更好/更快/更有效的方法来实现这一点,也许是用linq?

void Main()
{
string SourcePath = @"L:machinelogs";
string filemask = "*.log";
string searchitem = @"cannot access server data";

List<string> fileswithsearchitem = new List<string>();

DirectoryInfo directory = new DirectoryInfo(SourcePath);
IEnumerable<DirectoryInfo> dirs = directory.EnumerateDirectories("*",new EnumerationOptions() { RecurseSubdirectories = true, IgnoreInaccessible = true });
dirs.Append(directory);
foreach (var dir in dirs)
{
var found = false;
var files = dir.EnumerateFiles(filemask);
foreach(var file in files.OrderByDescending(f => f.CreationTime).ToList())
{
foreach (var line in File.ReadLines(file.FullName))
{
if(line.Contains(searchitem))
{
fileswithsearchitem.Add(file.FullName + " : " + line);
found = true;
break;
}
}
if(found)
{
break;
}
}
}

foreach (string item in fileswithsearchitem)
{
Console.WriteLine(item);
}
}

我想知道是否有一种更容易/更好/更快/更有效的方法来完成

我赞成将您的问题发布在https://codereview.stackexchange.com/.我没有恶意或敌意。请求";一种更容易/更好/更快/更有效的方式">正在请求代码审查。在那里你会得到更好的答案。有了这个。。。

。。。也许和林克在一起?

我从未见过Linq让任何东西执行得更快。事实上,我唯一注意到性能差异的时候是更糟的。另一方面,它可以使代码更有表现力。所以我认为林克是一种权衡。在这种情况下,是的,这对你来说可能是值得的。

任务是提取每个目录中包含特定字符串的最新文件的名称(并非所有文件都有该字符串(,以便可以对每台机器进行分析。

我在下面列出了我的尝试,但它看起来相当笨拙和冗长的

您编写的代码不可重用;相反,它需要:

  • 一个名为";L";(不常见(
  • 驱动器号和用分隔的路径(仅在Windows上发生(
  • 日志文件名的扩展名为";。log">
  • 搜索文本为";不能访问服务器数据">
  • 要显示在STDOUT中的文本
  • 真实的文件系统

但是这些对你来说真的是问题吗?做抽象值得花费时间和精力吗?如果你所拥有的是有效的,那么为什么要修复它呢?我不能为你回答这些问题,但你必须进行一些自我反省。

摘要

以下是一些可能的抽象,以及使用它们的一些可能方法。

抽象文件系统

拥有一个抽象的文件系统可以更容易地编写自动化测试,这样你就可以确保你的代码在未来几年里会继续工作。

这些是我看到你使用的方法:

  • 枚举目录
  • 枚举文件

只要有足够的小手挥舞,你的代码就会看起来像这样:

interface IDirectory
{
/// <summary>
/// Recursively yields all accessible nested directories
/// </summary>
IEnumerable<IDirectory> EnumerateDirectories();
/// <summary>
/// Yields all file paths that match the given mask. Yields them in order of
/// newest first.
/// </summary>
IEnumerable<string> EnumerateFiles(string mask);
}
interface IFileSystem
{
IDirectory OpenDirectory(string path);
Stream OpenFile(string path);
}
class DirectoryInfoAdapter : IDirectory
{
readonly DirectoryInfo _info;
public IEnumerable<IDirectory> EnumerateDirectories() => _info
.EnumerateDirectories("*", new EnumerationOptions() { RecurseSubdirectories = true, IgnoreInaccessible = true })
.Select(x => new DirectoryInfoAdapter(x));
public IEnumerable<string> EnumerateFiles(string mask) => _info
.EnumerateFiles(mask)
.Select(x => x.FullName);
}
class RealFileSystem : IFileSystem
{
public IDirectory OpenDirectory(string path) => new DirectoryInfoAdapter(new DirectoryInfo(path));
public Stream OpenFile(string path) => File.Open(path);
}
void DoStuff(IFileSystem fileSystem)
{
string SourcePath = @"L:machinelogs";
string filemask = "*.log";
string searchitem = @"cannot access server data";

List<string> fileswithsearchitem = new List<string>();
IDirectory directory = fileSystem.OpenDirectory(SourcePath);
IEnumerable<IDirectory> dirs = directory.EnumerateDirectories();
dirs.Append(directory);
foreach (var dir in dirs)
{
var found = false;
var files = dir.EnumerateFiles(filemask);
foreach(var file in files)
{
using var stream = fileSystem.OpenFile(file);
using var reader = new StreamReader(stream);
while (reader.ReadLine() is {} line)
{
if(line.Contains(searchitem))
{
fileswithsearchitem.Add(file + " : " + line);
found = true;
break;
}
}
if(found)
{
break;
}
}
}

foreach (string item in fileswithsearchitem)
{
Console.WriteLine(item);
}
}
void Main()
{
IFileSystem fileSystem = new RealFileSystem();
DoStuff(fileSystem);
}

然后你可以写这样一个自动测试:

class DictionaryBackedDirectory : IDirectory
{
readonly IReadOnlyCollection<IDirectory> _directories;
readonly IReadOnlyCollection<string> _files;
public DictionaryBackedDirectory(
IReadOnlyCollection<IDirectory> directories,
IReadOnlyCollection<string> files)
{
_directories = directories;
_files = files;
}
public IEnumerable<IDirectory> EnumerateDirectories() => _directories;
public IEnumerable<string> EnumerateFiles(string mask) => _files; // TODO: implement masking
}
class DictionaryBackedFileSystem : IFileSystem
{
readonly IReadOnlyDictionary<string, IDirectory> _directories;
readonly IReadOnlyDictionary<string, Func<Stream>> _files;
public DictionaryBackedFileSystem(
IReadOnlyDictionary<string, IDirectory> directories,
IReadOnlyDictionary<string, Func<Stream>> files)
{
_directories = directories;
_files = files
}
public IDirectory OpenDirectory(string path) => _directories[path];
public Stream OpenFile(string path) => _files[path]();
}
void AutomatedTest()
{
var mockFileSystem = new DictionaryBackedFileSystem(
new Dictionary<string, IDirectory>()
{
[@"L:machinelogs"] = new DictionaryBackedDirectory(
new Dictionary<string, IDirectory>(),
new string[]
{
@"L:machinelogslog1.log"
}
)
},
new Dictionary<string, Func<Stream>>()
{
[@"L:machinelogslog1.log"] = () => new MemoryStream() // TODO: populate the memory stream with data for the test
}
)
DoStuff(mockFileSystem);
}

这样做的优点:

  • 提高可重用性
    • 如果需要,可以实现远程文件系统
  • 使代码更易于测试
    • 拥有";单元可测试代码";,并且抽象可以是";嘲笑的";让你更接近那个黄金城市

更抽象地输出结果

您的代码不必绑定到Console.WriteLine()或特定的输出编码。

例如:

readonly struct Result
{
public readonly string Path;
public readonly string Line;
public Result(string path, string line)
{
Path = path;
Line = line;
}
}
IEnumerable<Result> DoStuff(IFileSystem fileSystem)
{
string SourcePath = @"L:machinelogs";
string filemask = "*.log";
string searchitem = @"cannot access server data";
IDirectory directory = fileSystem.OpenDirectory(SourcePath);
IEnumerable<IDirectory> dirs = directory.EnumerateDirectories();
dirs.Append(directory);
foreach (var dir in dirs)
{
var files = dir.EnumerateFiles(filemask);
foreach(var file in files)
{
using var stream = fileSystem.OpenFile(file);
using var reader = new StreamReader(stream);
while (reader.ReadLine() is {} line)
{
if(line.Contains(searchitem))
{
yield return new Result(file, line)
}
}
}
}
}
void Main()
{
IFileSystem fileSystem = new RealFileSystem();
foreach (var result in DoStuff(fileSystem))
{
Console.WriteLine(result.File + " : " + result.Line);
break; // Could easily change this to continue searching
}
}

看看这是如何将控制台交互从你的代码中移除的,使输出格式成为别人的问题,并让你的代码的使用者在你搜索成功后决定是否继续搜索?

这也将使您的代码离单元可测试性更近一步。如果不清楚原因,请随意询问。

注入参数

源路径、文件掩码和搜索项不必是硬编码的常量。

例如:

IEnumerable<Result> DoStuff(
IFileSystem fileSystem,
string sourcePath,
string fileMask,
string searchItem)
{
IDirectory directory = fileSystem.OpenDirectory(sourcePath);
IEnumerable<IDirectory> dirs = directory.EnumerateDirectories();
dirs.Append(directory);
foreach (var dir in dirs)
{
var files = dir.EnumerateFiles(fileMask);
foreach(var file in files)
{
using var stream = fileSystem.OpenFile(file);
using var reader = new StreamReader(stream);
while (reader.ReadLine() is {} line)
{
if(line.Contains(searchItem))
{
yield return new Result(file, line)
}
}
}
}
}
void Main()
{
IFileSystem fileSystem = new RealFileSystem();
foreach (var result in DoStuff(
fileSystem,
@"L:machinelogs",
"*.log",
@"cannot access server data"
))
{
Console.WriteLine(result.File + " : " + result.Line);
break;
}
}

看看这是如何使搜索其他东西成为可能的?

使用Path.Combine

这将删除对Windows的一个依赖项,即反斜杠路径分隔符。

void Main()
{
IFileSystem fileSystem = new RealFileSystem();
foreach (var result in DoStuff(
fileSystem,
Path.Combine("L:", "machinelogs"),
"*.log",
@"cannot access server data"
))
{
Console.WriteLine(result.File + " : " + result.Line);
break;
}
}

如果上面的代码都没有编译,我不会感到惊讶。这是即兴写的

最新更新