find -exec mit mehreren Befehlen


429

Ich versuche, find -exec mit mehreren Befehlen ohne Erfolg zu verwenden. Weiß jemand, ob Befehle wie die folgenden möglich sind?

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

Grundsätzlich versuche ich, die letzte Zeile jeder txt-Datei im aktuellen Verzeichnis zu drucken und am Ende der Zeile ein Komma gefolgt vom Dateinamen zu drucken.



1
Haben Sie den Befehl nicht auf Ihrem System ausprobiert, um die Möglichkeit des Befehls zu prüfen?
Sriram

1
Von der findHandbuchseite: 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 ist defekt, Alternative bei ss64.com/bash/find.html
Keith M

Antworten:


657

findakzeptiert mehrere -execTeile des Befehls. Zum Beispiel:

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

Beachten Sie, dass in diesem Fall der zweite Befehl nur ausgeführt wird, wenn der erste erfolgreich zurückgegeben wird, wie von @Caleb erwähnt. Wenn beide Befehle unabhängig von Erfolg oder Misserfolg ausgeführt werden sollen, können Sie dieses Konstrukt verwenden:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;

wie man zweimal grep? Dies schlägt fehl: find ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "my_string" {} \;
Rajeev

51
@rajeev Der zweite Exec wird nur ausgeführt, wenn der Rückkehrcode für den ersten Erfolg erfolgreich ist. Andernfalls wird er übersprungen. Dies sollte wahrscheinlich in dieser Antwort vermerkt werden.
Caleb

1
Dies wäre die Antwort
Pylover

1
Beachten Sie die Verwendung von -nin einigen anderen Antworten, um die durch Echo erzeugte neue Zeile zu unterdrücken. Dies ist praktisch, wenn Ihr zweiter Befehl nur eine Ausgabezeile erzeugt und Sie möchten, dass sie leichter zu lesen sind.
William Turrell

Tolle Lösung. Klappt wunderbar.
Philippe Delteil


52

Einer der folgenden:

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"' _ {} \;

12
Wofür steht der Unterstrich vor {}?
Qed

2
@qed: Es ist ein Wegwerfwert, der den Platz von einnimmt $0. Versuchen Sie dies mit "foobar" anstelle von "_": find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- der Ausgabe: "[foobar] [/ usr / bin / find]".
Bis auf weiteres angehalten.

1
@XuWang: Ja, ich würde sagen, dass das der Fall ist. Wie Sie wissen, $0ist normalerweise der Programmname ( ARGV[0]).
Bis auf weiteres angehalten.

3
Für diese Methode ist es wichtig, dass das übergebene Skript sh -cin einfachen Anführungszeichen und nicht in doppelten Anführungszeichen steht. Ansonsten $1ist im falschen Bereich.
Nick

1
@ Nick-Anführungszeichen haben nichts damit zu tun - Sie können '$1'mit doppelten Anführungszeichen schreiben, solange Sie der Variablen ( "\$1") entkommen . Sie können auch anderen Zeichen entkommen ( "\"").
Camilo Martin

22

Ein anderer Weg ist wie folgt:

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

in einer Zeile

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • " multiple_cmd()" - ist eine Funktion
  • " export -f multiple_cmd" - exportiert es, damit jede andere Subshell es sehen kann
  • " find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - finde, dass die Funktion in deinem Beispiel ausgeführt wird

Auf diese Weise kann multiple_cmd so lang und komplex sein, wie Sie es benötigen.

Hoffe das hilft.


perfekt, genau das was ich brauchte!
Anentropic

funktioniert bei OSX nicht für mich
Thomas

@Thomas tut es, aber versuchen Sie diesen einen Liner, getestet in OSX. Ich habe ein Verzeichnis namens 'aaa' mit einigen Dateien / Verzeichnissen und CDd erstellt. Dann ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
Barlop

@barlop, ich werde es versuchen; Vielen Dank!
Thomas

14

Es gibt einen einfacheren Weg:

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

Alternative:

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

1
Dateinamen können Zeilenumbrüche enthalten. Aus diesem Grund hat find das Argument -print0 und xargs das Argument -0
abasterfield

@abasterfield Ich hoffe immer, nie in freier Wildbahn zu finden lol
Camilo Martin

1
Ich wollte "find ... -exec zcat {} | wc -l \;" was nicht funktioniert hat. Finden Sie jedoch ... | während -r-Datei lesen; Echo "$ file: zcat $file | wc -l"; erledigt funktioniert, also danke!
Greg Dougherty

Im obigen Kommentar habe ich "Back Ticks" um "zcat $ file | wc -l". Leider wandelt SO diese in Formatierungen um, daher habe ich sie als tatsächliche Antwort mit dem richtigen sichtbaren Code hinzugefügt
Greg Dougherty

1
@GregDougherty Sie können den Backticks entkommen `, um Backslashes wie folgt zu verwenden: \​`( Dies ist jedoch ein weiterer guter Grund, $()stattdessen zu verwenden ).
Camilo Martin

8

Ich weiß nicht, ob Sie dies mit find tun können, aber eine alternative Lösung wäre, ein Shell-Skript zu erstellen und dies mit find auszuführen.

lastline.sh:

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

Machen Sie das Skript ausführbar

chmod +x lastline.sh

Verwendung find:

find . -name "*.txt" -exec ./lastline.sh {} \;

7
Backticks sind veraltet. Bitte empfehlen Sie die Verwendung von $ (...), das besser lesbar, unabhängig von Schriftarten und einfach zu verschachteln ist. Vielen Dank.
Benutzer unbekannt

4

Die erste Antwort von Denis ist die Antwort, um das Problem zu lösen. Tatsächlich ist es jedoch kein Fund mehr mit mehreren Befehlen in nur einer Ausführung, wie der Titel vermuten lässt. Um den einen Exec mit mehreren Befehlen zu beantworten, müssen wir nach etwas anderem suchen, um es zu lösen. Hier ist ein Beispiel:

Behalten Sie die letzten 10000 Zeilen von .log-Dateien bei, die in den letzten 7 Tagen mit 1 exec-Befehl unter Verwendung mehrerer {} Verweise geändert wurden

1) Sehen Sie, was der Befehl für welche Dateien tun wird:

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

2) Mach es: (notiere nicht mehr "\>" sondern nur ">" dies ist erwünscht)

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


Aber das wird kaputt gehen, wenn einer der Dateinamen ein Leerzeichen hat, glaube ich.
Camilo Martin

4

Dank Camilo Martin konnte ich eine verwandte Frage beantworten:

Was ich tun wollte war

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

was nicht funktioniert hat. Jedoch,

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

funktioniert, also danke!


2

Erweiterung der Antwort von @ Tinker,

In meinem Fall musste ich ein command | command | commandInside -execerstellen, um sowohl den Dateinamen als auch den gefundenen Text in Dateien zu drucken, die einen bestimmten Text enthalten.

Ich konnte es tun mit:

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

Das Ergebnis ist:

    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

1
Sie können den Dateinamen und den grep'd-Inhalt auch in einer einzelnen Zeile drucken, indem Sie /dev/nullals zweites Argument grepmit einem -execParameter an den Befehl übergeben :find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Bill Feth

1

sollte xargs verwenden :)

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

ein anderer (arbeitet an osx)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'

3
Dies übersieht einen wichtigen Anwendungsfall für findSituationen, in denen die Anzahl der übereinstimmenden Dateien für eine Befehlszeile zu groß ist. -execist ein Weg, um diese Grenze zu umgehen. Bei der Weiterleitung an ein Versorgungsunternehmen fehlt dieser Vorteil.
Chris Johnson

xargs -nexistiert, um die Anzahl der Übereinstimmungen pro Aufruf zu wählen. xargs -n 1 foocmdwird foocmd {}für jedes Spiel ausgeführt.
AndrewF

0

Eine find+xargsAntwort.

Im folgenden Beispiel werden alle .htmlDateien gefunden und eine Kopie mit der .BAKangehängten Erweiterung (z . B. 1.html> 1.html.BAK) erstellt.

Einzelner Befehl mit mehreren Platzhaltern

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

Mehrere Befehle mit mehreren Platzhaltern

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

Dieser Befehl wird auch die Arbeit mit Dateien , die mit einem Bindestrich oder Leerzeichen enthalten , wie beispielsweise starten -my file.htmldank Parameter unter Angabe und die --nach cpdem Signal zu cpEnde Parameter und der Beginn der eigentlichen Dateinamen.

-print0 leitet die Ergebnisse mit Null-Byte-Terminatoren weiter.


für xargs-I {} definiert der Parameter {}als Platzhalter; Sie können einen beliebigen Platzhalter verwenden. -0Gibt an, dass die Eingabeelemente durch Null getrennt sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.