Bash: Kopiuj nazwane pliki rekurencyjnie, zachowując strukturę folderów

103

Miałem nadzieję:

cp -R src/prog.js images/icon.jpg /tmp/package

dałoby symetryczną strukturę w katalogu docelowym:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

ale zamiast tego oba pliki są kopiowane do / tmp / package. Płaska kopia. (To jest na OSX).

Czy istnieje prosta funkcja bash, której mogę użyć do skopiowania wszystkich plików, w tym plików określonych za pomocą symboli wieloznacznych (np. Src / *. Js) w ich właściwe miejsce w katalogu docelowym. Trochę jak „dla każdego pliku uruchom mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")”, ale być może jedno polecenie.

To jest istotny wątek, który sugeruje, że nie jest to możliwe. Autorskie rozwiązanie nie jest jednak dla mnie zbyt przydatne, ponieważ chciałbym po prostu podać listę plików, bez symboli wieloznacznych lub nie, i skopiować je wszystkie do docelowego katalogu. IIRC MS-DOS xcopy robi to, ale wydaje się, że nie ma odpowiednika dla cp.

mahemoff
źródło

Odpowiedzi:

154

Czy próbowałeś użyć opcji --parents? Nie wiem, czy OS X to obsługuje, ale to działa na Linuksie.

cp --parents src/prog.js images/icon.jpg /tmp/package

Jeśli to nie zadziała w systemie OS X, spróbuj

rsync -R src/prog.js images/icon.jpg /tmp/package

jak sugerowano aif.

ustun
źródło
4
dzięki. okazuje się, że "cp --parents" nie jest możliwe na Macu, ale miło jest znać flagę dla innych unixenów. rsync -R to najprostsze przenośne rozwiązanie tego problemu.
mahemoff
1
Zaakceptowałem ten ze względu na jego elegancję / łatwość zapamiętywania, ale właśnie odkryłem, że nie kopiuje całych katalogów (przynajmniej na OSX), podczas gdy ten z tar poniżej robi to.
mahemoff
cp --parentsjest niedozwoloną opcją w OSX (BSD cp), ale gcp(GNU cp) działa dobrze. Jeśli nie ma go jeszcze w twoim systemie, użyj brew install coreutils. Będziesz mieć wiele narzędzi z prefiksem g.
kyb
@mahemoff cp -R --parentsi rsync -rRkopiuje względnie zarówno pliki, jak i katalogi.
Vortico
22

Jednokierunkowa:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
źródło
Och, podoba mi się to o wiele bardziej niż moja odpowiedź.
EMPraptor
4
Możesz także użyć -Copcji, aby zrobić chdir za Ciebie - tar cf - _files_ | tar -C /dest xf -lub coś w tym stylu.
D.Shawley
dzięki, to jest zwięzłe, chociaż wolę rsync ze względu na prostotę.
mahemoff
1
Wspaniały! Czy ktoś wie, jak zamienić to w polecenie powłoki? Powinien przyjmować N danych wejściowych, pierwsze N-1 to pliki do skopiowania, a ostatnie to folder docelowy.
arod
@arod $ {! #} to ostatni parametr i użyj go, aby uzyskać poprzednie argumenty stackoverflow.com/questions/1215538/… . Jeśli napiszesz polecenie, podaj link do sedna tutaj.
mahemoff
18

Alternatywnie, jeśli jesteś oldschoolowy, użyj cpio:

cd /source;
find . -print | cpio -pvdmB /target

Oczywiście możesz filtrować listę plików według własnego uznania.

Opcja „-p” dotyczy trybu „tranzytowego” (w porównaniu z „-i” dla wejścia lub „-o” dla wyjścia). Opcja „-v” jest gadatliwa (wyświetla pliki w trakcie ich przetwarzania). „-M” zachowuje czasy modyfikacji. „-B” oznacza użycie „dużych bloków” (gdzie duże bloki mają 5120 bajtów zamiast 512 bajtów); możliwe, że obecnie nie ma to żadnego efektu.

Jonathan Leffler
źródło
1
Lepiej jest używać -print0kombinacji --nullopcji, aby nie zerwała ze znakami specjalnymi i takimi:find . -print0 | cpio -pvdmB --null /target
haridsv
Nazwij to oldschoolem, nazwij to przenośnym, nazwij to czymkolwiek, ale zdecydowanie ufam cpiotemu zadaniu. Zgadzam się, że należy użyć opcji -print0i -null, w przeciwnym razie w pewnym momencie ktoś poda Ci foldery ze „znakami specjalnymi” (najprawdopodobniej spacjami) i coś się stanie. Nie mówię o tym z własnego doświadczenia, ale możesz spróbować wykonać kopię zapasową kilku plików i skończyć z kopią zapasową tylko połowy z nich ze względu na spacje w nazwach plików. (Ok, mówię z własnego doświadczenia.)
bballdave025
@ bballdave025: Występują tylko problemy z cpiotak, jak pokazano, jeśli nazwy plików zawierają znaki nowej linii - wtedy zwykle pojawia się komunikat o błędzie, że nie znaleziono dwóch (lub więcej) nazw plików dla każdego nowego wiersza w nazwie pliku. (Czasami możesz otrzymać mniej komunikatów - ale wymaga to dużej ostrożności podczas tworzenia przypadku testowego). Kiedy użyłem cpio, nie było --nullopcji; opcje z podwójnym myślnikiem nie były częścią notacji opcji SVR4, a koncepcja -print0nie była obecna w findobu. Ale to było dawno temu (na przykład połowa lat 90. zanim Linux osiągnął dominację).
Jonathan Leffler
Dzięki za szczegóły, @Jonathan_Leffler. Uwielbiam się tutaj uczyć. Teraz próbuję sobie przypomnieć, jaki błąd w kopiowaniu rekurencyjnym spowodował problemy ze spacjami - jest wiele sposobów, w jakie mogłem
popełnić
@ bballdave025— Jedną możliwością jest użycie xargsw miksie - dzieli się na białe znaki - spacje, tabulatory, znaki nowej linii. OTOH, nie jestem pewien, jak i dlaczego to zrobiłeś. Instrukcja GNU cpiodotycząca trybu wyjściowego zawiera informacje o jednej nazwie pliku w każdym wierszu. Podręcznik użytkownika SVR4 (wydrukowany 1990) jest niejasny: cpio -o(tryb kopiowania) czyta standardowe wejście w celu uzyskania listy nazw ścieżek i kopiuje te pliki na standardowe wyjście wraz z nazwą ścieżki i informacjami o stanie. Jest zatem szansa, że ​​złamał nazwy w spacjach.
Jonathan Leffler
16

Opcja -R rsync zrobi to, czego oczekujesz. To bardzo bogata w funkcje kopiarka plików. Na przykład:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Przykładowe wyniki:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Ryan Bright
źródło
1

Próbować...

for f in src/*.js; do cp $f /tmp/package/$f; done

więc za to, co robiłeś pierwotnie ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

lub

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
źródło
Nie potrzebujesz echoani $vtutaj. Ta metoda również zakończy się niepowodzeniem, jeśli odpowiedni katalog nie istnieje w miejscu docelowym.
Weijun Zhou