Search MS word files in a directory for specific content in Linux

Viewed 17964

I have a directory structure full of MS word files and I have to search the directory for particular string. Until now I was using the following command to search files for in a directory

find . -exec grep -li 'search_string' {} \;

find . -name '*' -print | xargs grep 'search_string'

But, this search doesn't work for MS word files.

Is it possible to do string search in MS word files in Linux?

11 Answers

I've found a way of searching Word files (doc and docx) that uses the preprocessor functionality of ripgrep.

This depends on the following being installed:

  • ripgrep (more information about the preprocessor here)
  • LibreOffice
  • docx2txt
  • this catdoc2 script, which I've added to my $PATH:
#!/bin/bash

temp_dir=$(mktemp -d)
trap "rm $temp_dir/* && rmdir $temp_dir" 0 2 3 15

libreoffice --headless --convert-to "txt:Text (encoded):UTF8" --outdir ${temp_dir} $1 1>/dev/null
cat ${temp_dir}/$(basename -s .doc $1).txt

The command pattern tor a one-level recursive search is:

$ rg --pre <preprocessor> --glob <glob with filetype> <search string> 

Example:

$ ls *
one:
a.docx

two:
b.docx  c.doc
$ rg --pre docx2txt --glob *.docx This
two/b.docx
1:This is file b.

one/a.docx
1:This is file a.
$ rg --pre catdoc2 --glob *.doc This
two/c.doc
1:This is file c.

Here's the full script I use on macOS (Catalina, Big Sur, Monterey). It's based on Ralph's suggestion, but using built-in textutil for .doc

#!/bin/bash

searchInDoc() {
    # in .doc
    find "$DIR" -name "*.doc" |
        while read -r i; do
            textutil -stdout -cat txt "$i" | grep --color=auto -iH --label="$i" "$PATTERN"
        done
}

searchInDocx() {
    for i in "$DIR"/*.docx; do
        #extract
        docx2txt.sh "$i" 1> /dev/null
        #point, grep, remove
        txtExtracted="$i"
        txtExtracted="${txtExtracted//.docx/.txt}"
        grep -iHn "$PATTERN" "$txtExtracted"
        rm "$txtExtracted"
    done
}

askPrompts() {
    local i
    for i in DIR PATTERN; do
        #prompt
        printf "\n%s to search: \n" "$i"
        #read & assign
        read -e REPLY
        eval "$i=$REPLY"
    done
}

makeLogs() {
    local i
    for i in results errors; do
        
        # extract dir for log name
        dirNAME="${DIR##*/}"

        # set var
        eval "${i}LOG=$HOME/$i-$PATTERN-$dirNAME.log"
        local VAR="${i}LOG"

        # remove if existant
        if [ -f "${!VAR}" ]; then
            printf "WARNING: %s will be overwriten.\n" "${!VAR}"
        fi

        # touch file
        touch "${!VAR}"
    done
}

checkDocx2txt() {
    #see if soft exists
    if ! command -v docx2txt.sh 1>/dev/null; then
        printf "\nWARNING: docx2txt is required.\n"
        printf "Use \e[3mbrew install docx2txt\e[0m.\n\n"
        exit
    else
        printf "\n~~~~~~~~~~~~~~~~~~~~~~~~\n"
        printf "Welcome to scandocs macOS.\n"
        printf "~~~~~~~~~~~~~~~~~~~~~~~~\n"
    fi
}

parseLogs() {
    # header
    printf "\n------\n"
    printf "Scandocs finished.\n"

    # results
    if [ ! -s "$resultsLOG" ]; then
        printf "But no results were found."
        printf "\"%s\" did not match in \"%s\"" "$PATTERN" "$DIR" > "$resultsLOG" 
    else
        printf "See match results in %s" "$resultsLOG"
    fi

    # errors
    if [ ! -s "$errorsLOG" ]; then
        rm -f "$errorsLOG"
    else
        printf "\nWARNING: there were some errors. See %s" "$errorsLOG"
    fi

    # footer
    printf "\n------\n\n"
}



#the soft
checkDocx2txt
askPrompts
makeLogs
{
    searchInDoc
    searchInDocx
} 1>"$resultsLOG" 2>"$errorsLOG"
parseLogs
Related