【问题标题】:SSH.NET SFTP Get a list of directories and files recursivelySSH.NET SFTP 递归获取目录和文件列表
【发布时间】:2019-08-17 04:18:57
【问题描述】:

我正在使用 Renci.SshNet 库通过 SFTP 递归获取文件和目录列表。我可以连接 SFTP 站点,但我不确定如何在 C# 中递归地获取目录和文件列表。我还没有找到任何有用的例子。

有人试过吗?如果是这样,您能否发布一些有关如何递归获取这些文件和文件夹的示例代码。

谢谢,
事实

【问题讨论】:

    标签: c# .net sftp ssh.net


    【解决方案1】:

    这个库有一些使这个递归列表变得棘手的怪癖,因为ChangeDirectoryListDirectory 之间的交互不像你所期望的那样工作。

    以下内容列出 /home 目录中的文件,而是列出 /(根)目录中的文件:

    sftp.ChangeDirectory("home");
    sftp.ListDirectory("").Select (s => s.FullName);
    

    以下工作并返回 SftpPathNotFoundException:

    sftp.ChangeDirectory("home");
    sftp.ListDirectory("home").Select (s => s.FullName);
    

    下面是列出/home目录下文件的正确方法

    sftp.ChangeDirectory("/");
    sftp.ListDirectory("home").Select (s => s.FullName);
    

    如果你问我,这太疯狂了。使用ChangeDirectory 方法设置默认目录对ListDirectory 方法没有影响,除非您在该方法的参数中指定文件夹。似乎应该为此编写一个错误。

    因此,当您编写递归函数时,您必须设置一次默认目录,然后在遍历文件夹时更改 ListDirectory 调用中的目录。该清单返回一个可枚举的 SftpFiles。然后可以单独检查这些 IsDirectory == true。请注意,该列表还返回 ... 条目(它们是目录)。如果你想避免无限循环,你会想跳过这些。 :-)

    2018 年 2 月 23 日编辑

    我正在查看我的一些旧答案,并想为上述答案道歉并提供以下工作代码。请注意,此示例不需要ChangeDirectory,因为它使用Fullname 作为ListDirectory

    void Main()
    {
        using (var client = new Renci.SshNet.SftpClient("sftp.host.com", "user", "password"))
        {
            var files = new List<String>();
            client.Connect();
            ListDirectory(client, ".", ref files);
            client.Disconnect();
            files.Dump();
        }
    }
    
    void ListDirectory(SftpClient client, String dirName, ref List<String> files)
    {
        foreach (var entry in client.ListDirectory(dirName))
        {
    
            if (entry.IsDirectory)
            {
                ListDirectory(client, entry.FullName, ref files);
            }
            else
            {
                files.Add(entry.FullName);
            }
        }
    }
    

    【讨论】:

    • sftp.ListDirectory("") 不起作用,但 sftp.ListDirectory(".") 起作用 - 记住,'.'表示“当前目录”。但是,它似乎不支持主文件夹的“~”快捷方式。
    • 感谢您的解释,是否有一个完整的示例如何从给定的根文件夹开始实际获取所有目录和子目录的列表或树?我想知道为什么 ssh.net 没有在 ListDirectory 方法本身中提供布尔递归。这些天有没有更好的.net 解决方案? IE。一个更好的免费库,已经实现了这些基本任务?但是,一个工作示例会非常有帮助,谢谢!
    • 非常适合我。但是我将它从 List 更改为 sftp 文件类型,因此无需使用 files.Add(entry.FullName) 它只需要 files.Add(entry) 并且您将拥有每个文件的所有信息。我使用它是因为我的 FTP 非常慢,我认为这可以优化一点,因此下载时可能不必再次获取文件信息。
    【解决方案2】:

    试试这个:

    var filePaths = client.ListDirectory(client.WorkingDirectory);
    

    【讨论】:

      【解决方案3】:

      我使用递归实现了这一点。像这样创建了一个类 TransportResponse

       public class TransportResponse
      {
          public string directoryName { get; set; }
          public string fileName { get; set; }
          public DateTime fileTimeStamp { get; set; }
          public MemoryStream fileStream { get; set; }
          public List<TransportResponse> lstTransportResponse { get; set; }
      }
      

      我创建了一个 TransportResponse 类的列表。如果 directoryName 不为空,它将包含同一类的列表,该类将在该目录中的文件作为 MemoryStream (这可以根据您的用例进行更改)

      List<TransportResponse> lstResponse = new List<TransportResponse>();
      using (var client = new SftpClient(connectionInfo))
        {
                try
                {
                          Console.WriteLine("Connecting to " + connectionInfo.Host + " ...");
                          client.Connect();
                          Console.WriteLine("Connected to " + connectionInfo.Host + " ...");
                 }
                 catch (Exception ex)
                 {
                          Console.WriteLine("Could not connect to "+ connectionInfo.Host +" server. Exception Details: " + ex.Message);
                 }
                 if (client.IsConnected)
                 {
                          var files = client.ListDirectory(transport.SourceFolder);
                          lstResponse = downloadFilesInDirectory(files, client);
                          client.Disconnect();
                  }
                  else
                  {
                          Console.WriteLine("Could not download files from "+ transport.TransportIdentifier +" because client was not connected.");
                   }
         }
      
      
      
      private static List<TransportResponse> downloadFilesInDirectory(IEnumerable<SftpFile> files, SftpClient client)
          {
              List<TransportResponse> lstResponse = new List<TransportResponse>();
              foreach (var file in files)
              {
                  if (!file.IsDirectory)
                  {
                      if (file.Name != "." && file.Name != "..")
                      {
                          if (!TransportDAL.checkFileExists(file.Name, file.LastWriteTime))
                          {
                              using (MemoryStream fs = new MemoryStream())
                              {
                                  try
                                  {
                                      Console.WriteLine("Reading " + file.Name + "...");
                                      client.DownloadFile(file.FullName, fs);
                                      fs.Seek(0, SeekOrigin.Begin);
                                      lstResponse.Add(new TransportResponse { fileName = file.Name, fileTimeStamp = file.LastWriteTime, fileStream = new MemoryStream(fs.GetBuffer()) });
                                  }
                                  catch(Exception ex)
                                  {
                                      Console.WriteLine("Error reading File. Exception Details: " + ex.Message);
                                  }
                              }
                          }
                          else
                          {
                              Console.WriteLine("File was downloaded previously");
                          }
                      }
                  }
                  else
                  {
                      if (file.Name != "." && file.Name != "..")
                      {
                          lstResponse.Add(new TransportResponse { directoryName = file.Name,lstTransportResponse = downloadFilesInDirectory(client.ListDirectory(file.Name), client) });
                      }                
                  }
              }
      
              return lstResponse;
          }
      

      希望这会有所帮助。谢谢

      【讨论】:

        【解决方案4】:

        这是一个完整的课程。它是 .NET Core 2.1 Http 触发函数应用 (v2)

        我想删除所有以“.”开头的目录,因为我的 sftp 服务器有 .cache 文件夹和带有键的 .ssh 文件夹。也不想处理像“。”这样的文件夹名称。或'..'

        我最终要做的是将 SftpFile 投影到我使用的类型并将其返回给调用者(在这种情况下,它将是一个逻辑应用程序)。然后我将该对象传递到一个存储过程中,并使用 OPENJSON 来构建我的监控表。这基本上是创建我的 SFTP 处理队列的第一步,它将把文件从我的 SFTP 文件夹中移到我的数据湖中(现在是 blob,直到我想出更好的东西)。

        我使用 .WorkingDirectory 的原因是因为我创建了一个主目录为“/home”的用户。这让我可以遍历我的所有用户文件夹。我的应用程序不需要有一个特定的文件夹作为起点,可以说是用户“root”。

        using Microsoft.AspNetCore.Http;
        using Microsoft.AspNetCore.Mvc;
        using Microsoft.Azure.WebJobs;
        using Microsoft.Azure.WebJobs.Extensions.Http;
        using Microsoft.Extensions.Logging;
        using Renci.SshNet;
        using Renci.SshNet.Sftp;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;
        
        namespace SFTPFileMonitor
        {
            public class GetListOfFiles
            {
                [FunctionName("GetListOfFiles")]
                public async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req, ILogger log)
                {
                    log.LogInformation("C# HTTP trigger function processed a request.");
        
                    List<SftpFile> zFiles;
                    int fileCount;
                    decimal totalSizeGB;
                    long totalSizeBytes;
        
                    using (SftpClient sftpClient = new SftpClient("hostname", "username", "password"))
                    {
                        sftpClient.Connect();
                        zFiles = await GetFiles(sftpClient, sftpClient.WorkingDirectory, new List<SftpFile>());
                        fileCount = zFiles.Count;
                        totalSizeBytes = zFiles.Sum(l => l.Length);
                        totalSizeGB = BytesToGB(totalSizeBytes);
                    }
        
                    return new OkObjectResult(new { fileCount, totalSizeBytes, totalSizeGB, zFiles });
                }
                private async Task<List<SftpFile>> GetFiles(SftpClient sftpClient, string directory, List<SftpFile> files)
                {
                    foreach (SftpFile sftpFile in sftpClient.ListDirectory(directory))
                    {
                        if (sftpFile.Name.StartsWith('.')) { continue; }
        
                        if (sftpFile.IsDirectory)
                        {
                            await GetFiles(sftpClient, sftpFile.FullName, files);
                        }
                        else
                        {
                            files.Add(sftpFile);
                        }
                    }
                    return files;
                }
                private decimal BytesToGB(long bytes)
                {
                    return Convert.ToDecimal(bytes) / 1024 / 1024 / 1024;
                }
            }
        }
        

        【讨论】:

        • 就我一个,但由于BytesToGB只用在RunAsync(...)函数中,所以可以做成本地函数...
        • @AdrianHum 或辅助类中的扩展方法。
        • 我实际上把它作为一个扩展方法,调用win32 long to string函数,返回文件大小,就像windows一样......到三位数...... 1.23GB 12.3GB 0.81kb等......它提供了与 windows 的一致性。
        【解决方案5】:

        @卡洛斯·博斯

        这个库有一些使这个递归列表变得棘手的怪癖 因为 ChangeDirectory 和 ListDirectory 之间的交互 不像你预期的那样工作。

        正确

        当ChangeDirectory()参数为“.”时效果很好

        如果你这样做了

        SftpClient sftp ...;
        sftp.ChangeDirectory("some_folder");
        //get file list
        List<SftpFile> fileList = sftp.ListDirectory("some_folder").ToList();
        

        然后有一个断言,因为 ListDirectory() 调用需要 "some_folder/some_folder"

        我使用的解决方法是在远程上传/重命名为“some_folder”之前保存并恢复当前目录,并且您需要在操作之前列出该文件夹(例如查看文件已经存在)

        string working_directory = sftp.WorkingDirectory;
        sftp.ChangeDirectory("some_folder");
        sftp.RenameFile("name", "new_name");
        sftp.ChangeDirectory(working_directory);
        

        检查文件是否存在,这个调用就足够了

        sftp.Exists(path)
        

        或者如果您想添加一些其他条件,例如是否区分大小写

         public FileExistence checkFileExists(string folder, string fileName)
            {
              //get file list
              List<SftpFile> fileList = sftp.ListDirectory(folder).ToList();
        
              if (fileList == null)
              {
                return FileExistence.UNCONFIRMED;
              }
        
              foreach (SftpFile f in fileList)
              {
                Console.WriteLine(f.ToString());
                //a not case sensitive comparison is made
                if (f.IsRegularFile && f.Name.ToLower() == fileName.ToLower())
                {
                  return FileExistence.EXISTS;
                }
              }
        
              //if not found in traversal , it does not exist
              return FileExistence.DOES_NOT_EXIST;
            }
        

        FileExistence 在哪里

        public enum FileExistence
            {
              EXISTS,
              DOES_NOT_EXIST,
              UNCONFIRMED
            };
        

        【讨论】:

          猜你喜欢
          • 2018-07-13
          • 1970-01-01
          • 2011-04-26
          • 2016-07-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-07
          • 1970-01-01
          相关资源
          最近更新 更多