Skopiuj całą zawartość katalogu w C #

524

Chcę skopiować całą zawartość katalogu z jednej lokalizacji do drugiej w C #.

Wydaje się, że nie ma sposobu, aby to zrobić za pomocą System.IOklas bez dużej rekurencji.

W VB istnieje metoda, której możemy użyć, jeśli dodamy odwołanie do Microsoft.VisualBasic:

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

To wydaje się raczej brzydkim hackem. Czy jest lepszy sposób?

Keith
źródło
101
Powiedziałbym, że patrząc na zamieszczone poniżej alternatywy, sposób VB nie wygląda tak brzydko.
Kevin Kershaw,
41
Jak może być włamaniem, gdy jest częścią .NET Framework? Przestań pisać kod i użyj tego, co masz.
AMissico,
15
To powszechne nieporozumienie. Microsft.VisualBasic zawiera wszystkie popularne procedury Visual Basic, które znacznie ułatwiają kodowanie w VB. Microsot.VisualBasic.Compatibility to zestaw używany dla starszych wersji VB6.
AMissico,
63
Istnieje ponad 2000 linii kodu do Microsoft.VisualBasic.Devices.Computer.FileSystem. CopyDirectory zapewnia, że ​​nie kopiujesz folderu nadrzędnego do folderu podrzędnego i inne kontrole. Jest wysoce zoptymalizowany i tak dalej. Wybrana odpowiedź to w najlepszym razie kruchy kod.
AMissico,
17
@AMissico - ok, więc dlaczego jest to zoptymalizowany i kompletny kod, Microsoft.VisualBasica nie System.IO? Powodem, dla którego nie ma go w Mono, jest to, że wszystkie biblioteki uważane za „podstawowe” są System.[something]- wszystkie inne nie. Nie mam problemu z powołaniem się na dodatkową bibliotekę DLL, ale jest dobry powód, dla którego Microsoft nie zawarł tej funkcji System.IO.
Keith,

Odpowiedzi:

553

Dużo łatwiej

//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);
tboswell
źródło
25
To naprawdę niezły kawałek kodu, ale nie jest to rodzaj kodu, którego można użyć w dowolnym miejscu. Programiści powinni zachować ostrożność, ponieważ dirPath.Replace może powodować niepożądane konsekwencje. Tylko ostrzeżenie dla osób, które lubią kopiować i wklejać w sieci. Kod opublikowany przez @jaysponsored jest bezpieczniejszy, ponieważ nie używa łańcucha. Wymień, ale jestem pewien, że ma również swoje narożniki.
Alex
17
Uważaj na ten kod, ponieważ spowoduje zgłoszenie wyjątku, jeśli katalog docelowy już istnieje. Nie zastąpi również plików, które już istnieją. Po prostu dodaj zaznaczenie przed utworzeniem każdego katalogu i użyj przeciążenia File.Copy, aby zastąpić plik docelowy, jeśli istnieje.
dołączenie
30
@Xaisoft - Replacema problem, jeśli masz powtarzający się wzór na ścieżce, na przykład "sourceDir/things/sourceDir/things"powinien się stać "destinationDir/things/sourceDir/things", ale jeśli użyjesz zamień, staje się"destinationDir/things/destinationDir/things"
Keith
35
Dlaczego *.*zamiast *? Nie chcesz też kopiować plików bez rozszerzeń?
Daryl
10
Zbudujmy coś i dodajmy to do Open Source .NET Core ...: /
Mzn
231

Hmm, chyba nie rozumiem pytania, ale zaryzykuję. Co jest nie tak z następującą prostą metodą?

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));
}

EDYCJA Ponieważ ten post zebrał imponującą liczbę głosów negatywnych za tak prostą odpowiedź na równie proste pytanie, pozwól mi dodać wyjaśnienie. Proszę przeczytać przed downvoting .

Przede wszystkim ten kod nie jest zamiennikiem zastępującym kod w pytaniu. To jest wyłącznie w celach ilustracyjnych.

Microsoft.VisualBasic.Devices.Computer.FileSystem.CopyDirectorywykonuje dodatkowe testy poprawności (np. czy źródło i cel są poprawnymi katalogami, czy źródło jest rodzicem celu itp.), których brakuje w tej odpowiedzi. Ten kod jest prawdopodobnie również bardziej zoptymalizowany.

To powiedziawszy, kod działa dobrze . To jest (prawie identycznie) zostały wykorzystane w dojrzałym oprogramowania przez lata. Oprócz nieodłącznej zmienności występującej we wszystkich operacjach we / wy (np. Co się stanie, jeśli użytkownik ręcznie odłączy dysk USB podczas pisania na nim kodu?), Nie ma znanych problemów.

W szczególności chciałbym zwrócić uwagę, że zastosowanie rekurencji tutaj absolutnie nie stanowi problemu. Ani w teorii (koncepcyjnie jest to najbardziej eleganckie rozwiązanie), ani w praktyce: ten kod nie przepełni stosu . Stos jest wystarczająco duży, aby obsłużyć nawet głęboko zagnieżdżone hierarchie plików. Na długo przed pojawieniem się problemu ze stosem zaczyna się ograniczenie długości ścieżki folderu.

Zauważ, że złośliwy użytkownik może złamać to założenie, używając głęboko zagnieżdżonych katalogów po jednej literze. Nie próbowałem tego. Ale żeby to zilustrować: aby przepełnić ten kod na typowym komputerze, katalogi musiałyby być zagnieżdżone kilka tysięcy razy. To po prostu nie jest realistyczny scenariusz.

Konrad Rudolph
źródło
5
To jest rekurencja głowy. Jeśli katalogi są wystarczająco zagnieżdżone, może paść ofiarą przepełnienia stosu.
spoulson,
19
Do niedawna głębokość zagnieżdżania katalogów była ograniczona przez system operacyjny. Wątpię, czy znajdziesz katalogi zagnieżdżone więcej niż kilkaset razy (jeśli w ogóle). Powyższy kod może zająć znacznie więcej.
Konrad Rudolph
5
Podoba mi się podejście rekurencyjne, ryzyko przepełnienia stosu jest w najgorszym przypadku minimalne.
David Basarab,
49
@DTashkinov: przepraszam, ale wydaje się to trochę przesadne. Dlaczego oczywisty kod == jest negatywny? Przeciwnie powinno być prawdą. Wbudowana metoda została już opublikowana, ale Keith poprosił o inną metodę. Co masz na myśli przez ostatnie zdanie? Przepraszam, ale po prostu w ogóle nie rozumiem powodów, dla których przegłosowałem.
Konrad Rudolph
6
@AMissico: lepsze niż co ? Nikt nie twierdził, że jest lepszy niż kod VB z frameworka. Wiemy , że tak nie jest.
Konrad Rudolph,
131

Skopiowano z 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.
}
Justin R.
źródło
8
Nie ma powodu, aby sprawdzać, czy katalog istnieje, wystarczy wywołać Directoty.CreateDirectory, który nic nie zrobi, jeśli katalog już istnieje.
Tal Jerome
1
Dla tych, którzy chcą radzić sobie ze ścieżkami dłuższymi niż 256 znaków, możesz użyć pakietu Nuget o nazwie ZetaLongPaths
AK
2
Ta odpowiedź wydaje się najbardziej przydatna ze wszystkich. Używając DirectoryInfo zamiast ciągów, unika się wielu potencjalnych problemów.
DaedalusAlpha
50

Spróbuj tego:

Process proc = new Process();
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "xcopy.exe");
proc.StartInfo.Arguments = @"C:\source C:\destination /E /I";
proc.Start();

Twoje argumenty xcopy mogą się różnić, ale masz pomysł.

d4nt
źródło
3
/ E każe mu skopiować wszystkie podkatalogi (nawet puste). / Mówię, że jeśli miejsce docelowe nie istnieje, utwórz katalog o tej nazwie.
d4nt
6
dodaj podwójny cytat, aby być bezpiecznym.
jaysonragasa
6
Dodaj / Y, aby zapobiec wyświetlaniu monitu o zastąpienie istniejących plików. stackoverflow.com/q/191209/138938
Jon Crowell
16
Przepraszam, ale to okropne. Zakłada, że ​​system docelowy to Windows. Zakłada się, że przyszłe wersje zawierają xcopy.exe na tej konkretnej ścieżce. Zakłada się, że parametry xcopy nie zmieniają się. Wymaga złożenia parametrów xcopy jako łańcucha, co wprowadza duży potencjał błędu. Również próbka nie wspomina o żadnej obsłudze błędów dla wyników rozpoczętego procesu, czego oczekiwałbym, ponieważ w przeciwieństwie do innych metod, to by się nie udało.
cel sharp
3
@MatthiasJansen, myślę, że podjąłeś to bardzo osobiście. Odpowiedź jest istotna i wyjaśnia wiele na temat tego, jak to osiągnąć ... Ponieważ pytanie nie wymaga kompatybilności międzyplatformowej ani użycia xcopy lub czegokolwiek innego, plakat właśnie odpowiedział, aby wyjaśnić, jak można to osiągnąć w jeden sposób ... Tam może być 1000 sposobów na zrobienie tego samego, a odpowiedzi są różne ... dlatego forum jest tutaj, aby się nim zająć, a programiści z całego świata przyjeżdżają tutaj, aby podzielić się swoimi doświadczeniami. Głosuję za twoim komentarzem.
KMX
47

Lub, jeśli chcesz pójść na poważnie, dodaj odniesienie do swojego projektu dla Microsoft.VisualBasic, a następnie użyj następujących poleceń:

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

Jednak korzystanie z jednej z funkcji rekurencyjnych jest lepszym sposobem, ponieważ nie trzeba ładować biblioteki DLL VB.

Josef
źródło
1
To tak naprawdę nie różni się od tego, jak to zrobiłem - wciąż musisz załadować elementy kompatybilności wstecznej VB, aby móc to zrobić.
Keith
10
Czy ładowanie zestawu VB jest drogie? Opcje VB są znacznie bardziej eleganckie niż wersje C #.
jwmiller5
3
Co to jest „kompatybilność wsteczna VB”? CopyDirectory używa powłoki lub frameworka.
AMissico,
3
Chciałbym, żeby było włączone System.IO.Directory, ale to lepsze niż przepisywanie!
Josh M.
2
To jest droga do przejścia w tryb Imo, znacznie łatwiej niż w przypadku innych opcji
reggaeguitar
38

Ta strona zawsze bardzo mi pomogła, a teraz moja kolej, aby pomóc innym w tym, co wiem.

Mam nadzieję, że mój poniższy kod będzie dla kogoś przydatny.

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(System.IO.Path.Combine(destination_dir, dir.Substring(source_dir.Length + 1)));
        // 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, System.IO.Path.Combine(destination_dir, file_name.Substring(source_dir.Length + 1)));
    }
jaysponsorowane
źródło
1
Pamiętaj o ostatnim ukośniku odwrotnym
Alexey F,
24
Ludzie, używajcie Path.Combine(). Nigdy nie używaj konkatenacji ciągów do łączenia ścieżek plików.
Andy,
3
Masz OBOB w powyższym fragmencie kodu. Powinieneś używać source_dir.Length + 1, nie source_dir.Length.
PellucidWombat
Ten kod jest dobrym pomysłem, ale ... Plik nie musi mieć „.” w nim, więc lepiej byłoby użyć ystem.IO.Directory.GetFiles (katalog_źródłowy, „*”, System.IO.SearchOption.AllDirectories))
Jean Libera
Dziękuję @JeanLibera, masz rację. Zmieniłem kod z twoją sugestią.
jaysponsorowany
14

Skopiuj folder rekurencyjnie bez rekurencji, aby uniknąć przepełnienia stosu.

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;
    }
}
Jens Granlund
źródło
przydatny szablon nierekurencyjny :)
Minh Nguyen
2
Trudno wyobrazić sobie wysadzenie stosu przed świeceniem limitu ścieżki
Ed S.,
5

Oto klasa narzędzi, której użyłem do takich zadań IO.

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

źródło
Należy pamiętać, że Microsoft używa SHFileOperation wewnętrznie dla Microsoft.VisualBasic.
jrh 12.03.19
3

Może nie jest świadomy wydajności, ale używam go do folderów 30 MB i działa bezbłędnie. Poza tym nie podobała mi się cała ilość kodu i rekursji wymagana do tak łatwego zadania.

var source_folder = "c:\src";
var dest_folder = "c:\dest";
var zipFile = source_folder + ".zip";

ZipFile.CreateFromDirectory(source_folder, zipFile);
ZipFile.ExtractToDirectory(zipFile, dest_folder);
File.Delete(zipFile);

Uwaga: ZipFile jest dostępny w .NET 4.5+ w przestrzeni nazw System.IO.Compression

AlexanderD
źródło
1
Ja też nie, stąd pytanie, ale wybrana odpowiedź nie wymaga rekurencji. Ta odpowiedź tworzy plik zip na dysku, co stanowi dużo dodatkowej pracy dla kopii pliku - nie tylko tworzysz dodatkową kopię danych, ale spędzasz czas procesora na kompresji i dekompresji. Jestem pewien, że to działa, w ten sam sposób, w jaki prawdopodobnie możesz wbić gwóźdź butem, ale jest więcej pracy z większą liczbą rzeczy, które mogą się nie udać, podczas gdy istnieją lepsze sposoby na to.
Keith
Powodem tego było zastąpienie ciągów. Jak zauważyli inni, przyjęta odpowiedź budzi wiele obaw; Łącze skrzyżowania może nie działać, podobnie jak powtarzanie wzorca folderu lub plików bez rozszerzenia lub nazwy. Mniej kodu, mniejsza szansa na błąd. A ponieważ czas procesora nie jest dla mnie problemem, dlatego nadaje się do mojego konkretnego przypadku
AlexanderD
2
Tak, to jak przejechanie 1000 mil na twojej drodze, aby ominąć pojedyncze światło, ale to twoja podróż, więc idź. Sprawdzanie wzorów folderów jest banalne w porównaniu z tym, co ZIP musi zrobić pod maską. Zdecydowanie odradzam to każdemu, kto dba o to, aby nie marnować procesora, dysku, energii elektrycznej lub gdzie musi to działać razem z innymi programami na tym samym komputerze. Ponadto, jeśli kiedykolwiek zadajesz takie pytanie podczas rozmowy kwalifikacyjnej, nigdy nie mów „mój kod jest prosty, więc nie dbam o czas procesora” - nie dostaniesz pracy.
Keith,
1
Przełączyłem się na odpowiedź udzieloną przez @ justin-r . Mimo to pozostawię tę odpowiedź jako kolejny sposób na zrobienie tego
AlexanderD
1
Jeśli foldery znajdują się w osobnych udziałach sieciowych i zawierają dużo plików, to moim zdaniem najlepsza opcja.
Danny Parker
2

Drobne ulepszenie odpowiedzi d4nt, ponieważ prawdopodobnie chcesz sprawdzić błędy i nie musisz zmieniać ścieżek xcopy, jeśli pracujesz na serwerze i komputerze programistycznym:

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));
    }
}
Chris S.
źródło
2

To jest mój kod, mam nadzieję, że ta pomoc

    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;
    }
Khoi_Vjz_Boy
źródło
Zobacz wybraną odpowiedź, używając SearchOptionflagi przy wyszukiwaniu folderów i plików, robi to w 4 liniach kodu. Sprawdź również .HasFlagrozszerzenie teraz na wyliczeniach.
Keith
2

Jeśli podoba Ci się popularna odpowiedź Konrada, ale chcesz, aby sourcesam był folderem pod target, zamiast umieszczać w nim swoje dzieci target, oto kod do tego. Zwraca nowo utworzony DirectoryInfo, co jest przydatne:

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;
}
toddmo
źródło
2

Zawsze możesz tego użyć , pobranego ze strony Microsofts.

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);
        }
    }
}
iato
źródło
1
To świetnie - pamiętaj, że w wierszu jest file.CopyTo(temppath, false);napisane „skopiuj ten plik do tego miejsca, tylko jeśli nie istnieje”, co w większości przypadków nie jest tym, czego chcemy. Ale rozumiem, dlaczego to domyślnie. Może dodać flagę do metody zastępowania plików.
Andy,
2

tboswell's replace Proof version (który jest odporny na powtarzanie wzorca w ścieżce pliku)

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);
    }
bh_earth0
źródło
3
Ludzie, używajcie Path.Combine(). Nigdy nie używaj konkatenacji ciągów do łączenia ścieżek plików.
Andy,
2

Moje rozwiązanie jest w zasadzie modyfikacją odpowiedzi @ Termininja, jednak nieco ją ulepszyłem i wydaje się, że jest ponad 5 razy szybsza niż zaakceptowana odpowiedź.

public static void CopyEntireDirectory(string path, string newPath)
{
    Parallel.ForEach(Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories)
    ,(fileName) =>
    {
        string output = Regex.Replace(fileName, "^" + Regex.Escape(path), newPath);
        if (File.Exists(fileName))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(output));
            File.Copy(fileName, output, true);
        }
        else
            Directory.CreateDirectory(output);
    });
}

EDYCJA: Modyfikacja @Ahmed Sabry do pełnego równoległego foreach daje lepszy wynik, jednak kod używa funkcji rekurencyjnej i w niektórych sytuacjach nie jest idealny.

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

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

    Parallel.ForEach(source.GetFiles(), sourceFile =>
        sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles));
}
Jerry Liang
źródło
1

Przepraszamy za poprzedni kod, nadal miał błędy :( (padł ofiarą problemu z najszybszym działem). Tutaj jest testowany i działa. Kluczem jest SearchOption.AllDirectories, co eliminuje potrzebę wyraźnej rekursji.

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);
    }
}
Vinko Vrsalovic
źródło
1

Oto metoda rozszerzenia DirectoryInfo a la FileInfo.CopyTo (zwróć uwagę na overwriteparametr):

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;
}
Daryl
źródło
1

Użyj tej klasy.

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);
    }
}
Ahmed Sabry
źródło
1
Jest to podobne do innych odpowiedzi, ponownie wykorzystane .ToList().ForEach((co jest nieco więcej pracy, pamięci i nieco wolniej niż tylko bezpośrednie wyliczanie katalogów) i jako metoda rozszerzenia. Wybrana odpowiedź używa SearchOption.AllDirectoriesrekurencji i unika jej, więc polecam przejście na ten model. Ponadto zwykle nie potrzebujesz nazwy typu w metodach rozszerzenia - zmieniłem nazwę na, CopyTo()aby stała sięsourceDir.CopyTo(destination);
Keith
1

Jeden wariant z tylko jedną pętlą do kopiowania wszystkich folderów i plików:

foreach (var f in Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories))
{
    var output = Regex.Replace(f, @"^" + path, newPath);
    if (File.Exists(f)) File.Copy(f, output, true);
    else Directory.CreateDirectory(output);
}
Termininja
źródło
Jeśli zamierzasz użyć Regex, prawdopodobnie powinieneś również Regex.Escape(path)stanowić część kompozycji wyrażenia (szczególnie biorąc pod uwagę separator ścieżek systemu Windows). Państwo może również uzyskać korzyści z tworzenia (a może kompilacji) na new Regex()zewnątrz obiektu pętli, a nie opierając się na metodzie statycznej.
jimbobmcgee
0

Lepszy niż jakikolwiek kod (metoda rozszerzenia DirectoryInfo z rekurencją)

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;
        }
    }
malballah
źródło
1
Nie jestem pewien, co to dodaje do zaakceptowanej odpowiedzi, poza wykorzystaniem rekurencji (tam, gdzie nie jest to konieczne) i ukrywanie wyjątków, aby utrudnić debugowanie.
Keith,
0

Skopiuj i zamień wszystkie pliki w folderze

        public static void CopyAndReplaceAll(string SourcePath, string DestinationPath, string backupPath)
    {
            foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory($"{DestinationPath}{dirPath.Remove(0, SourcePath.Length)}");
                Directory.CreateDirectory($"{backupPath}{dirPath.Remove(0, SourcePath.Length)}");
            }
            foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", SearchOption.AllDirectories))
            {
                if (!File.Exists($"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"))
                    File.Copy(newPath, $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}");
                else
                    File.Replace(newPath
                        , $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"
                        , $"{ backupPath}{newPath.Remove(0, SourcePath.Length)}", false);
            }
    }
Lakmal
źródło
Pozdrawiam odpowiedź, ale nie jestem pewien, co to dodaje. Również nie try catch throwma sensu.
Keith
0

Poniższy kod jest sugestią Microsoft dotyczącą kopiowania katalogów i jest udostępniany przez drogi @iato, ale kopiuje tylko podkatalogi i pliki folderu źródłowego rekurencyjnie i nie kopiuje folderu źródłowego samodzielnie (jak kliknięcie prawym przyciskiem myszy -> kopiowanie ).

ale pod tą odpowiedzią jest trudny sposób :

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs = true)
        {
            // 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);
                }
            }
        }

jeśli chcesz skopiować zawartość z źródłowego folderu i podfolderów rekurencyjnie można po prostu używać go tak:

string source = @"J:\source\";
string dest= @"J:\destination\";
DirectoryCopy(source, dest);

ale jeśli chcesz skopiować katalog źródłowy samodzielnie (podobnie, jak kliknąłeś folder źródłowy i kliknąłeś kopię, a następnie w folderze docelowym kliknąłeś wklej), powinieneś użyć w ten sposób:

 string source = @"J:\source\";
 string dest= @"J:\destination\";
 DirectoryCopy(source, Path.Combine(dest, new DirectoryInfo(source).Name));
Arash.Zandi
źródło
został już opublikowany kilka odpowiedzi poniżej: stackoverflow.com/a/45199038/1951524
Martin Schneider
Dzięki @ MA-Maddin, ale czy kopiuje sam folder źródłowy? czy tylko zawartość?
Arash.Zandi