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](http://codereview.stackexchange.com/a/49111/29587) 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.