Parsing FtpWebRequest ListDirectoryDetails line

Eu preciso de alguma ajuda para analisar a resposta de ListDirectoryDetails em c #.

Eu só preciso dos seguintes campos.

  • Nome do arquivo / nome do diretório
  • Data Criada
  • e o tamanho do arquivo.

Aqui está o que algumas das linhas parecem quando executo ListDirectoryDetails :

 d--x--x--x 2 ftp ftp 4096 Mar 07 2002 bin -rw-r--r-- 1 ftp ftp 659450 Jun 15 05:07 TEST.TXT -rw-r--r-- 1 ftp ftp 101786380 Sep 08 2008 TEST03-05.TXT drwxrwxr-x 2 ftp ftp 4096 May 06 12:24 dropoff 

Desde já, obrigado.

Não tenho certeza se você ainda precisa disso, mas esta é a solução que eu criei:

 Regex regex = new Regex ( @"^([d-])([rwxt-]{3}){3}\s+\d{1,}\s+.*?(\d{1,})\s+(\w+\s+\d{1,2}\s+(?:\d{4})?)(\d{1,2}:\d{2})?\s+(.+?)\s?$", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace ); 

Grupos de correspondências:

  1. Tipo de object:
    • d: diretório
    • – : Arquivo
  2. Matriz [3] de permissions (rwx-)
  3. Tamanho do arquivo
  4. Data da última modificação
  5. Última hora modificada
  6. Nome do arquivo / diretório

Para esta listview específica, o código a seguir fará:

 FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://ftp.example.com/"); request.Credentials = new NetworkCredential("user", "password"); request.Method = WebRequestMethods.Ftp.ListDirectoryDetails; StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream()); string pattern = @"^([\w-]+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+" + @"(\w+\s+\d+\s+\d+|\w+\s+\d+\s+\d+:\d+)\s+(.+)$"; Regex regex = new Regex(pattern); IFormatProvider culture = CultureInfo.GetCultureInfo("en-us"); string[] hourMinFormats = new[] { "MMM dd HH:mm", "MMM dd H:mm", "MMM d HH:mm", "MMM d H:mm" }; string[] yearFormats = new[] { "MMM dd yyyy", "MMM d yyyy" }; while (!reader.EndOfStream) { string line = reader.ReadLine(); Match match = regex.Match(line); string permissions = match.Groups[1].Value; int inode = int.Parse(match.Groups[2].Value, culture); string owner = match.Groups[3].Value; string group = match.Groups[4].Value; long size = long.Parse(match.Groups[5].Value, culture); DateTime modified; string s = Regex.Replace(match.Groups[6].Value, @"\s+", " "); if (s.IndexOf(':') >= 0) { modified = DateTime.ParseExact(s, hourMinFormats, culture, DateTimeStyles.None); } else { modified = DateTime.ParseExact(s, yearFormats, culture, DateTimeStyles.None); } string name = match.Groups[7].Value; Console.WriteLine( "{0,-16} permissions = {1} size = {2, 9} modified = {3}", name, permissions, size, modified.ToString("yyyy-MM-dd HH:mm")); } 

Você receberá (a partir do ano de 2016):

 bin permissions = d--x--x--x size = 4096 modified = 2002-03-07 00:00 TEST.TXT permissions = -rw-r--r-- size = 659450 modified = 2016-06-15 05:07 TEST03-05.TXT permissions = -rw-r--r-- size = 101786380 modified = 2008-09-08 00:00 dropoff permissions = drwxrwxr-x size = 4096 modified = 2016-05-06 12:24 

Mas, na verdade, tentar analisar a listview retornada pelo ListDirectoryDetails não é o caminho certo a seguir.

Você deseja usar um cliente FTP que suporte o comando MLSD moderno que retorna uma listview de diretório em um formato legível por máquina especificado no RFC 3659 . Analisando o formato legível pelo homem, retornado pelo antigo comando LIST (usado internamente pelo método ListDirectoryDetails ), deve ser usado como a última opção de recurso, quando se fala de servidores FTP obsoletos, que não suportam o comando MLSD (como o Microsoft Servidor FTP do IIS).

Muitos servidores usam um formato diferente para a resposta do comando LIST . Particularmente o IIS pode usar o formato DOS. Veja a class C # para analisar a resposta do WebRequestMethods.Ftp.ListDirectoryDetails FTP .


Por exemplo, com o assembly WinSCP .NET , você pode usar seus methods Session.EnumerateRemoteFiles ou Session.EnumerateRemoteFiles .

Eles usam internamente o comando MLSD , mas podem retornar ao comando LIST e oferecer suporte a dezenas de diferentes formatos de listview legíveis por humanos.

A listview retornada é apresentada como uma coleção de instâncias do RemoteFileInfo com propriedades como:

  • Name
  • LastWriteTime (com fuso horário correto)
  • Length
  • FilePermissions (analisadas em direitos individuais)
  • Group
  • Owner
  • IsDirectory
  • IsParentDirectory
  • IsThisDirectory

(Eu sou o autor do WinSCP)


A maioria das bibliotecas de terceiros fará o mesmo. Usando a class FtpWebRequest não é confiável para essa finalidade. Infelizmente, não há outro cliente FTP interno na estrutura .NET.

Este é o meu algoritmo para obter o nome do arquivo / dir, data de criação, atributo (arquivo / dir), tamanho. Espero que isto ajude…

  FtpWebRequest _fwr = FtpWebRequest.Create(uri) as FtpWebRequest _fwr.Credentials = cred; _fwr.UseBinary = true; _fwr.UsePassive = true; _fwr.KeepAlive = true; _fwr.Method = WebRequestMethods.Ftp.ListDirectoryDetails; StreamReader _sr = new StreamReader(_fwr.GetResponse().GetResponseStream()); List _dirlist = new List(); List _attlist = new List(); List _datelist = new List(); List _szlist = new List(); while (!_sr.EndOfStream) { string[] buf = _sr.ReadLine().Split(' '); //string Att, Dir; int numcnt = 0, offset = 4; ; long sz = 0; for (int i = 0; i < buf.Length; i++) { //Count the number value markers, first before the ftp markers and second //the file size. if (long.TryParse(buf[i], out sz)) numcnt++; if (numcnt == 2) { //Get the attribute string cbuf = "", dbuf = "", abuf = ""; if (buf[0][0] == 'd') abuf = "Dir"; else abuf = "File"; //Get the Date if (!buf[i+3].Contains(':')) offset++; for (int j = i + 1; j < i + offset; j++) { dbuf += buf[j]; if (j < buf.Length - 1) dbuf += " "; } //Get the File/Dir name for (int j = i + offset; j < buf.Length; j++) { cbuf += buf[j]; if (j < buf.Length - 1) cbuf += " "; } //Store to a list. _dirlist.Add(cbuf); _attlist.Add(abuf); _datelist.Add(dbuf); _szlist.Add(sz); offset = 0; break; } } } 

Com base na ideia de regex de Ryan Conrad , este é o meu código final de leitura:

 protected static Regex m_FtpListingRegex = new Regex(@"^([d-])((?:[rwxt-]{3}){3})\s+(\d{1,})\s+(\w+)?\s+(\w+)?\s+(\d{1,})\s+(\w+)\s+(\d{1,2})\s+(\d{4})?(\d{1,2}:\d{2})?\s+(.+?)\s?$", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); protected static readonly String Timeformat = "MMM dd yyyy HH:mm"; ///  /// Handles file info given in the form of a string in standard unix ls output format. ///  /// The file listing string. /// A list of FtpFileInfo objects public static List GetFilesListFromFtpListingUnix(String filesListing) { List files = new List(); MatchCollection matches = m_FtpListingRegex.Matches(filesListing); if (matches.Count == 0 && filesListing.Trim('\r','\n','\t',' ').Length != 0) return null; // parse error. Could throw some kind of exception here too. foreach (Match match in matches) { FtpFileInfo fileInfo = new FtpFileInfo(); Char dirchar = match.Groups[1].Value.ToLowerInvariant()[0]; fileInfo.IsDirectory = dirchar == 'd'; fileInfo.Permissions = match.Groups[2].Value.ToCharArray(); // No clue what "inodes" actually means... Int32 inodes; fileInfo.NrOfInodes = Int32.TryParse(match.Groups[3].Value, out inodes) ? inodes : 1; fileInfo.User = match.Groups[4].Success ? match.Groups[4].Value : null; fileInfo.Group = match.Groups[5].Success ? match.Groups[5].Value : null; Int64 fileSize; Int64.TryParse(match.Groups[6].Value, out fileSize); fileInfo.FileSize = fileSize; String month = match.Groups[7].Value; String day = match.Groups[8].Value.PadLeft(2, '0'); String year = match.Groups[9].Success ? match.Groups[9].Value : DateTime.Now.Year.ToString(CultureInfo.InvariantCulture); String time = match.Groups[10].Success ? match.Groups[10].Value.PadLeft(5, '0') : "00:00"; String timeString = month + " " + day + " " + year + " " + time; DateTime lastModifiedDate; if (!DateTime.TryParseExact(timeString, Timeformat, CultureInfo.InvariantCulture, DateTimeStyles.None, out lastModifiedDate)) lastModifiedDate = DateTime.MinValue; fileInfo.LastModifiedDate = lastModifiedDate; fileInfo.FileName = match.Groups[11].Value; files.Add(fileInfo); } return files; } 

E a class FtpFileInfo que está cheia:

 public class FtpFileInfo { public Boolean IsDirectory { get; set; } public Char[] Permissions { get; set; } public Int32 NrOfInodes { get; set; } public String User { get; set; } public String Group { get; set; } public Int64 FileSize { get; set; } public DateTime LastModifiedDate { get; set; } public String FileName { get; set; } }