znajdź -exec w skrypcie bash ze zmiennym rozszerzaniem

14

Próbuję uruchomić polecenie podobne do poniższego w skrypcie bash. Powinien przeszukiwać wszystkie podfoldery $sourcediri kopiować wszystkie pliki określonego typu na poziom główny $targetdir.

#!/bin/bash

# These are set as arguments to the script, not hard-coded
sourcedir="/path/to/sourcedir"
targetdir="/path/to/targetdir"

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2/`basename "$1"`"' "{}" "$targetdir" \;

To wydaje się być dość blisko, oprócz tego, że {}nie jest przekazywana jako $2do-exec sh -c ...

Chciałbym to zrobić tak blisko „właściwej drogi”, jak to możliwe, z tolerancją dla znaków specjalnych w nazwach plików (w szczególności znaków pojedynczego cudzysłowu).

Edycja: widzę ludzi sugerujących użycie xargslub tworzenie łańcuchów argumentów. Miałem wrażenie, że jest to w porządku tylko w przypadku ograniczonej liczby argumentów. Jeśli mam na przykład tysiące plików .jpg, które próbuję skopiować z wielu katalogów galerii do gigantycznego katalogu pokazu slajdów, czy rozwiązania łańcuchowe argumentów nadal będą działać?

Edycja 2: Mój problem polegał na tym, że brakowało _mi przed pierwszą opcją sh w -execpoleceniu. Dla każdego, kto jest ciekawy, jak sprawić, by polecenie find działało, dodaj _i wszystko będzie dobrze:

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2"' _ "{}" "$targetdir" \;

Przyjąłem odpowiedź poniżej, ponieważ spełnia to samo zadanie, ale jest bardziej wydajne i eleganckie.

Mateusz
źródło
4
Właśnie dlatego xargskiedykolwiek został stworzony, aby automatycznie obsługiwać ogromne ilości argumentów na zwykłych komendach, które miały ograniczenia. Należy również wziąć pod uwagę, że większość maksymalnych limitów argumentów została znacznie ulepszona dla standardowych narzędzi GNU. Zauważysz także poprawę wydajności, unikając wszystkich tych forków procesowych, co w tysiącach plików jest istotne.
JM Becker
Za pomocą gnu-find i + zamiast „;” możesz także obsługiwać wiele argumentów jednocześnie za pomocą find. Zapisujesz skomplikowane przekazywanie argumentów za pomocą opcji -print0.
użytkownik nieznany
@userunknown: odpowiadam na to poniżej twojej odpowiedzi.
JM Becker
@ użytkownik nieznany Cóż, UWIELBIAM ten kod. Jest przynajmniej w pełni zgodny z POSIX i będzie działał bez żadnych GNU na komputerze. Są chwile, kiedy jest to potrzebne, szczególnie na serwerach w pracy.
składnia błąd

Odpowiedzi:

6

Chcesz skopiować pliki określonego typu do określonego katalogu? Najlepiej to zrobić xargsi nawet tego nie potrzebujesz sh. Jest to bardziej odpowiedni sposób, aby to zrobić, powinien również działać bardziej wydajnie.

find "$sourcedir" -type f -name "*.type" | xargs cp -t targetdir

Jeśli potrzebujesz obsługiwać specjalne nazwy plików, użyj ich NULLjako separatora

find "$sourcedir" -type f -name "*.type" -print0 | xargs -0 cp -t "$targetdir"
JM Becker
źródło
1
Na 2. przypadku, nie zapomnij dodać -print0do findi -0doxargs
SiegeX
@ SiegeX już to robił, zanim zauważyłem twój komentarz.
JM Becker
Również NULLnie jest konieczne, jeśli używasz '{}', jest ważne, gdy nie używasz. Prawdziwą korzyścią między innymi, oprócz POSIXzgodności, jest wydajność.
JM Becker
6

Musisz przekazać {}jako argument do powłoki, a następnie zapętlić każdy argument.

find "$sourcedir" -type f -name "*.type" -exec sh -c 'for f; do cp "$f" "$0"; done' "$targetdir" {} +

Uwaga : W ten sposób pierwszy argument do powłoki jest nazwą powłoki , możemy to wykorzystać, przekazując nazwę jako, $targetdira następnie używając specjalnego parametru $0w skrypcie powłoki, aby uzyskać dostęp do tego katalogu docelowego.

SiegeX
źródło
1
"$targetdir"nie jest rozwijany w pojedynczych cudzysłowach.
enzotib
5

Jeśli nie wierzysz w kościół xargs:

find "$sourcedir" -type f -name "*.mp3" -exec cp -t "$targetdir" {} +

Wyjaśnienie:

cp -t a b c d 

kopiuje b, cid do docelowego katalogu a.

-exec cmd {} +

wywołuje polecenie na dużej grupie plików jednocześnie, nie jeden po drugim (co jest standardem, jeśli używasz ";"zamiast +). Dlatego musisz wyciągnąć katalog docelowy na przód i oznaczyć go jako cel.

Działa to dla gnu-find i może nie działać w przypadku innych implementacji find. Oczywiście zależy to również od flagi -t.

TechZilla ma oczywiście rację, ponieważ shnie jest potrzebna do wywołania cp.

Jeśli nie używasz xargs, co jest w większości przypadków niepotrzebne w połączeniu z find, oszczędzasz się od nauki flagi -print0i -0.

nieznany użytkownik
źródło
1
Oczywiście, która metoda może być preferowana, jest nieco subiektywna. To powiedziawszy, istnieją powody, aby nadal korzystać xargs. Największy przykład, co jeśli nie znajdziesz plików? forCały czas używam xargs zamiast ogólnych pętli, findma znacznie mniejszy zakres. Ponadto findobsługuje tylko to, że +jeśli używasz GNU find, nie jest to zdefiniowane w POSIX. Więc chociaż powinieneś swobodnie wybierać findsamemu, nie robi wszystkiego, co robi xargs. Rozważając GNU xargs, dostajesz także to, -Pco robi wielordzeniowy. xargsNiezależnie od tego warto się uczyć .
JM Becker
Mówiąc subiektywnie, moja opinia oczywiście się różni. Z drugiej strony obiektywnie twoja odpowiedź jest poprawna. Jest to jedno z niewielu najlepszych dostępnych rozwiązań.
JM Becker
@TechZilla: Mam nadzieję, że ponownie odwiedzę tę stronę, gdy find zacznie obsługiwać wywoływanie równoległe. :) W większości przypadków podczas kopiowania / przenoszenia szybkość dysku będzie czynnikiem ograniczającym, ale dyski SSD mogą zmienić obraz. Masz rację, że dostarczenie rozwiązania innego niż GNU jest dobrą rzeczą. W przeciwnym razie rozwiązanie find jest obiektywnie krótsze, prostsze i wykorzystuje tylko dwa procesy.
użytkownik nieznany
0
read -p "SOURCE: " sourcedir
read -p "TYPE: " type
read -p "TARGET: " targetdir
find -L $sourcedir -iname "*.$type" -exec cp -v {} $targetdir \;
Berthad33
źródło
3
Rozważ dodanie wyjaśnienia swojego rozwiązania.
HalosGhost,