/ / Optimaler Weg, rekursiv Dateien zu finden, die einem oder mehreren Mustern entsprechen - bash, grep, find

Optimaler Weg, rekursiv Dateien zu finden, die mit einem oder mehreren Mustern übereinstimmen - bash, grep, find

Ich muss ein Shell-Skript optimieren, aber nach einer Woche ist es mir nicht gelungen, es ausreichend zu optimieren.

Ich muss rekursiv nach .c .h und .cpp-Dateien in einem Verzeichnis suchen und prüfen, ob ein solches Wort existiert: "float short unsigned fortfahren für signed void default goto sizeof volatile tun wenn statisch während"

words=$(echo $@ | sed "s/ /\|/g")

files=$(find $dir -name "*.cpp" -o -name "*.c" -o -name "*.h" )

for file in  $files; do
(
test=$(grep -woh "$words" "$file" | sort -u | awk "{print}" ORS=" ")
if [ "$test" != "" ] ; then
echo "$(realpath $file) contains : $test"
fi
)&
done
wait

Ich habe versucht mit xargs und -exec, aber kein Ergebnis, ich muss dieses Ergebnisformat behalten:

/usr/include/c++/6/bits/stl_set.h enthält: Standard für wenn ungültig

Vielleicht kannst du mir helfen (um es zu optimieren) ..

EDIT: Ich muss ein Vorkommen von jedem Wort halten JA: während, für, volatile ... NOPE: während, für, für flüchtige ...

Antworten:

0 für die Antwort № 1

Wenn Sie daran interessiert sind, alle Dateien zu finden, die mindestens eine Übereinstimmung mit einem Ihrer Muster haben, können Sie verwenden globstar:

shopt -s globstar
oldIFS=$IFS; IFS="|"; patterns="$*"; IFS=$oldIFS  # make a | delimited string from arguments
grep -lwE "$patterns" **/*.c **/*.h **/*.cpp       # list files with matching patterns

globstar

Wenn gesetzt, wird das Muster '**' in einem Dateinamen verwendetErweiterungskontext passt alle Dateien und null oder mehr Verzeichnisse und Unterverzeichnisse an. Wenn auf das Muster ein '/' folgt, nur Verzeichnisse und Unterverzeichnisse stimmen überein.


0 für die Antwort № 2

Hier ist ein Ansatz, der das gewünschte Format beibehält und die Verwendung von find und bash looping überflüssig macht:

words="float|short|unsigned|continue|for|signed|void|default|goto|sizeof|volatile|do|if|static|while"
grep  -rwoE --include "*.[ch]" --include "*.cpp" "$words" path | awk -F: "$1!=last{printf "%s%s: contains %s",r,$1,$2; last=$1; r=ORS; delete a; a[$2]} $1==last && !($2 in a){printf " %s",$2; a[$2]} END{print""}"

Wie es funktioniert

  • grep -rwoE --include "*.[ch]" --include "*.cpp" "$words" Pfad

    Dies führt rekursiv durch Verzeichnisse, die mit beginnen path nur in Dateien suchen, deren Namen mit den Globs übereinstimmen *.[ch] oder *.cpp.

  • awk -F: "$1!=last{printf "%s%s: contains %s",r,$1,$2; last=$1; r=ORS; delete a; a[$2]} $1==last{printf " %s",$2} END{print""}"

    Dieser awk Befehl formatiert die Ausgabe von grep passend zu Ihrer gewünschten Ausgabe. Das Skript verwendet eine Variable last und Array a. last verfolgt, auf welcher Datei wir sind und a enthält die Liste der bisher gesehenen Wörter. Ausführlicher:

    • -F:

      Dies sagt awk zu verwenden : als Feldtrennzeichen. Auf diese Weise ist das erste Feld der Dateiname und das zweite ist das gefundene Wort. (Einschränkung: Dateinamen, die einschließen : werden nicht unterstützt.)

    • "$ 1! = Letzte {printf"% s% s: enthält% s ", r, $ 1, $ 2; last = $ 1; r = ORS; löschen a; a [$ 2]}

      Jedes Mal, wenn der Dateiname $1, stimmt nicht mit der Variablen überein lastWir starten die Ausgabe für eine neue Datei. Dann aktualisieren wir last um den Namen dieser neuen Datei zu enthalten. Wir löschen dann das Array a und dann Schlüssel zuweisen $2 zu einem neuen Array a.

    • $1==last && !($2 in a){printf " %s",$2; a[$2]}

      Wenn der aktuelle Dateiname derselbe wie der vorherige ist und das aktuelle Wort vorher nicht gesehen wurde, drucken wir das neue gefundene Wort aus. Wir fügen auch dieses Wort hinzu, $2 als Schlüssel zum Array a.

    • END{print""}

      Dadurch wird ein endgültiges Zeilenumbruchzeichen (Datensatztrennzeichen) gedruckt.

Mehrzeilige Version des Codes

Für diejenigen, die ihren Code über mehrere Zeilen verteilt bevorzugen:

grep  -rwoE 
--include "*.[ch]" 
--include "*.cpp" 
"$words" path |
awk -F: "
$1!=last{
printf "%s%s: contains %s",r,$1,$2
last=$1
r=ORS
delete a
a[$2]
}
$1==last && !($2 in a){
printf " %s",$2; a[$2]
}
END{
print""
}"

0 für die Antwort № 3

Sie sollten das meiste davon mit einem einzigen tun können grep Befehl:

grep -Rw $dir --include *.c --include *.h --include *.cpp -oe "$words"

Dies wird es in das Datei: Word-Format bringen, so dass es nur übrig bleibt, es zu ändern, um die gewünschte Ausgabe zu erzeugen.

echo $output | sed "s/:/ /g" | awk "{print $1 " contains : " $2}"

Dann können Sie hinzufügen | sort -u um nur einzelne Vorkommnisse für jedes Wort in jeder Datei zu erhalten.


#!/bin/bash

#dir=.
words=$(echo $@ | sed "s/ /\|/g")

grep -Rw $dir --include *.c --include *.h --include *.cpp -oe "$words" 
| sort -u 
| sed "s/:/ /g" 
| awk "{print $1 " contains : " $2}"