find und grep erzeugen unterschiedliche Ausgaben, wenn sie von der Skriptdatei oder der Befehlszeile ausgeführt werden

537
ad0x

Ich verwende diese Befehle, um in mehreren PDF-Dateien mit einem Dateipfad zu suchen:

>>find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "phrase"' \;

Wo phraseist der Begriff, nach dem Sie in den PDF-Dokumenten suchen möchten? Das funktioniert wie erwartet. Ich bekomme alle Vorkommen des Wortes "volym". Ausgabe in Terminal

Wenn ich versuche, dasselbe in einem .sh-Skript auszuführen (search.sh)

#!/bin/bash read -p "Enter term to search for: " phrase find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \; echo "Search completed" 

 >>./search.sh >>Enter term to search for:volym 

Es gibt jede Zeile in jedem PDF aus. Die Ausgabe: gibt jede Zeile in jedem PDF aus

Ich vermute, es hat etwas damit zu tun, wie readdie Eingabe interpretiert wird, aber ich habe online keine Lösung für mein Problem gefunden.

-1
In einfache Anführungszeichen geändert, und jetzt funktioniert es! Vielen Dank! ad0x vor 6 Jahren 0

1 Antwort auf die Frage

2
Kamil Maciorowski

Der direkte Schuldige ist $phrasein einfachen Anführungszeichen. Dies ist nicht das einzige Problem.

Was geschieht

Dies ist der relevante Code (Hinweis: Ich benutze Ellipsen für den am wenigsten interessanten Teil; diese Linie soll vom Menschen verstanden werden, nicht direkt in einer Shell ausgeführt):

find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \; 

Die Shell, die das Skript interpretiert, enthält den Wert der phraseVariablen. Sagen wir mal der Wert ist volym. Im obigen Befehl bleibt alles, was in einfachen Anführungszeichen steht, unberührt, denn so funktioniert das einfache Anführungszeichen. so $phraseist noch nicht ausgebaut. Die Shell analysiert nur \die folgenden ;Befehle und soll die Befehle nicht trennen. Sie sollte als Befehlszeilenargument für behandelt werden find.

Wenn das findDienstprogramm ausgeführt wird, sieht es als Argumente (ab 0. Dh an sich findselbst; ein Argument pro Zeile, mit Ausnahme mehrerer weniger interessanter Argumente):

find … -exec sh -c pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase" ; 

Beachten Sie, dass die vorletzte Zeile ein langes Argument ist.

Nehmen wir an, es foo.pdfwird gefunden und -execwird seine Arbeit machen. Alle Argumente zwischen -execund ;werden zu einem neuen Befehl, {}der durch ersetzt wird foo.pdf. Der neue Befehl lautet (wieder beginnend mit dem 0. Argument; ein Argument pro Zeile):

sh -c pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase" 

Läuft also sh, er bekommt -cund weiß daher, dass das nächste Argument so ausgeführt werden sollte, als wäre es in die Befehlszeile eingegeben worden:

pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase" 

Dies ist der Moment $phraseerweitert. Es erweitert sich zu nichts (das letzte Wort wird zu ""), weil es nicht in dieser Shell gesetzt wurde. Es würde sich erweitern, volymwenn Sie die Variable in Ihr Skript exportieren. aber du hast nicht Ich würde aber nicht exportieren; Meiner Meinung nach würde der Export die Umwelt unnötig belasten.

Lösung? Noch nicht

Das Anbringen $phrasevon einfachen Anführungszeichen scheint eine gute Idee zu sein. In einigen Fällen wird es funktionieren. Der naivste Ansatz:

find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'$phrase'"' \; 

Es ist fehlerhaft. Mit dem Satz sind " ; -exec rm "{}dies Argumente, die wir findsehen werden:

find … -exec sh -c pdftotext "{}" - | grep --with-filename --label="{}" --color "" ; -exec rm "{}" ; 

Ihre PDFs sind weg. Künstliches Beispiel? Könnte sein. Selbst wenn Sie der Einzige sind, der das Skript verwendet, ist eine solche Sicherheitsanfälligkeit durch Code-Injektion nicht gut.

Dies war, weil überhaupt $phrasenicht zitiert wurde. Sie wissen wahrscheinlich, dass Sie Variablen fast immer in Anführungszeichen setzen sollten. Lass uns das machen. Ein verbesserter Ansatz:

find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'"$phrase"'"' \; 

Mit der Phrase wird " ; -exec rm "{}dies findsehen:

find … -exec sh -c pdftotext "{}" - | grep --with-filename --label="{}" --color "" ; -exec rm "{}" ; 

Sieht etwas besser aus; immer noch fehlerhaft, weil foo.pdf shwill versuchen zu laufen:

pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "" ; -exec rm "foo.pdf" 

Der letzte Teil wird höchstwahrscheinlich einen Fehler auslösen, da es keinen -execBefehl gibt. Was ist, wenn der Satz war " ; rm "{}? Was passiert, wenn es war " ; rm -rf ~/".

Es gibt mehr. Lassen Sie die Phrase volym(ziemlich sicher) sein, aber benennen Sie eine Ihrer PDF-Dateien "; rm -rf ~ #.pdf(dies ist in wenigen Dateisystemen einschließlich der Ext-Familie möglich). Nachdem {}-s ersetzt wurden, shwird Folgendes ausgeführt:

pdftotext "/home/ad0x/…/"; rm -rf ~ #.pdf" - | grep … 

Ich denke, pdftotextwird versagen (irrelevant); dann sind deine Dateien weg; dann #beginnt ein Kommentar, was auch immer.

Lösung

Das ist der richtige Weg, um Ihre passieren {}und $phrasezu sh sicher :

find … -exec sh -c 'pdftotext "$1" - | grep --with-filename --label="$1" --color "$2"' dummy {} "$phrase" \; 

Wenn dies shder gegebenen Befehlsfolge ausführt, $1erweitert wird, was auch immer findfür substituierte {}, $2zu, was für die ursprüngliche Schale substituierte erweitert wird $phrase. Im Kontext werden shdiese Parameter richtig zitiert, sodass Sie keinen Code mehr einfügen können. ( Diese andere Antwort von mir erklärt dummy).

Auch jetzt gibt es Raum für Verbesserungen. Was ist, wenn der Satz war -f? Der grepTeil würde schließlich sein:

grep --with-filename --label="…" --color "-f" 

es würde sich über das fehlende Argument beschweren. Verwenden Sie --diese Option, um das Ende der Optionen anzuzeigen. -fafter --wird nicht als Option behandelt. Gleiches gilt für pdftotext(obwohl in Ihrem speziellen Fall jeder Pfad zu PDF mit beginnen muss, /homedamit er nicht als Option interpretiert werden kann; er kann jedoch im Allgemeinen $1zu einem String erweitert werden, der wie eine Option aussieht). Unser shAufruf ist bereits immun, da shOptionen vor einer Befehlszeichenfolge genommen werden können und unsere Befehlszeichenfolge nicht mit einer Option verwechselt werden kann ( sh -c -- 'pdftotext …' …sie schadet trotzdem nicht). Robusterer Befehl:

find … -exec sh -c 'pdftotext -- "$1" - | grep --with-filename --label="$1" --color -- "$2"' dummy {} "$phrase" \;