Jak skopiować folder rekurencyjnie w idempotentny sposób za pomocą cp?

46

Kiedy używam

cp -R inputFolder outputFolder

wynik zależy od kontekstu :

  1. jeśli outputFoldernie istnieje, zostanie utworzony, a ścieżka sklonowanego folderu będzie outputFolder.
  2. jeśli outputFolderistnieje, utworzony zostanie klonoutputFolder/inputFolder

To okropne , ponieważ chcę utworzyć skrypt instalacyjny, a jeśli użytkownik uruchomi go dwa razy przez pomyłkę, utworzy go outputFolderza pierwszym razem, a następnie przy drugim uruchomieniu wszystkie elementy zostaną utworzone ponownie outputFolder/inputFolder.

  • Chcę zawsze pierwszego zachowania: utwórz klon obok oryginału (jako rodzeństwo).
  • Chcę cpbyć przenośny (np. MINGW nie został rsyncwysłany)
  • Sprawdziłem, cp -R --parentsale to odtwarza ścieżkę w górę drzewa katalogów (więc klon nie będzie, outputFolderale some/path/outputFolder)
  • --remove-destinationlub --updatew przypadku, gdy 2 nic nie zmieni, nadal rzeczy są kopiowaneoutputFolder/inputFolder

Czy istnieje sposób, aby to zrobić bez uprzedniego sprawdzenia istnienia outputFolder(jeśli folder nie istnieje, to ...) lub użycia rm -rf outputFolder?

Jaki jest uzgodniony, przenośny sposób UNIX?

jakub.g
źródło
2
Niektóre z tych opcji nie są POSIX, więc te, które wypróbowałeś, nie są zbyt przenośne. Jeśli możesz żyć z niewielkim brakiem przenośności, dlaczego nie użyć rsync?
muru
1
Jeśli nie musisz używać cp, przesyłanie potokowe między dwoma tarpoleceniami to niezawodny niezawodny sposób na kopiowanie drzew.
R ..
@R .. czy możesz podać przykład? @muru Chciałbym, aby to działało w MINGW, który nie został rsyncwysłany.
jakub.g
Z GNU tar tar -C input -cf - . | tar -C output -xf -działa. -Czmienia katalog roboczy, ale nie jest to standardowa opcja, więc jeśli nie jest obsługiwany, musisz uruchomić dwie tabele w odpowiednich katalogach roboczych na początek, np. ( cd input && tar -cf - . ) | ( cd output && tar -xf - )Jeśli jednak masz do czynienia z Windows, użyłbym tylko odpowiedzi roaima.
R ..

Odpowiedzi:

54

Użyj tego zamiast:

cp -R inputFolder/. outputFolder

Działa to dokładnie tak samo, jak, powiedzmy, cp -R aaa/bbb cccdziała: jeśli cccnie istnieje, to jest tworzone jako kopia bbbi jego zawartość; ale jeśli cccjuż istnieje, to ccc/bbbjest tworzony jako kopia bbbi jego zawartość.

W prawie każdym przypadku bbbdaje to niepożądane zachowanie, które zauważyłeś w swoim pytaniu. Jednak w tej konkretnej sytuacji bbbjest po prostu .tak aaa/bbbjest naprawdę aaa/., co z kolei jest tak naprawdę aaa, ale pod inną nazwą. Mamy więc dwa scenariusze:

  1. ccc nie istnieje:

    Polecenie cp -R aaa/. cccoznacza „utwórz ccci skopiuj zawartość aaa/.do ccc/., tzn. Skopiuj aaado ccc.

  2. ccc istnieje:

    Polecenie cp -R aaa/. cccoznacza „skopiuj zawartość aaa/.do ccc/., tzn. Skopiuj aaado ccc.

roaima
źródło
3
Huh, to dla mnie nowy, ale wydaje się, że działa! Czy to gdziekolwiek udokumentowane?
Ilmari Karonen,
1
Przetestowałem inputFolder/.zamiast inputFolderi rzeczywiście działa dla mnie na Windows / Git Bash. (Nie działa to jednak tylko z końcem /, potrzebuję także kropki po ukośniku). Dałem +1, ponieważ jest interesujący, choć prawdopodobnie jest to zbyt trudne, aby użyć go w publicznym kodzie :)
jakub.g
@IlmariJaronen nie trzeba tego jawnie dokumentować. Postępuj zgodnie z wyjaśnieniem, a zobaczysz, jak to po prostu „przestrzega zasad”.
roaima
18

Nie kopiuj folderu, tylko kopiuj zawartość:

## Create the target directory. The -p suppresses error messages
## if the directory already exists
mkdir -p outputFolder

## Copy the contents recursively, this will not recreate the parent
cp -R inputfolder/* outputfolder/

W ten sposób oboje upewnisz się, że katalog docelowy jest tworzony przy pierwszym uruchomieniu skryptu i unikniesz problemu podczas jego uruchamiania po raz drugi.

Chris Down bardzo poprawnie wskazuje, że w bashie pominie to pliki, których nazwa zaczyna się od .. Aby tego uniknąć, możesz uruchomić shopt -s dotglobprzed uruchomieniem powyższej komendy.

Zarówno -pdla,mkdir jak i -Rdlacp są zdefiniowane przez POSIX, więc powinno to być całkowicie przenośne.

terdon
źródło
6
Uwaga: nie spowoduje to skopiowania plików zaczynających się od .. W bash możesz dotglobdo tego użyć .
Chris Down,
2
Należy zauważyć, że ta metoda prawdopodobnie zawiedzie, jeśli liczba pozycji katalogu w folderze wejściowym jest duża, ponieważ rozszerzenie globalne może przekroczyć maksymalną długość wiersza poleceń.
Tim Cutts,
11

Wypróbuj -Topcję cp. Istnieje w GNU coreutils cpwersja 8.22; poza tym może nie być przenośny.

Tom Hunt
źródło
1
Tak, wygląda na to, że tego właśnie chciałem: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Niestety moja wersja cpjest znacznie starsza.
jakub.g
1
+1 Użyłem tego już dziś. -ujest świetną opcją do dodania, aby był leniwy.
PSkocik,
4

Możesz także użyć rsync:

rsync -uav inputFolder/ outputFolder/

(zwróć uwagę na ukośniki, szczególnie po pierwszym)

Ned64
źródło
0

Moim zdaniem nie ma nic prostszego i bardziej przenośnego niż rm -rf outputFolder. Więc zawsze się z tym trzymam. Rozumiem, że twoje pytanie jest inne, ale uważam, że jest to najlepsza praktyka.

dashohoxha
źródło
Nie powiedzie się to, jeśli istnieją określone uprawnienia lub prawa własności do celu, które należy zachować. Lub jeśli był to dowiązanie symboliczne.
roaima,
1
Pytanie chce, aby wynik cpbył taki sam, zarówno gdy katalog nie istnieje, jak i kiedy istnieje. O czym ty mówisz?
dashohoxha
cpKomenda podana przez OP nie zachowuje uprawnienia (lub znaczniki czasu).
roaima,
0

Możesz użyć -topcji cppolecenia jako:

cp -R inputFolder -t outputFolder

teraz, jeśli folder docelowy nie istnieje, wygeneruje błąd:

cp: failed to access ‘outputFolder’: No such file or directory

Uwaga: powyższe polecenie skopiuje inputFolderwraz z zawartością (a nie tylko zawartą w nim zawartością)

jeśli chcesz skopiować tylko jego zawartość inputFolderstaje się nieco trudna (ponieważ musisz zachować ostrożność podczas korzystania z globowania powłoki przy użyciu gwiazdki *)

cp -R  -t outputFolder/ -- inputFolder/*

teraz, jeśli folder docelowy nie istnieje, wygeneruje błąd:

cp: failed to access ‘outputFolder’: No such file or directory

pracuje z cp (GNU coreutils) 8.23

Edward Torvalds
źródło