znaleźć -exec funkcję powłoki w systemie Linux?

185

Czy istnieje sposób na findwykonanie funkcji zdefiniowanej w powłoce? Na przykład:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Wynikiem tego jest:

find: dosomething: No such file or directory

Czy istnieje sposób, aby uzyskać find„S -execzobaczyć dosomething?

alxndr
źródło

Odpowiedzi:

262

Ponieważ tylko powłoka wie, jak uruchamiać funkcje powłoki, musisz uruchomić powłokę, aby uruchomić funkcję. Musisz także zaznaczyć swoją funkcję do eksportu export -f, w przeciwnym razie podpowłoka nie odziedziczy ich:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
Adam Rosenfield
źródło
7
Pokonałeś mnie. Nawiasem mówiąc, zamiast wstawiać nawiasy klamrowe w cudzysłowie $0.
Wstrzymano do odwołania.
20
Jeśli find . -exec bash -c 'dosomething "$0"' {} \;go użyjesz , będzie on obsługiwał spacje (i inne dziwne znaki) w nazwach plików ...
Gordon Davisson
3
@alxndr: to nie powiedzie się na nazwach plików z podwójnymi cudzysłowami, cudzysłowami, znakami dolara, niektórymi kombinacjami ucieczki itp.
Gordon Davisson
4
Zauważ też, że jakiekolwiek funkcje, które może wywoływać twoja funkcja , nie będą dostępne, dopóki nie wyeksportujesz -f również.
hraban
3
export -fBędzie działać tylko w niektórych wersjach bash. To nie jest posix, nie crossplatforn, /bin/shbędzie miał z tym błąd
Роман Коптев 17.04.16
120
find . | while read file; do dosomething "$file"; done
Jac
źródło
10
Niezłe rozwiązanie. Nie wymaga eksportowania funkcji ani robienia bałaganu wokół uciekających argumentów i jest prawdopodobnie bardziej wydajny, ponieważ nie tworzy spawów w celu wykonania każdej funkcji.
Tom
19
Pamiętaj jednak, że spowoduje to uszkodzenie nazw plików zawierających nowe linie.
chepner
3
Jest to bardziej „skorupowe”, ponieważ twoje globalne zmienne i funkcje będą dostępne, bez tworzenia całkowicie nowej powłoki / środowiska za każdym razem. Nauczyłem się tego na własnej skórze po wypróbowaniu metody Adama i napotkaniu różnego rodzaju problemów środowiskowych. Ta metoda nie powoduje również uszkodzenia powłoki bieżącego użytkownika przy wszystkich eksportach i wymaga mniejszej przewidywalności.
Ray Foss,
TAK DUŻO, niż akceptowana odpowiedź bez podpowłoki! MIŁY! Dziękuję Ci!
Bill Heller
2
naprawiłem także mój problem, zmieniając while readsię na pętlę for; for item in $(find . ); do some_function "${item}"; done
user5359531
22

Powyższa odpowiedź Jaka jest świetna, ale ma kilka pułapek, które można łatwo pokonać:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Używa wartości null jako separatora zamiast kanału, więc nazwy plików z kanałami będą działać. Używa także -rflagi, która wyłącza ucieczkę ukośnika odwrotnego, bez tego ukośniki w nazwach plików nie będą działać. Usuwa się również IFS, aby potencjalne końcowe spacje w nazwach nie były odrzucane.

pajamian
źródło
1
Jest dobry, /bin/bashale nie działa /bin/sh. Jaka szkoda.
Роман Коптев 17.04.16
@ РоманКоптев Jak szczęście, że przynajmniej działa w / bin / bash.
sdenham,
21

Dodaj cytaty, {}jak pokazano poniżej:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

To koryguje wszelkie błędy spowodowane przez znaki specjalne zwracane przez find, na przykład pliki z nawiasami w nazwie.

Wagner
źródło
1
To nie jest poprawny sposób użycia {}. Spowoduje to uszkodzenie pliku zawierającego podwójne cudzysłowy. touch '"; rm -rf .; echo "I deleted all you files, haha. Ups
gniourf_gniourf
Tak, to bardzo źle. Można go wykorzystać przez zastrzyki. Bardzo niebezpieczne. Nie używaj tego!
Dominik
1
@kdubs: Użyj $ 0 (bez cudzysłowu) w wierszu polecenia i przekaż nazwę pliku jako pierwszy argument: -exec bash -c 'echo $0' '{}' \;Zauważ, że podczas używania bash -c$ 0 jest pierwszym argumentem, a nie nazwą skryptu.
sdenham
10

Przetwarzanie wyników luzem

Aby zwiększyć wydajność, wiele osób używa xargsdo przetwarzania wyników luzem, ale jest to bardzo niebezpieczne. Z tego powodu wprowadzono alternatywną metodę, findktóra wykonuje wyniki masowo.

Zauważ jednak, że ta metoda może zawierać pewne zastrzeżenia, takie jak na przykład wymóg POSIX-, findaby mieć {}na końcu polecenia.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findprzekaże wiele wyników jako argumenty do pojedynczego wywołania bashi forpętla iteruje te argumenty, wykonując funkcję dosomethingna każdym z nich.

Powyższe rozwiązanie rozpoczyna argumenty od $1, i dlatego istnieje _(który reprezentuje$0 ).

Przetwarzanie wyników jeden po drugim

W ten sam sposób uważam, że należy poprawić przyjętą najwyższą odpowiedź

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Jest to nie tylko bardziej rozsądne, ponieważ argumenty powinny zawsze zaczynać się od $1, ale także użycie $0może prowadzić do nieoczekiwanego zachowania, jeśli nazwa pliku zwrócona przez findma specjalne znaczenie dla powłoki.

Dominik
źródło
9

Niech skrypt sam się wywołuje, przekazując każdy znaleziony element jako argument:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Po uruchomieniu skryptu sam znajduje to, czego szukasz, i nazywa się, przekazując każdy wynik znalezienia jako argument. Kiedy skrypt jest uruchamiany z argumentem, wykonuje polecenia na argumencie, a następnie kończy działanie.

Mike Maready
źródło
fajny pomysł, ale zły styl: używa tego samego skryptu do dwóch celów. jeśli chcesz zmniejszyć liczbę plików w koszu /, możesz połączyć wszystkie skrypty w jeden, który na początku ma dużą klauzulę. bardzo czyste rozwiązanie, prawda?
user829755
nie wspominając o tym, że to się nie powiedzie, find: ‘myscript.sh’: No such file or directoryjeśli zacznie się jako bash myscript.sh...
Camusensei
5

Dla tych z Was, którzy szukają funkcji bash, która wykona dane polecenie na wszystkich plikach w bieżącym katalogu, skompilowałem jedną z powyższych odpowiedzi:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Zauważ, że łamie się w nazwach plików zawierających spacje (patrz poniżej).

Jako przykład weź tę funkcję:

world(){
    sed -i 's_hello_world_g' "$1"
}

Powiedzmy, że chciałem zmienić wszystkie wystąpienia hello na world we wszystkich plikach w bieżącym katalogu. Chciałbym zrobić:

toall world

Aby być bezpiecznym z dowolnymi symbolami w nazwach plików, użyj:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(ale potrzebujesz takiego, findktóry obsługuje -print0np. GNU find).

Jason Basanese
źródło
3

Najłatwiej znajduję się w następujący sposób, powtarzając dwa polecenia w jednym do

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done
edib
źródło
3

Dla porównania unikam tego scenariusza, używając:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Pobierz wyjście z find w bieżącym pliku skryptu i iteruj po wyjściu, jak chcesz. Zgadzam się z zaakceptowaną odpowiedzią, ale nie chcę ujawniać funkcji poza moim plikiem skryptu.

dinesh saini
źródło
ma to ograniczenia wielkości
Richard
2

Wykonywanie funkcji w ten sposób nie jest możliwe .

Aby temu zaradzić, możesz umieścić swoją funkcję w skrypcie powłoki i wywołać ją z find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Teraz użyj go w find jako:

find . -exec dosomething.sh {} \;
kodaddict
źródło
Próbowałem uniknąć więcej plików w moim ~ / bin. W każdym razie dzięki!
alxndr
Zastanawiałem się nad oddaniem głosu, ale samo rozwiązanie nie jest złe. Proszę użyć poprawnego cytowania: dosomething $1=> dosomething "$1"i poprawnie uruchomić plik za pomocąfind . -exec bash dosomething.sh {} \;
Camusensei
2

Umieść funkcję w osobnym pliku i finduruchom ją.

Funkcje powłoki są wewnętrzne względem powłoki, w której są zdefiniowane; findnigdy ich nie zobaczy.

Angus
źródło
Gotcha; ma sens. Próbowałem jednak uniknąć więcej plików w moim ~ / bin.
alxndr
2

Aby zapewnić uzupełnienia i wyjaśnienia niektórych innych odpowiedzi, jeśli używasz opcji zbiorczej dla execlub execdir( -exec command {} +) i chcesz odzyskać wszystkie argumenty pozycyjne, musisz rozważyć obsługę $0z bash -c. Mówiąc bardziej konkretnie, rozważ poniższe polecenie, które używa bash -cjak zasugerowano powyżej, i po prostu wyświetla ścieżki plików kończące się na „.wav” z każdego znalezionego katalogu:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Podręcznik bash mówi:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

Oto 'check $@'ciąg polecenia i _ {}argumenty występujące po nim. Zauważ, że $@jest to specjalny parametr pozycyjny w bash, który rozszerza się na wszystkie parametry pozycyjne, zaczynając od 1 . Zauważ również, że przy tej -copcji pierwszy argument jest przypisywany do parametru pozycyjnego $0. Oznacza to, że jeśli spróbujesz uzyskać dostęp do wszystkich parametrów pozycyjnych za pomocą $@, otrzymasz parametry tylko od początku $1do góry. Dlatego odpowiedź Dominika zawiera _argument, który jest fikcyjnym argumentem służącym do wypełnienia parametru $0, więc wszystkie argumenty, które chcemy, będą dostępne później, jeśli użyjemy $@na przykład rozszerzenia parametrów lub pętli for jak w tej odpowiedzi.

Oczywiście, podobnie jak w przypadku zaakceptowanej odpowiedzi, bash -c 'shell_function $0 $@'działałby również jawnie $0, ale trzeba pamiętać, że $@nie będzie działać zgodnie z oczekiwaniami.

firedrillsergeant
źródło
0

Nie bezpośrednio, nie. Find wykonuje się w osobnym procesie, a nie w powłoce.

Utwórz skrypt powłoki, który wykonuje to samo zadanie co twoja funkcja, i znajdź -execto.

Laurence Gonsalves
źródło
Próbowałem uniknąć więcej plików w moim ~ / bin. W każdym razie dzięki!
alxndr
-2

Unikałbym używania -execcałkowicie. Dlaczego nie użyć xargs?

find . -name <script/command you're searching for> | xargs bash -c
Barry
źródło
W tym czasie IIRC próbowało zmniejszyć ilość wykorzystywanych zasobów. Pomyśl o znalezieniu milionów pustych plików i ich usunięciu.
alxndr