Skopiuj folder rekurencyjnie, z wyłączeniem niektórych folderów

197

Próbuję napisać prosty skrypt bash, który skopiuje całą zawartość folderu, w tym ukryte pliki i foldery, do innego folderu, ale chcę wykluczyć niektóre określone foldery. Jak mogłem to osiągnąć?

trobrock
źródło
1
Wyobrażam sobie coś takiego. -nazwa * potokował do grep / v „exclude-pattern”, aby odfiltrować te, których nie chcesz, a następnie przesłał do cp, aby wykonać kopię.
i_am_jorf
1
Próbowałem zrobić coś takiego, ale nie mogłem wymyślić, jak używać cp z fajką
trobrock
1
To powinno prawdopodobnie przejść do superużytkownika. Polecenie, którego szukasz, to xargs. Możesz także zrobić coś takiego jak dwie smoły połączone rurą.
Kyle Butt,
1
Być może jest już późno i nie odpowiada dokładnie na pytanie, ale oto wskazówka: jeśli chcesz wykluczyć tylko bezpośrednie cp -R !(dir1|dir2) path/to/destination
potomki
1
Pamiętaj, że !(dir1|dir2)wzorzec wymaga extglobwłączenia ( shopt -s extglobaby go włączyć).
Boris D. Teoharov,

Odpowiedzi:

334

Użyj rsync:

rsync -av --exclude='path1/to/exclude' --exclude='path2/to/exclude' source destination

Pamiętaj, że używanie sourcei source/są różne. Końcowy ukośnik środki, aby skopiować zawartość folderu sourcedo destination. Bez końcowego ukośnika oznacza to skopiowanie folderusource do destination.

Alternatywnie, jeśli masz wiele katalogów (lub plików) do wykluczenia, możesz użyć --exclude-from=FILEgdzieFILE jest nazwa pliku zawierającego pliki lub katalogi do wykluczenia.

--exclude może również zawierać symbole wieloznaczne, takie jak --exclude=*/.svn*

Kaleb Pederson
źródło
10
Sugeruję dodanie opcji --dry-run, aby sprawdzić, które pliki zostaną skopiowane.
loretoparisi,
1
@AmokHuginnsson - Z jakich systemów korzystasz? Rsync jest domyślnie dołączony do wszystkich popularnych dystrybucji Linuksa, w tym RHEL, CentOS, Debian i Ubuntu, i wierzę, że jest również w FreeBSD.
siliconrockstar
1
W przypadku dystrybucji pochodzących z RHEL: yum install rsync, lub w wersjach opartych na Debianie: apt-get install rsync. To nie jest problem, chyba że budujesz swój serwer z absolutnej bazy na własnym sprzęcie. rsync jest domyślnie instalowany na moich urządzeniach Amazon EC2, a także na moich urządzeniach ZeroLag i RackSpace.
siliconrockstar
2
rsync wydaje się być bardzo wolny w porównaniu do cp? Przynajmniej takie było moje doświadczenie.
Kojo
2
Na przykład, aby zignorować git reż:rsync -av --exclude='.git/' ../old-repo/ .
nycynik
40

Użyj smoły razem z fajką.

cd /source_directory
tar cf - --exclude=dir_to_exclude . | (cd /destination && tar xvf - )

Możesz nawet użyć tej techniki w ssh.

Kyle Butt
źródło
Takie podejście niepotrzebnie najpierw przetwarza źródło docelowe (i wyklucza określone katalogi w archiwum), a następnie rozdziela je w celu. Niepolecane!
Wouter Donders
4
@Waldheri się mylisz. To najlepsze rozwiązanie. Robi dokładnie to, czego zażądał OP i działa przy domyślnej instalacji większości systemów operacyjnych takich jak * nix. Tarowanie i odłączanie odbywa się w locie bez artefaktu systemu plików (w pamięci), koszt tego tar + untar jest znikomy.
AmokHuginnsson
@WouterDonders Tar to minimalny narzut. Nie stosuje kompresji.
Kyle Butt
9

Możesz używać findz tą -pruneopcją.

Przykład z man find:

       cd / source-reż
       odnaleźć . -nazwa .snapshot -prune -o \ (\! -name * ~ -print0 \) |
       cpio -pmd0 / dest-reż

       To polecenie kopiuje zawartość katalogu / source-dir do / dest-dir, ale pomija
       pliki i katalogi o nazwie .snapshot (i wszystko w nich zawarte). To także
       pomija pliki lub katalogi, których nazwa kończy się na ~, ale nie ich
       namioty Konstrukcja -prune -o \ (... -print0 \) jest dość powszechna. The
       Chodzi o to, że wyrażenie przed -prune pasuje do rzeczy, które są
       być przycinanym. Jednak sama akcja -prune zwraca true, więc
       następujące -o zapewnia, że ​​oceniana jest tylko prawa strona
       katalogi, które nie zostały przycięte (zawartość przycinanego
       katalogi nie są nawet odwiedzane, więc ich zawartość nie ma znaczenia).
       Wyrażenie po prawej stronie -o znajduje się tylko w nawiasach
       dla jasności. Podkreśla, że ​​akcja -print0 ma miejsce tylko
       dla rzeczy, które nie miały zastosowania - przycinała je. Ponieważ
       domyślny warunek `i 'między testami wiąże się mocniej niż -o, to
       i tak jest ustawieniem domyślnym, ale nawiasy pomagają pokazać, co się dzieje
       na.
Wstrzymano do odwołania.
źródło
Rekwizyty do znalezienia bardzo odpowiedniego przykładu bezpośrednio ze strony podręcznika.
David M
Wygląda dobrze! Jest to również dostępne w dokumentach internetowych . Niestety cpionie został jeszcze spakowany dla MSYS2.
underscore_d
3

możesz użyć tar, z opcją --exclude, a następnie rozpakuj ją w miejscu docelowym. na przykład

cd /source_directory
tar cvf test.tar --exclude=dir_to_exclude *
mv test.tar /destination 
cd /destination  
tar xvf test.tar

zobacz stronę man tar, aby uzyskać więcej informacji

ghostdog74
źródło
2

Podobne do pomysłu Jeffa (niesprawdzone):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
źródło
Przepraszam, ale tak naprawdę nie rozumiem, dlaczego 5 osób zagłosowało za tym, gdy było to wprawdzie niesprawdzone i wydaje się, że nie działa na prostym teście: próbowałem tego w podkatalogu /usr/share/iconsi od razu dostałem, find: paths must precede expression: 22x22gdzie ten drugi jest jednym z podkatalogów . Moje polecenie brzmiało find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(wprawdzie, jestem na MSYS2, więc naprawdę w /mingw64/share/icons/Adwaita, ale nie widzę, jak to wina MSYS2)
underscore_d
0
EXCLUDE="foo bar blah jah"                                                                             
DEST=$1

for i in *
do
    for x in $EXCLUDE
    do  
        if [ $x != $i ]; then
            cp -a $i $DEST
        fi  
    done
done

Nie przetestowano ...

Steve Lazaridis
źródło
To jest niepoprawne. Kilka problemów: Jak napisano, skopiuje plik, który nie powinien być wykluczony wiele razy (liczba elementów do wykluczenia, która w tym przypadku wynosi 4). Nawet jeśli spróbujesz skopiować „foo”, pierwszy element na liście wykluczeń, nadal zostanie on skopiowany, gdy dojdziesz do x = bar, a ja nadal jest foo. Jeśli nalegasz na zrobienie tego bez wcześniejszych narzędzi (np. Rsync), przenieś kopię do instrukcji if poza pętlę „for x in ...” i spraw, aby pętla „for x ...” zmieniła instrukcję logiczną w plik kopii (prawda). To powstrzyma Cię przed wielokrotnym kopiowaniem.
Eric Bringley,
0

zainspirowany odpowiedzią @ SteveLazaridis, która się nie powiedzie, oto funkcja powłoki POSIX - wystarczy skopiować i wkleić do pliku o nazwie cpxyout $PATHi uczynić go wykonywalnym ( chmod a+x cpr). [Źródło jest teraz utrzymywane w moim GitLab .

#!/bin/sh

# usage: cpx [-n|--dry-run] "from_path" "to_path" "newline_separated_exclude_list"
# limitations: only excludes from "from_path", not it's subdirectories

cpx() {
# run in subshell to avoid collisions
  (_CopyWithExclude "$@")
}

_CopyWithExclude() {
  case "$1" in
    -n|--dry-run) { DryRun='echo'; shift; } ;;
  esac

  from="$1"
  to="$2"
  exclude="$3"

  $DryRun mkdir -p "$to"

  if [ -z "$exclude" ]; then
      cp "$from" "$to"
      return
  fi

  ls -A1 "$from" \
    | while IFS= read -r f; do
        unset excluded
        if [ -n "$exclude" ]; then
          for x in $(printf "$exclude"); do
          if [ "$f" = "$x" ]; then
              excluded=1
              break
          fi
          done
        fi
        f="${f#$from/}"
        if [ -z "$excluded" ]; then
          $DryRun cp -R "$f" "$to"
        else
          [ -n "$DryRun" ] && echo "skip '$f'"
        fi
      done
}

# Do not execute if being sourced
[ "${0#*cpx}" != "$0" ] && cpx "$@"

Przykładowe użycie

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
źródło
Nie wydaje się pomocne stwierdzenie, że czyjaś odpowiedź „nie powiedzie się” bez wyjaśnienia, co jest z nią nie tak i jak to naprawić ...
podkreślenie
@underscore_d: prawda, z perspektywy czasu, zwłaszcza, że ​​nie pamiętam teraz, co się nie udało :-(
go2null
Wiele rzeczy: (1) kopiuje pliki wiele razy i (2) logika nadal kopiuje pliki do wykluczenia. Uruchom przez pętle za pomocą i = foo: zostanie skopiowany 3 razy zamiast 4 dla każdego innego pliku, np. I = test.txt.
Eric Bringley,
1
dzięki @EricBringley za wyjaśnienie braków odpowiedzi Steve'a. (Powiedział jednak, że to nie zostało przetestowane .)
go2null