Скопируйте все содержимое каталога в C #

396

Я хочу скопировать все содержимое каталога из одного места в другое в С#.

Кажется, что нет способа сделать это, используя классы System.IO без большой рекурсии.

В VB существует метод, который мы можем использовать, если добавить ссылку на Microsoft.VisualBasic:

new Microsoft.VisualBasic.Devices.Computer().
    FileSystem.CopyDirectory( sourceFolder, outputFolder );

Это кажется довольно уродливым взломом. Есть ли лучший способ?

  • 88
    Я бы сказал, что, глядя на альтернативы, опубликованные ниже, путь VB выглядит не так безобразно.
Показать ещё 18 комментариев
Теги:
copy

17 ответов

383
Лучший ответ

Намного легче

//Now Create all of the directories
foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", 
    SearchOption.AllDirectories))
    Directory.CreateDirectory(dirPath.Replace(SourcePath, DestinationPath));

//Copy all the files & Replaces any files with the same name
foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", 
    SearchOption.AllDirectories))
    File.Copy(newPath, newPath.Replace(SourcePath, DestinationPath), true);
  • 0
    Идеальная идея - я не знаю, почему я никогда не думал об использовании SearchOption.AllDirectories . Я бы, вероятно, использовал метод SubString а не Replace , но это всего лишь стиль кода.
  • 23
    Это действительно хороший кусок кода, но это не тот код, который можно использовать где угодно. Разработчики должны быть осторожны, потому что dirPath.Replace может привести к нежелательным последствиям. Просто предупреждение людям, которые любят копировать и вставлять по сети. Код, размещенный @jaysponsored, безопаснее, потому что он не использует string.Replace, но я уверен, что он также имеет свои угловые случаи.
Показать ещё 15 комментариев
159

Хм, я думаю, что неправильно понимаю вопрос, но я буду рисковать. Что случилось со следующим простым способом?

public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) {
    foreach (DirectoryInfo dir in source.GetDirectories())
        CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
    foreach (FileInfo file in source.GetFiles())
        file.CopyTo(Path.Combine(target.FullName, file.Name));
}

EDIT. Поскольку эта публикация собрала внушительное количество downvotes для такого простого ответа на столь же простой вопрос, позвольте мне добавить объяснение. Пожалуйста, прочитайте это перед downvoting.

Прежде всего, этот код не является заменой на код в вопросе. Это только для иллюстрации.

Microsoft.VisualBasic.Devices.Computer.FileSystem.CopyDirectory выполняет некоторые дополнительные проверки правильности (например, являются ли источник и цель действительными директориями, является ли источник родителем объекта и т.д.), которые отсутствуют в этом ответе. Вероятно, этот код также оптимизирован.

Тем не менее, код работает хорошо. Он (почти идентично) использовался в зрелом программном обеспечении в течение многих лет. Помимо неотъемлемой изменчивости, присутствующей во всех обработках ввода-вывода (например, что происходит, если пользователь вручную отключает USB-накопитель, пока ваш код пишет к нему?), Нет известных проблем.

В частности, Id хотел бы отметить, что использование рекурсии здесь абсолютно не проблема. Ни в теории (концептуально, это самое элегантное решение), ни на практике: этот код не будет переполнять стек. Стек достаточно велик, чтобы обрабатывать даже глубоко вложенные иерархии файлов. Задолго до того, как пространство стека становится проблемой, ограничение длины пути к папке запускается.

Обратите внимание, что злоумышленник может нарушить это предположение, используя глубоко вложенные каталоги по одной букве. Я этого не делал. Но просто для того, чтобы проиллюстрировать суть: для того, чтобы этот переполнение кода на типичном компьютере, каталоги должны были быть вложены несколько тысяч раз. Это просто не реалистичный сценарий.

  • 0
    Теперь, 'кроме рекурсивного вызова. Почему мы должны сделать это в C #?
  • 5
    Это рекурсия головы. Это может стать причиной переполнения стека, если каталоги вложены достаточно глубоко.
Показать ещё 10 комментариев
83

Скопировано из MSDN:

using System;
using System.IO;

class CopyDir
{
    public static void Copy(string sourceDirectory, string targetDirectory)
    {
        DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);
        DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);

        CopyAll(diSource, diTarget);
    }

    public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
    {
        Directory.CreateDirectory(target.FullName);

        // Copy each file into the new directory.
        foreach (FileInfo fi in source.GetFiles())
        {
            Console.WriteLine(@"Copying {0}\{1}", target.FullName, fi.Name);
            fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
        }

        // Copy each subdirectory using recursion.
        foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
        {
            DirectoryInfo nextTargetSubDir =
                target.CreateSubdirectory(diSourceSubDir.Name);
            CopyAll(diSourceSubDir, nextTargetSubDir);
        }
    }

    public static void Main()
    {
        string sourceDirectory = @"c:\sourceDirectory";
        string targetDirectory = @"c:\targetDirectory";

        Copy(sourceDirectory, targetDirectory);
    }

    // Output will vary based on the contents of the source directory.
}
  • 5
    Нет причин проверять, существует ли каталог, просто вызовите Directoty.CreateDirectory, который ничего не сделает, если каталог уже существует.
  • 1
    Для тех, кто хочет иметь дело с путями длиной более 256 символов, вы можете использовать пакет Nuget с именем ZetaLongPaths
Показать ещё 1 комментарий
44

Попробуйте следующее:

Process proc = new Process();
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.FileName = @"C:\WINDOWS\system32\xcopy.exe";
proc.StartInfo.Arguments = @"C:\source C:\destination /E /I";
proc.Start();

Ваши аргументы xcopy могут отличаться, но вы получаете идею.

  • 0
    что означает / E / I? Переписать?
  • 3
    / E говорит ему скопировать все подкаталоги (даже пустые). / Я говорю, что если место назначения не существует, создайте каталог с таким именем.
Показать ещё 10 комментариев
30

Или, если вы хотите идти сложным способом, добавьте ссылку на свой проект для Microsoft.VisualBasic, а затем используйте следующее:

Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(fromDirectory, toDirectory);

Однако использование одной из рекурсивных функций - лучший способ, так как ему не придется загружать dll VB.

  • 1
    Это не сильно отличается от того, как я это сделал - вам все равно нужно загрузить материал обратной совместимости VB, чтобы иметь возможность это сделать.
  • 9
    Дорого ли загружается сборка VB? Варианты VB намного более элегантны, чем версии C #.
Показать ещё 4 комментария
20

Этот сайт всегда помогал мне, и теперь самое время помочь другим тем, что я знаю.

Я жду, что мой код ниже будет полезен для кого-то.

string source_dir = @"E:\";
string destination_dir = @"C:\";

// substring is to remove destination_dir absolute path (E:\).

// Create subdirectory structure in destination    
    foreach (string dir in System.IO.Directory.GetDirectories(source_dir, "*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.Directory.CreateDirectory(destination_dir + dir.Substring(source_dir.Length));
        // Example:
        //     > C:\sources (and not C:\E:\sources)
    }

    foreach (string file_name in System.IO.Directory.GetFiles(source_dir, "*.*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.File.Copy(file_name, destination_dir + file_name.Substring(source_dir.Length));
    }
  • 0
    Помните о конце обратной косой черты
  • 13
    Path.Combine() , используйте Path.Combine() . Никогда не используйте конкатенацию строк для объединения путей к файлам.
Показать ещё 8 комментариев
12

Скопировать папку рекурсивно без рекурсии, чтобы избежать.

public static void CopyDirectory(string source, string target)
{
    var stack = new Stack<Folders>();
    stack.Push(new Folders(source, target));

    while (stack.Count > 0)
    {
        var folders = stack.Pop();
        Directory.CreateDirectory(folders.Target);
        foreach (var file in Directory.GetFiles(folders.Source, "*.*"))
        {
            File.Copy(file, Path.Combine(folders.Target, Path.GetFileName(file)));
        }

        foreach (var folder in Directory.GetDirectories(folders.Source))
        {
            stack.Push(new Folders(folder, Path.Combine(folders.Target, Path.GetFileName(folder))));
        }
    }
}

public class Folders
{
    public string Source { get; private set; }
    public string Target { get; private set; }

    public Folders(string source, string target)
    {
        Source = source;
        Target = target;
    }
}
  • 0
    полезный шаблон без рекурсии :)
  • 1
    Трудно себе представить, как взорвать стек, прежде чем светится предел пути
5

Вот класс утилиты, который я использовал для задач ввода-вывода, подобных этому.

using System;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
    public class ShellFileOperation
    {
        private static String StringArrayToMultiString(String[] stringArray)
        {
            String multiString = "";

            if (stringArray == null)
                return "";

            for (int i=0 ; i<stringArray.Length ; i++)
                multiString += stringArray[i] + '\0';

            multiString += '\0';

            return multiString;
        }

        public static bool Copy(string source, string dest)
        {
            return Copy(new String[] { source }, new String[] { dest });
        }

        public static bool Copy(String[] source, String[] dest)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_COPY;

            String multiSource = StringArrayToMultiString(source);
            String multiDest = StringArrayToMultiString(dest);
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }

        public static bool Move(string source, string dest)
        {
            return Move(new String[] { source }, new String[] { dest });
        }

        public static bool Delete(string file)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_DELETE;

            String multiSource = StringArrayToMultiString(new string[] { file });
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo =  IntPtr.Zero;

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_SILENT | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION | (ushort)Win32.ShellFileOperationFlags.FOF_NOERRORUI | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMMKDIR;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }

        public static bool Move(String[] source, String[] dest)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_MOVE;

            String multiSource = StringArrayToMultiString(source);
            String multiDest = StringArrayToMultiString(dest);
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }
    }
}
  • 0
    Обратите внимание, что Microsoft использует SHFileOperation для Microsoft.VisualBasic.
2

tboswell заменить версию Proof (которая устойчива к повторению шаблона в пути к файлу)

public static void copyAll(string SourcePath , string DestinationPath )
{
   //Now Create all of the directories
   foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
      Directory.CreateDirectory(Path.Combine(DestinationPath ,dirPath.Remove(0, SourcePath.Length ))  );

   //Copy all the files & Replaces any files with the same name
   foreach (string newPath in Directory.GetFiles(SourcePath, "*.*",  SearchOption.AllDirectories))
      File.Copy(newPath, Path.Combine(DestinationPath , newPath.Remove(0, SourcePath.Length)) , true);
    }
  • 2
    Path.Combine() , используйте Path.Combine() . Никогда не используйте конкатенацию строк для объединения путей к файлам.
2

Если вам нравится популярный ответ Konrad, но вы хотите, чтобы source был папкой под target, вместо того, чтобы помещать его в папку target, вот код для этого. Он возвращает вновь созданный DirectoryInfo, который удобен:

public static DirectoryInfo CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
  var newDirectoryInfo = target.CreateSubdirectory(source.Name);
  foreach (var fileInfo in source.GetFiles())
    fileInfo.CopyTo(Path.Combine(newDirectoryInfo.FullName, fileInfo.Name));

  foreach (var childDirectoryInfo in source.GetDirectories())
    CopyFilesRecursively(childDirectoryInfo, newDirectoryInfo);

  return newDirectoryInfo;
}
1

Вы всегда можете использовать this, взятый с веб-сайта Microsoft.

static void Main()
{
    // Copy from the current directory, include subdirectories.
    DirectoryCopy(".", @".\temp", true);
}

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
    // Get the subdirectories for the specified directory.
    DirectoryInfo dir = new DirectoryInfo(sourceDirName);

    if (!dir.Exists)
    {
        throw new DirectoryNotFoundException(
            "Source directory does not exist or could not be found: "
            + sourceDirName);
    }

    DirectoryInfo[] dirs = dir.GetDirectories();
    // If the destination directory doesn't exist, create it.
    if (!Directory.Exists(destDirName))
    {
        Directory.CreateDirectory(destDirName);
    }

    // Get the files in the directory and copy them to the new location.
    FileInfo[] files = dir.GetFiles();
    foreach (FileInfo file in files)
    {
        string temppath = Path.Combine(destDirName, file.Name);
        file.CopyTo(temppath, false);
    }

    // If copying subdirectories, copy them and their contents to new location.
    if (copySubDirs)
    {
        foreach (DirectoryInfo subdir in dirs)
        {
            string temppath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, temppath, copySubDirs);
        }
    }
}
  • 0
    Это здорово - имейте в виду строку file.CopyTo(temppath, false); говорит «скопируйте этот файл в это место, только если он не существует», что в большинстве случаев не то, что мы хотим. Но я могу понять, почему это по умолчанию. Возможно, добавьте флаг в метод для перезаписи файлов.
1

Вот метод расширения для DirectoryInfo a la FileInfo.CopyTo (обратите внимание на параметр overwrite):

public static DirectoryInfo CopyTo(this DirectoryInfo sourceDir, string destinationPath, bool overwrite = false)
{
    var sourcePath = sourceDir.FullName;

    var destination = new DirectoryInfo(destinationPath);

    destination.Create();

    foreach (var sourceSubDirPath in Directory.EnumerateDirectories(sourcePath, "*", SearchOption.AllDirectories))
        Directory.CreateDirectory(sourceSubDirPath.Replace(sourcePath, destinationPath));

    foreach (var file in Directory.EnumerateFiles(sourcePath, "*", SearchOption.AllDirectories))
        File.Copy(file, file.Replace(sourcePath, destinationPath), overwrite);

    return destination;
}
1

Это мой код, надеюсь, эта помощь

    private void KCOPY(string source, string destination)
    {
        if (IsFile(source))
        {
            string target = Path.Combine(destination, Path.GetFileName(source));
            File.Copy(source, target, true);
        }
        else
        {
            string fileName = Path.GetFileName(source);
            string target = System.IO.Path.Combine(destination, fileName);
            if (!System.IO.Directory.Exists(target))
            {
                System.IO.Directory.CreateDirectory(target);
            }

            List<string> files = GetAllFileAndFolder(source);

            foreach (string file in files)
            {
                KCOPY(file, target);
            }
        }
    }

    private List<string> GetAllFileAndFolder(string path)
    {
        List<string> allFile = new List<string>();
        foreach (string dir in Directory.GetDirectories(path))
        {
            allFile.Add(dir);
        }
        foreach (string file in Directory.GetFiles(path))
        {
            allFile.Add(file);
        }

        return allFile;
    }
    private bool IsFile(string path)
    {
        if ((File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory)
        {
            return false;
        }
        return true;
    }
  • 0
    Смотрите выбранный ответ, используя флаг SearchOption при поиске папок и файлов, он делает это в 4 строки кода. Также проверьте расширение .HasFlag теперь на enums.
1

Небольшое улучшение ответа d4nt, поскольку вы, вероятно, хотите проверить наличие ошибок и не должны изменять пути xcopy, если вы работаете на сервере и машине разработки:

public void CopyFolder(string source, string destination)
{
    string xcopyPath = Environment.GetEnvironmentVariable("WINDIR") + @"\System32\xcopy.exe";
    ProcessStartInfo info = new ProcessStartInfo(xcopyPath);
    info.UseShellExecute = false;
    info.RedirectStandardOutput = true;
    info.Arguments = string.Format("\"{0}\" \"{1}\" /E /I", source, destination);

    Process process = Process.Start(info);
    process.WaitForExit();
    string result = process.StandardOutput.ReadToEnd();

    if (process.ExitCode != 0)
    {
        // Or your own custom exception, or just return false if you prefer.
        throw new InvalidOperationException(string.Format("Failed to copy {0} to {1}: {2}", source, destination, result));
    }
}
1

Извините за предыдущий код, у него все еще были ошибки:( (стал жертвой самой быстрой проблемы с пистолетом). Здесь он протестирован и работает. Ключ - это SearchOption.AllDirectories, который устраняет необходимость в явной рекурсии.

string path = "C:\\a";
string[] dirs = Directory.GetDirectories(path, "*.*", SearchOption.AllDirectories);
string newpath = "C:\\x";
try
{
    Directory.CreateDirectory(newpath);
}
catch (IOException ex)
{
    Console.WriteLine(ex.Message);
}
for (int j = 0; j < dirs.Length; j++)
{
    try
    {
        Directory.CreateDirectory(dirs[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

string[] files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
for (int j = 0; j < files.Length; j++)            
{
    try
    {
        File.Copy(files[j], files[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}
0

Лучше, чем любой код (метод расширения для DirectoryInfo с рекурсией)

public static bool CopyTo(this DirectoryInfo source, string destination)
    {
        try
        {
            foreach (string dirPath in Directory.GetDirectories(source.FullName))
            {
                var newDirPath = dirPath.Replace(source.FullName, destination);
                Directory.CreateDirectory(newDirPath);
                new DirectoryInfo(dirPath).CopyTo(newDirPath);
            }
            //Copy all the files & Replaces any files with the same name
            foreach (string filePath in Directory.GetFiles(source.FullName))
            {
                File.Copy(filePath, filePath.Replace(source.FullName,destination), true);
            }
            return true;
        }
        catch (IOException exp)
        {
            return false;
        }
    }
  • 0
    Я не уверен, что это добавляет к принятому ответу, кроме использования рекурсии (где это не нужно) и сокрытия исключений, чтобы усложнить отладку.
0

Используйте этот класс.

public static class Extensions
{
    public static void CopyTo(this DirectoryInfo source, DirectoryInfo target, bool overwiteFiles = true)
    {
        if (!source.Exists) return;
        if (!target.Exists) target.Create();

        Parallel.ForEach(source.GetDirectories(), (sourceChildDirectory) => 
            CopyTo(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name))));

        foreach (var sourceFile in source.GetFiles())
            sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles);
    }
    public static void CopyTo(this DirectoryInfo source, string target, bool overwiteFiles = true)
    {
        CopyTo(source, new DirectoryInfo(target), overwiteFiles);
    }
}
  • 1
    Это похоже на другие ответы, реорганизованные для использования .ToList().ForEach( (что немного больше работы, памяти и немного медленнее, чем просто перечисление каталогов напрямую) и в качестве метода расширения. Выбранный ответ использует SearchOption.AllDirectories и избегает рекурсия, поэтому я бы рекомендовал переключиться на эту модель. Кроме того, вам обычно не нужно имя типа в методах расширения - я бы переименовал его в CopyTo() чтобы он стал sourceDir.CopyTo(destination);

Ещё вопросы

Сообщество Overcoder
Наверх
Меню