znajdź -exec z wieloma poleceniami

429

Próbuję użyć find -exec z wieloma poleceniami bez powodzenia. Czy ktoś wie, czy możliwe są następujące polecenia?

find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;

Zasadniczo próbuję wydrukować ostatni wiersz każdego pliku txt w bieżącym katalogu i wydrukować na końcu wiersza, przecinek, po którym następuje nazwa pliku.

Andy
źródło
4
superuser.com/questions/236601/…
Ignacio Vazquez-Abrams
1
Jeśli chodzi o sprawdzenie możliwości polecenia, czy nie wypróbowałeś go w swoim systemie?
Sriram
1
Ze strony findpodręcznika: There are unavoidable security problems surrounding use of the -exec option; you should use the -execdir option instead.unixhelp.ed.ac.uk/CGI/man-cgi?find
JVE999
@ JVE999 link jest zepsuty, alternatywnie na ss64.com/bash/find.html
Keith M

Odpowiedzi:

658

findakceptuje wiele -execczęści polecenia. Na przykład:

find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;

Zauważ, że w takim przypadku drugie polecenie zostanie uruchomione tylko wtedy, gdy pierwsze powróci pomyślnie, jak wspomniano w @Caleb. Jeśli chcesz, aby oba polecenia były uruchamiane niezależnie od ich powodzenia lub niepowodzenia, możesz użyć tej konstrukcji:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
Majstrować
źródło
jak dwa razy grep to się nie udaje: znajdź ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "my_string" {} \;
rajeev
51
@rajeev Drugi exec będzie działał tylko wtedy, gdy kod powrotu dla pierwszego sukcesu powróci, w przeciwnym razie zostanie pominięty. Powinno to prawdopodobnie zostać odnotowane w tej odpowiedzi.
Caleb
1
To byłaby odpowiedź
pylover
1
Zwróć uwagę na użycie -nniektórych innych odpowiedzi do tłumienia nowej linii generowanej przez echo, co jest przydatne, jeśli drugie polecenie generuje tylko jeden wiersz wyniku i chcesz, aby były łatwiejsze do odczytania.
William Turrell,
Świetne rozwiązanie. Działa jak marzenie.
Philippe Delteil,
98
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
Avari
źródło
3
Jeśli chcesz uruchomić Bash zamiast Bourne, możesz także użyć ... -exec bash -c ...zamiast niego ... -exec sh -c ....
Kenny Evitt,
9
Nigdy nie osadzaj {}w kodzie powłoki. Zobacz unix.stackexchange.com/questions/156008/…
Kusalananda
3
+1 @Kusalananda Wstrzykiwanie nazw plików jest kruche i niepewne. Użyj parametrów. patrz SC2156
pambda,
52

Jeden z następujących:

find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;

find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;

find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;
Wstrzymano do odwołania.
źródło
12
Do czego służy znak podkreślenia przed {}?
qed
2
@qed: Jest to wartość jednorazowa, która zajmuje miejsce $0. Spróbuj tego z „foobar” zamiast „_”: find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- wyjście: „[foobar] [/ usr / bin / find]”.
Wstrzymano do odwołania.
1
@XuWang: Tak, powiedziałbym, że tak jest. Jak wiesz, $0zwykle jest to nazwa programu ( ARGV[0]).
Wstrzymano do odwołania.
3
Dla tej metody kluczowe jest, aby skrypt przekazywany sh -cbył w pojedynczych cudzysłowach, a nie w podwójnych. W przeciwnym razie $1jest w niewłaściwym zakresie.
Nick
1
Cytaty @Nick nie mają z tym nic wspólnego - możesz pisać '$1'z podwójnymi cudzysłowami, o ile unikasz zmiennej ( "\$1"). Możesz także uciec innym postaciom ( "\"").
Camilo Martin
22

Inny sposób jest taki:

multiple_cmd() { 
    tail -n1 $1; 
    ls $1 
}; 
export -f multiple_cmd; 
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;

w jednej linii

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • multiple_cmd()” - jest funkcją
  • export -f multiple_cmd” - wyeksportuje go, aby każda inna podpowłoka mogła go zobaczyć
  • find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;” - znajdź, który wykona funkcję na twoim przykładzie

W ten sposób multiple_cmd może być tak długie i tak złożone, jak potrzebujesz.

Mam nadzieję że to pomoże.

al3x2ndru
źródło
idealnie, właśnie tego potrzebowałem!
Anentropic
nie działa dla mnie na OSX
Thomas
@ Thomas robi, ale wypróbuj ten jeden liner, przetestowany w systemie OSX. Zrobiłem katalog o nazwie „aaa” z kilkoma plikami / katalogami i CDd. Następnie ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
barlop
@barlop, spróbuję; dzięki!
Thomas
14

Jest prostszy sposób:

find ... | while read -r file; do
    echo "look at my $file, my $file is amazing";
done

Alternatywnie:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Camilo Martin
źródło
1
nazwy plików mogą mieć w sobie nowe linie, dlatego find ma argument -print0, a xargs ma argument -0
abasterfield
@abasterfield Zawsze mam nadzieję, że nigdy nie znajdę tych w dzikim lol
Camilo Martin
1
chciałem „znaleźć ... -exec zcat {} | wc -l \;” co nie działało. Jednak znajdź ... | podczas odczytu pliku -r; wykonaj echo „$ file: zcat $file | wc -l”; Gotowe działa, więc dziękuję!
Greg Dougherty,
W powyższym komentarzu mam „back ticks” wokół „zcat $ file | wc -l”. Niestety SO zmienia je w formatowanie, więc dodałem je jako rzeczywistą odpowiedź z widocznym poprawnym kodem
Greg Dougherty
1
@GregDougherty Możesz uciec się od tyknięć `, aby użyć odwrotnych ukośników w ten sposób: \​`(ale to kolejny dobry powód, aby użyć $()zamiast tego).
Camilo Martin
8

Nie wiem, czy możesz to zrobić za pomocą find, ale alternatywnym rozwiązaniem byłoby utworzenie skryptu powłoki i uruchomienie go za pomocą find.

lastline.sh:

echo $(tail -1 $1),$1

Spraw, aby skrypt był wykonywalny

chmod +x lastline.sh

Użyj find:

find . -name "*.txt" -exec ./lastline.sh {} \;
Andrea Spadaccini
źródło
7
backticks są przestarzałe, zachęcaj do używania $ (...), który jest lepiej czytelny, niezależny od czcionek i łatwy do zagnieżdżenia. Dziękuję Ci.
użytkownik nieznany
4

Pierwsza odpowiedź Denisa to odpowiedź na problem. Ale tak naprawdę nie jest to znalezisko z kilkoma poleceniami w jednym exec, jak sugeruje tytuł. Aby odpowiedzieć jednemu exec za pomocą kilku poleceń, będziemy musieli poszukać czegoś innego do rozwiązania. Oto przykład:

Zachowaj ostatnie 10000 wierszy plików .log, które zostały zmodyfikowane w ciągu ostatnich 7 dni za pomocą 1 polecenia exec z referencjami severals {}

1) zobacz, co zrobi polecenie na jakich plikach:

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;

2) Zrób to: (nie zaznaczaj więcej „\>”, ale tylko „>” jest to pożądane)

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;

Eric Duruisseau
źródło
Ale to pęknie, jeśli jeden z nazw plików ma spację, jak sądzę.
Camilo Martin,
4

Dzięki Camilo Martinowi mogłem odpowiedzieć na powiązane pytanie:

Chciałem to zrobić

find ... -exec zcat {} | wc -l \;

co nie działało. Jednak,

find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done

działa, więc dziękuję!

Greg Dougherty
źródło
2

Rozszerzając odpowiedź @ Tinker,

W moim przypadku musiałem zrobić command | command | commandwewnątrz, -execaby wydrukować zarówno nazwę pliku, jak i znaleziony tekst w plikach zawierających określony tekst.

Byłem w stanie to zrobić z:

find . -name config -type f \( -exec  grep "bitbucket" {} \; -a -exec echo {} \;  \) 

wynik to:

    url = git@bitbucket.org:a/a.git
./a/.git/config
    url = git@bitbucket.org:b/b.git
./b/.git/config
    url = git@bitbucket.org:c/c.git
./c/.git/config
użytkownik9869932
źródło
1
Możesz także wydrukować nazwę pliku i zawartość grep'd w jednym wierszu, przekazując /dev/nulljako drugi argument do greppolecenia z jednym -execparametrem:find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Bill Feth
1

należy użyć xargs :)

find *.txt -type f -exec tail -1 {} \; | xargs -ICONSTANT echo $(pwd),CONSTANT

inny (działa na systemie OSX)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
smapira
źródło
3
Pomija to główny przypadek użycia find- w sytuacjach, gdy liczba pasujących plików jest zbyt duża dla wiersza poleceń. -execjest sposobem na obejście tego limitu. Rurociągi do mediów tracą tę korzyść.
Chris Johnson
xargs -nistnieje, aby wybrać liczbę dopasowań dla każdego wywołania. xargs -n 1 foocmdwykona się foocmd {}dla każdego meczu.
AndrewF,
0

find+xargsOdpowiedź.

Poniższy przykład wyszukuje wszystkie .htmlpliki i tworzy kopię z .BAKdołączonym rozszerzeniem (np. 1.html> 1.html.BAK).

Jedno polecenie z wieloma symbolami zastępczymi

find . -iname "*.html" -print0 | xargs -0 -I {} cp -- "{}" "{}.BAK"

Wiele poleceń z wieloma symbolami zastępczymi

find . -iname "*.html" -print0 | xargs -0 -I {} echo "cp -- {} {}.BAK ; echo {} >> /tmp/log.txt" | sh

# if you need to do anything bash-specific then pipe to bash instead of sh

To polecenie będzie również działać z plikami, które zaczynają się od myślnika lub zawierają spacje, takie jak -my file.htmldzięki cytowaniu parametrów i --po cpktórym sygnalizuje cpkoniec parametrów i początek rzeczywistych nazw plików.

-print0 potokuje wyniki za pomocą terminatorów zerowych.


dla xargs-I {} parametr definiuje {}jako wypełniacz; możesz użyć dowolnego symbolu zastępczego; -0wskazuje, że elementy wejściowe są rozdzielone zerami.

ccpizza
źródło