Shell: Office-Dateien (Open Document Format) durchsuchen

Veröffentlicht am

Im ubuntuusers.de-Forum wurde letzte Woche die Frage gestellt, wie Office-Dokumente nach einem Suchbegriff durchsucht werden können. Ich habe darauf hin eine Antwort mit einem kleinen Shellskript geschrieben, das Writer-Dokumente und gleichermaßen auch Calc-Tabellen aus LibreOffice bzw. OpenOffice durchsucht.

Dieses Shellskript wollte ich hier nun noch einmal vorstellen – in einer überarbeiteten Version (weniger Bugs, mehr Funktion :))…

Zunächst sei noch erwähnt, dass es, wie im Forums-Thread geschrieben wurde, auch andere Möglichkeiten gibt, Office-Dateien zu durchsuchen: beispielsweise DocFetcher. Mit den Möglichkeiten, die einem die Shell bzw. die CLI-Tools bieten, hat aber m.E. auch die Shellskript-Variante ihren Charme.

Funktionsweise und Features

odtgrep.sh im Einsatz

Die .od*-Dateien können per unzip entpackt werden. Darin befindet sich u.a. eine Datei namens content.xml, die den Text enthält. Das Skript entpackt also diese Datei in ein temporäres Verzeichnis und sucht darin per grep nach dem Suchtext.

Das Skript zeigt nicht nur an, in welcher Datei der Suchtext gefunden wurde. Es werden auch die Fundstellen angezeigt. Die XML-Tags zur Formatierung des Textes werden dabei entfernt, Zeilenumbrüche aber umgesetzt. Der Suchtext wird in den Fundstellen schließlich farblich hervorgehoben.

Quelltext

Hier nun zunächst der Quellcode des Skriptes, das ich odfgrep.sh getauft habe:

#!/bin/bash
if [ $# -lt 2 ]; then
    echo "Verwendung: $(basename "$0") <Suchbegriff> <Datei>"
    exit 1
fi
HL_ON=$(echo -e '\033[33m')
HL_OFF=$(echo -e '\033[0m')
searchstr=$1
shift 1
tmpdir=/tmp/$(basename "$0").$$
mkdir -p "$tmpdir"
for file in "$@"; do
    unzip "$file" content.xml -d "$tmpdir" >/dev/null
    matches=$(sed 's/<\/text:p>/\n/g' "$tmpdir"/content.xml|sed 's/<[^>]*>//g'|grep "$searchstr")
    if [ $? == 0 ]; then
        echo "Gefunden in: $file"
        OLD_IFS=$IFS
        IFS=$'\n'
        for match in $matches; do
            echo " $match" | sed -e "s/\($searchstr\)/$HL_ON\1$HL_OFF/g"
        done
        IFS=$OLD_IFS
    fi
    rm -f "$tmpdir"/content.xml
done
rm -r "$tmpdir"

Verwendung

Verwendet werden kann das Skript dann beispielsweise so (um ein bestimmtes Dokument zu durchsuchen):

./odfgrep.sh "Testtext" eindokument.odt

oder so (um alle Writer-Dokumente im aktuellen Verzeichnis zu durchsuchen):

./odfgrep.sh "Testtext" *.odt

oder so (um rekursiv alle Writer-Dokumente unterhalb von /einverzeichnis zu durchsuchen):

find /einverzeichnis -name "*.odt" -exec ./odfgrep.sh "Texttext" {} \;

Womöglich mag das Skript noch nicht ganz rund sein. Gerade was die Darstellung der Fundstellen angeht, könnte sich noch der eine oder andere Haken finden lassen.

Wer Verbesserungs-Vorschläge hat oder Fehler findet kann diese gerne hier als Kommentar hinterlassen.

Update: Nach den Kommentaren von werzog und Andreas (vielen Dank für die Hinweise!) habe ich das Skript noch einmal angepasst. Hier der neue Quellcode:

#!/bin/bash
if [ $# -lt 2 ]; then
    echo "Verwendung: $(basename "$0")  "
    exit 1
fi
grep_opts="--color=always"
while :; do
    case "$1" in
        -*) grep_opts="$grep_opts $1" ;;
        *) break ;;
    esac
    shift 1
done
searchstr=$1
shift 1
for file in "$@"; do
    content=$(unzip -p "$file" content.xml)
    content_clean=$(echo "$content"|sed 's/<[^>]*>//g')
    matches=$(echo "$content_clean"|grep $grep_opts "$searchstr")
    if [ $? == 0 ]; then
        echo "Gefunden in: $file"
        echo "$matches"|sed 's/^ */ /g'
    fi
done

Neben einigen Vereinfachungen des Codes können nun Optionen an grep übergeben werden, z.B. -i zur case-insensitiven Suche – also z.B.:

./odfgrep.sh -i "testtext" *.odt

Dieser Artikel wurde in der/den Kategorie(n) Planet-U, Praxis, Programmierung und Skripting veröffentlicht und mit den Tags , , , , , , , , versehen.

7 Kommentare zu Shell: Office-Dateien (Open Document Format) durchsuchen

  1. Kommentar von Andreas
    18. September 2012, 12:41 Uhr.

    Du kannst unzip mit -p verwenden und dir die ganze tmp-dir-geschichte schenken…

    matches=$(unzip -p „$file“ content.xml | sed ‚s//\n/g‘ „$tmpdir“|sed ‚s/]*>//g’|grep „$searchstr“)

    Ist schneller und falls ein unzip failed, gibt’s nicht aus versehen die Matches aus dem letzten geglückten unzip…

    odfgrep.sh „test“ match.odt corrupted.odt hätte in deinem script nämlich für beide dateien die matches drinne…

    Gruss,
    Andreas

  2. Kommentar von werzog
    18. September 2012, 13:06 Uhr.

    Wozu das tmp-Verzeichnis? Wozu das manuelle hervorheben? – grep kann das doch bereits und auch noch angepasst an das verwendete bash-farbprofil. Außerdem gibt es bessere Wege eine mehrzeilige Variable auszulesen als den record separator zu ändern:

    #!/bin/bash
    if [ $# -lt 2 ]; then
    echo „Verwendung: $(basename „$0″) “
    exit 1
    fi
    searchstr=$1
    shift 1
    for file in „$@“; do
    matches=$(unzip -caq „$file“ content.xml | sed ‚s//\n/g‘ | sed ‚s/]*>//g‘ | grep –color=always „$searchstr“)
    if [ $? == 0 ]; then
    echo „Gefunden in: $file“
    OLD_IFS=$IFS
    IFS=$’\n‘
    for match in $matches; do
    echo “ $match“
    done
    IFS=$OLD_IFS
    fi
    done

    Außerdem möchte man doch wohl sicher Optionen an grep durchreichen (zumindest -i), aber das überlasse ich jetzt dir.

  3. Kommentar von senden9
    18. September 2012, 23:03 Uhr.

    Danke für diese erweiterte Version des Skripts.

  4. Kommentar von Andy
    19. September 2012, 18:17 Uhr.

    Cool, aber in der zweiten Version ist die RegExp des zweiten sed Aufrufs kaputt gegangen…

    • Kommentar von Gerald
      19. September 2012, 21:18 Uhr.

      Danke dir für den Hinweis, Andy. Da ist beim Copy&Paste was schief gelaufen. Ist jetzt korrigiert – hoffe es passt jetzt alles.
      Gruß, Gerald

  5. Kommentar von Andreas
    20. September 2012, 11:00 Uhr.

    Persönlich würde ich nicht
    content=$(getconent); dosomething $content
    sonder lieber
    getcontent|dosomething
    machen, wann auch immer möglich…

    Vorteil ist, dass die beiden Prozesse parallel gespawnt werden und die Daten gepipt werden. Grade bei odt-dateien von mehreren hundert seiten, kostet das evtl schon ganz schön speicher, das XML erstmal komplett in eine Bash-Variable (RAM) zu laden und dann dem nächsten Befehl wieder (mit nem neuen echo-Prozess) auszugeben, statt e.g. Zeilenweise durch die Pipe zu rutschen…

    Wenn du’s auf nem langsamen Rechner mit großen odts testest, wirst du’s sehen…

    Viel wichtiger ist aber, dass Befehlszeilen auf 65000 Zeichen begrezt sind, wenn ich mich recht entsinne… Also könnten große files das sprengen und das ganze läuft nicht mehr…

    Wenn du es übersichtlich magst, macht doch
    matches = dosth | \
    match | \
    clean...

    Dann hast du’s auch zeilenweise lesbar…

    Weitere optimierung, wenn du die sed-scripte mit -e aufrufst, kannst du mehrere in einem sed-run gleichzeitig evaluieren…
    echo hallo | sed -e s/hal/tsch/g -e s/lo/uess/g
    Spart noch ne PID, spart noch eine Pipe und damit CPU und RAM… :-)

    Gruss,
    Andreas

    • Kommentar von Gerald
      22. September 2012, 08:59 Uhr.

      Ich werde mir das bei Gelegenheit mal ansehen, Andreas. Danke für dein Kommentar!

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>