Skip to main content
3 of 4
added 1848 characters in body

Shortcut script for elusive grep command

This is an example of my filesystem:

  • /code/
    • internal/
      • dev/
      • main/
    • public/
      • dev/
      • main/
      • release/
    • tools/

/code/internal/dev/, /code/public/dev/ and /code/tools/ contain subdirectories for multiple projects. I work almost exclusively in the dev branches of /code/internal/ and /code/public/, and often I want to search for a text string in those directories along with /code/tools/ (which has no branches). In these instances I run a command like this:

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ /code/tools/

Additionally, sometimes I am only interested in certain file types. Then the command becomes:

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ /code/tools/ | grep .c:\|.h:

I usually forget this command between usages and end up having to relearn it. To alleviate that problem, I created a script - which I would appreciate feedback on :)

search() {
    local t
    local OPTIND
    local pattern
    local files
    local types
    
    if [ $1 = --help ]; then
        echo "Usage: search [OPTION] ... PATTERN [FILE] ..."
        echo "Search for PATTERN in each FILE."
        echo "Example: search -t c -t h 'hello world' /code/internal/dev/ /code/public/dev/"
        echo
        echo "Output control:"
        echo "  -t    limit results to files of type"
        return
    fi
    
    while getopts ":t:" opt; do
        case $opt in
            t) t=(${t[@]} $OPTARG);; # create an array
        esac
    done
    shift $((OPTIND-1))
    
    pattern=$1
    files=${@:2}
    
    if [ -n "$t" ]; then
        # cast the array to a string
        types=${t[@]}
        
        # convert the string to a pattern usable by grep
        # example: "c h" becomes ".c:\|.h:"
        types=.${types// /':\|.'}:
        
        grep -I -r $pattern $files | grep $types
    else
        grep -I -r $pattern $files
    fi
}

With this and a couple more shortcut scripts, I can (relatively) quickly find anything I'm looking for:

search-all-code() {
    search $@ /code/internal/dev/ /code/public/dev/ /code/tools/
}

Aside: I realize some versions of grep support --include and --exlude options, but the version of grep I'm stuck with doesn't.

#Edit

This is a compromise between what I had originally and glenn's suggestion using the find command (which is much, much slower unfortunately):

search() {
    local file_types grep_cmd opt OPTARG OPTIND pattern start_time stop_time usage
    
    start_time=$(date +%s)
    
    usage="Usage: search [OPTION] ... PATTERN [FILE] ...
Search for PATTERN in each FILE.
Example: search -t c -t h 'hello world' /code/internal/dev/ /code/public/dev/

Output control:
  -t    limit results to files of type"
    
    if [[ $1 == --help ]]; then
        echo "$usage"
        return
    fi
    
    file_types=()
    while getopts ":t:" opt; do
        case $opt in
            t)
                file_types+=("$OPTARG")
                ;;
            ?)
                echo "$usage"
                return
                ;;
        esac
    done
    shift $((OPTIND-1))
    
    if (( $# == 0 )); then
        echo "$usage"
        return
    fi
    
    pattern="$1"
    shift
    
    if (( $# == 0 )); then
        echo "$usage"
        return
    fi
    
    if (( ${#file_types[@]} > 0 )); then
        file_types="${file_types[@]}"
        file_types=.${file_types// /':\|.'}:
        
        grep -I -r "$pattern" "$@" | grep "$file_types"
    else
        grep -I -r "$pattern" "$@"
    fi

    stop_time=$(date +%s)
    echo "Elapsed (seconds):" $(( stop_time - start_time ))
}

I'm not sure if I need to declare all those variables locally at the top of the function, but it seems like a good precaution.