2016-03-29 2 views
2

Ich möchte ein Skript machen, dass die folgenden automatisch tun können:Wie erstellt man ein Skript, um mehrere Greps über eine Datei zu machen?

grep 'string1' file.txt | grep 'string2' | grep 'string3' ... | grep 'stringN' 

Die Idee ist, dass das Skript wie folgt ausgeführt werden können:

myScript.sh file.txt string1 string2 string3 ... stringN 

und das Skript hat alle Linien zurück von file.txt, die alle Zeichenfolgen enthalten.

Zum Beispiel, wenn file.txt sieht wie folgt aus:

hello world 
hello world run 
hello planet world 

Und ich kann ein grep wie diese machen:

grep hello file.txt | grep world 

und ich bekomme:

hello world 
hello world run 
hello planet world 

Ich möchte Machen Sie ein Skript, das dies automatisch vornimmt, mit einer undefinierten Anzahl von Strings als Parameter.

Ich fand, dass es schwierig ist, dies zu erreichen, da die Anzahl der Strings variabel sein kann. Zuerst habe ich versucht, ein Array args wie dies in myScript.sh genannt zu schaffen: mit dem Zweck

#!/bin/bash 
args=("[email protected]") 

der Argumente zu speichern. Ich weiß, dass die meine file.txt sein wird und der Rest sind die Saiten, die ich in den verschiedenen Greps verwenden muss, aber ich weiß nicht, wie man vorgeht und ob dies der beste Ansatz ist, um das Problem zu lösen. Ich würde mich über jeden Vorschlag freuen, wie dies zu programmieren ist.

Antwort

2

sed ist in der Lage, dies perfekt mit einem einzigen Prozess zu tun, und vermeidet diese eval shenanigans. Das resultierende Skript ist eigentlich ziemlich einfach.

#!/bin/sh 
file=$1 
shift 
printf '\\?%s?!d\n' "[email protected]" | 
sed -f - "$file" 

Wir generieren eine Zeile von sed Skript für jeden Ausdruck; Wenn der Ausdruck nicht gefunden wird (!), löschen wir (d) diese Eingabezeile und beginnen mit der nächsten Zeile.

Dies setzt voraus, dass sed- als Argument an -f akzeptiert, um das Skript von der Standardeingabe zu lesen. Dies ist nicht vollständig tragbar; Sie müssen das generierte Skript möglicherweise stattdessen in einer temporären Datei speichern, wenn dies ein Problem ist.

Dies verwendet ? als interne Regex-Trennzeichen. Wenn Sie ein Literal ? in einem der Muster benötigen, müssen Sie Backslash-escape. Im allgemeinen Fall wäre vielleicht das Erstellen eines Skripts, das ein alternatives Trennzeichen findet, das sich in keinem der Suchausdrücke befindet, möglich, aber an diesem Punkt würde ich stattdessen zu einer geeigneten Skriptsprache wechseln (Python wäre meine Präferenz).

+0

Ich weiß nicht, wie Sie Ihr Skript ausführen, da ich Ihren Code in eine Datei namens myScritpt.sh einfügen und ich myScript.sh file.txt string1 ... stringN ausführen, aber es funktioniert nicht, und ich tu 'es nicht Ich weiß, warum Sie #!/bin/sh statt #!/bin/bash geschrieben haben, ich glaube, dass dies im Bash-Skript gemacht werden kann und es unnötig ist, Python in diesem einfachen Fall zu verwenden. – neo33

+0

Bash ist eine Obermenge von 'sh' aka Bourne Shell. Es gibt keine Bash-spezifischen Konstrukte in diesem Skript, aber es steht Ihnen natürlich frei, den Shebang so zu ändern, dass er auf Bash zeigt, wenn Sie sich dadurch irgendwie wohler fühlen. Wie auch immer, Sie würden das Skript einfach mit './MyScript.sh' ausführen, nachdem Sie' chmod + x' es getan haben (obwohl ich für eine explizite '.sh' Erweiterung für interaktive Befehle empfehlen würde und diese spezielle Erweiterung schlägt a Bourne, nicht Bash, Skript). – tripleee

+0

Dieses Skript ist einfach, aber nicht vollständig robust. Für Ihren eigenen persönlichen Gebrauch und/oder mit seinen Mängeln und Einschränkungen ordnungsgemäß dokumentiert, ist es in Ordnung, wie es ist. Für ein Tool für die öffentliche Verbreitung würde ich zu Python oder Perl wechseln, einfach weil "sed" mit vom Benutzer bereitgestellten Skriptfragmenten eine Herausforderung darstellt, um vollständig robust zu sein. – tripleee

1

Sie können das Muster des Betriebs erzeugen und es in einer Variablen speichern:

pattern="$(printf 'grep %s file.txt' "$1"; printf ' | grep %s' "${@:2}" ; printf '\n')" 

und dann

eval "$pattern" 

Beispiel:

% cat file.txt               
foo bar 
bar spam 
egg 

% grep_gen() { pattern="$(printf 'grep %s file.txt' "$1"; printf ' | grep %s' "${@:2}" ; printf '\n')"; eval "$pattern" ;} 

% grep_gen foo bar       
foo bar 
+0

Dank dieser Ansatz sehr nützlich ist, aber ich habe den Namen meiner Datei in der Mustervariable zu schreiben, und die Idee ist, bash myScript.sh file.txt string1 ... stringN auszuführen, ich muss den Namen der Datei auch als Parameter des Skripts erhalten. – neo33

1

Sie können den Befehl erstellen in einer Schleife und dann eval verwenden, um i auszuwerten t. Dies verwendet cat, so dass Sie alle grep gruppieren können.

#! /bin/bash 

file="$1" 
shift 
args=("[email protected]") 


cmd="cat '$file'" 
for a in "${args[@]}" 
do 
    cmd+=' | ' 
    cmd+="grep '$a'" 
done 

eval $cmd 
+0

danke das ist genau das, was ich wollte, ich schätze deine Hilfe sehr. – neo33

+0

Wie immer wird 'eval' ein Sicherheitsproblem sein. Wenn eines der Argumente beispielsweise ein einfaches Anführungszeichen enthält, schlägt dies mit einer unergründlichen Fehlermeldung fehl. Es gibt wahrscheinlich Möglichkeiten, um es in noch interessanterer Weise zu brechen, einschließlich Wege, die zum Beispiel alle 'sudo'-Privilegien des aufrufenden Benutzers ausnutzen. – tripleee

+0

Nach dem Code von Diego machte ich meine Optimierung. – neo33

1

EVAL freie Alternative:

#!/bin/bash 

temp1="$(mktemp)" 
temp2="$(mktemp)" 

grep "$2" "$1" > temp1 
for arg in "${@:3}"; do 
    grep "$arg" temp1 > temp2 
    mv temp2 temp1 
done 

cat temp1 
rm temp1 

mktemp erzeugt eine temporäre Datei mit einem eindeutigen Namen und gibt seinen Namen; es sollte allgemein verfügbar sein.

Die Schleife führt dann grep für jedes Argument aus und benennt die zweite temporäre Datei für die nächste Schleife um.

+0

Ich nehme an, Sie könnten stattdessen eine einzelne Pipeline in einer temporären Skriptdatei erzeugen und ein einfacheres und schnelleres Skript verwenden. – tripleee

+0

@tripleee Ich dachte daran, so etwas zu tun, aber wie würden Sie dann das Skript ausführen? –

+0

'sh/tmp/script tripleee

1

Dies ist die Optimierung von Diego Torres Milano Code und die Antwort auf meine ursprüngliche Frage:

#! /bin/bash 
file=$1 
shift 
cmd="cat '$file'" 
for 'a' in "[email protected]" 
do 
    cmd+=" | grep '$a'" 
done 
eval $cmd 
+0

Gute Optimierung. Vergessen Sie nicht die Anführungszeichen um "$ file", falls der Dateiname Leerzeichen enthält. Gleiches gilt für "$ @" und "$ a". –

+0

ok danke für die Unterstützung, verstehe ich jetzt. die Verwendung der Zitate, das war sehr nützlich. – neo33

Verwandte Themen