web-dev-qa-db-ja.com

SSH.NET SFTPディレクトリとファイルのリストを再帰的に取得する

Renci.SshNetライブラリを使用して、SFTPを使用してファイルとディレクトリのリストを再帰的に取得しています。 SFTPサイトに接続できますが、C#でディレクトリとファイルのリストを再帰的に取得する方法がわかりません。役に立つ例は見つかりませんでした。

誰かがこれを試しましたか?もしそうなら、これらのファイルとフォルダを再帰的に取得する方法に関するサンプルコードを投稿できますか?.

おかげで、
プラヴ

12
user1462119

このライブラリには、ChangeDirectoryListDirectory間の相互作用が期待どおりに機能しないため、この再帰的なリストを扱いにくくするいくつかの癖があります。

次の例ではnotが/ homeディレクトリのファイルを一覧表示しますが、/(root)ディレクトリのファイルを一覧表示します。

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

以下はnotで機能し、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を個別に確認できます。リストは.および..エントリ(ディレクトリ)も返すことに注意してください。無限ループを回避したい場合は、これらをスキップしてください。 :-)

編集2/23/2018

私は私の古い答えのいくつかをレビューしていて、上記の答えを謝罪し、次の作業コードを提供したいと思います。この例では、ChangeDirectoryFullnameを使用しているため、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);
        }
    }
}
16
Carlo Bos

これを試して:

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

私は再帰を使用してこれを達成しました。このようなクラス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がnullでない場合は、そのディレクトリ内のファイルを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;
    }

お役に立てれば。ありがとう

1

@カルロスボス

ChangeLibraryと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
    };
0
Pedro Vicente

ここにフルクラスがあります。 .NET Core 2.1 Httpトリガー関数アプリ(v2)です。

'。'で始まるディレクトリをすべて削除したいのですが、私のsftpサーバーに.cacheフォルダーと.sshフォルダーがあり、キーがあります。また、「。」のようなフォルダ名を処理する必要はありませんでした。または「..」

最終的には、SftpFileを操作する型に射影し、それを呼び出し元に返します(この場合は、ロジックアプリになります)。次に、そのオブジェクトをストアドプロシージャに渡し、OPENJSONを使用して監視テーブルを作成します。これは基本的に、ファイルをSFTPフォルダーからData Lakeに移動するSFTP処理キューを作成する最初のステップです(私が推測するより良いものが思いつくまで、とりあえずはブロブ)。

.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;
        }
    }
}
0