Jak mogę użyć dwóch poleceń bash w -exec polecenia find?

32

Czy możliwe jest użycie 2 poleceń w -execczęści findpolecenia?

Próbowałem czegoś takiego:

find . -name "*" -exec  chgrp -v new_group {}  ; chmod -v 770 {}  \;

i dostaję:

find: brakujący argument dla -exec
chmod: nie można uzyskać dostępu {}: brak takiego pliku lub katalogu
chmod: nie można uzyskać dostępu ;: brak takiego pliku lub katalogu

Luc M.
źródło

Odpowiedzi:

44

Jeśli chodzi o findpolecenie, możesz także po prostu dodać więcej -execpoleceń z rzędu:

find . -name "*" -exec chgrp -v new_group '{}' \; -exec chmod -v 770 '{}' \;

Zauważ, że w rezultacie to polecenie jest równoważne użyciu

chgrp -v plik nowa_grupa && chmod -v 770 plik

na każdym pliku.

Wszystkie find„s parametry, takie jak -name, -exec, -sizei tak dalej, są rzeczywiście testuje : findbędą działać je jeden po drugim, tak długo jak cały łańcuch dotychczas oceniano na prawdziwe . Tak więc każde kolejne -execpolecenie jest wykonywane tylko wtedy, gdy poprzednie zwróciły wartość true (tzn. 0Status wyjścia poleceń). Ale findrozumie także operatory logiczne, takie jak lub ( -o) i not ( !). Dlatego, aby użyć łańcucha -exectestów niezależnie od poprzednich wyników, należałoby użyć czegoś takiego:

find . -name "*" \( -exec chgrp -v new_group {} \; -o -exec chmod -v 770 {} \; \)
rozcietrzewiacz
źródło
4
+1: Tak, to najbardziej elegancki sposób na zrobienie tego. Jeśli możesz wyjaśnić, dlaczego używasz '{}'(apostrofy wokół nawiasów klamrowych), odwiedź: unix.stackexchange.com/q/8647/4485
użytkownik nieznany
1
@ użytkownik Niestety nie wiem, czy nadal jest to konieczne. Właśnie zrobiłem test i nie spotkałem się z sytuacją, w której wszystko by to zmieniło. Myślę, że to tylko „dobra praktyka”, która wymrze.
rozcietrzewiacz
2
Cytaty są ważne dla plików ze spacjami w nazwach.
naught101
17
find . -name "*" -exec sh -c 'chgrp -v new_group "$0" ; chmod -v 770 "$0"' {} \;
Glenn Jackman
źródło
@Gilles: Cuda -cdziwnego obchodzenia się z 0 $ sprawiają, że za każdym razem, gdy na niego patrzę, myślę, że to jest złe, ale zdecydowanie poprawne.
derobert
Podoba mi się definiowanie jawnej powłoki ...
djangofan
Ta odpowiedź (i odpowiedź Gilesa) wydaje się lepszą odpowiedzią na zadane pytanie sh -c.
14

Twoje polecenie jest najpierw analizowane przez powłokę na dwa polecenia oddzielone znakiem a ;, co jest równoważne nowej linii:

find . -name "*" -exec chgrp -v new_group {}
chmod -v 770 {} \;

Jeśli chcesz uruchomić polecenie powłoki, jawnie wywołaj powłokę za pomocą bash -c(lub sh -cjeśli nie obchodzi cię, że powłoka jest specjalnie bash):

find . -name "*" -exec sh -c 'chgrp -v new_group "$0"; chmod -v 770 "$0"' {} \;

Zwróć uwagę na użycie {}jako argumentu dla powłoki; jest to zerowy argument (który zwykle jest nazwą powłoki lub skryptu, ale nie ma to tutaj znaczenia), stąd też nazywany "$0".

Możesz przekazać wiele nazw plików do powłoki jednocześnie i sprawić, że powłoka będzie się przez nie iterować, będzie to szybsze. Podaję tutaj _jako nazwę skryptu, a następujące argumenty to nazwy plików, które for x(skrót do for x in "$@") iterują.

find . -name "*" -exec sh -c 'for x; do chgrp -v new_group "$x"; chmod -v 770 "$x"; done' _ {} +

Zauważ, że odkąd bash 4 lub w zsh, tutaj wcale nie musisz go znaleźć. W bash, uruchom shopt -s globstar(włóż to do swojego ~/.bashrc), aby aktywować tryb **/stojący dla globalnego katalogu rekurencyjnego. (W Zsh jest to aktywne przez cały czas.) Następnie

chgrp -v new_group -- **/*; chmod -v 770 -- **/*

lub jeśli chcesz, aby pliki były iterowane w kolejności

for x in **/*; do
  chgrp -v new_group -- "$x"
  chmod -v 770 -- "$x"
done

Jedną różnicą w stosunku do findpolecenia jest to, że powłoka ignoruje pliki kropkowe (pliki, których nazwa zaczyna się od a .). Aby je uwzględnić, w bash, pierwszy zestaw GLOBIGNORE=.:..; w zsh użyj **/*(D)jako wzorca globu.

Gilles „SO- przestań być zły”
źródło
Ta odpowiedź (i odpowiedź Glenna) wydaje się lepszą odpowiedzią na zadane pytanie sh -c.