I'd lean towards find
. I'll provide some code review type comments at the bottom, but first a rewrite:
search() {
local extensions
local pattern
local find_cmd
local OPTIND opt
local usage=$( cat - << END
Usage: $FUNCNAME [OPTION] ... PATTERN [FILE] ...
Search for PATTERN in each FILE.
Example: $FUNCNAME -t c -t h 'hello world' /code/internal/dev/ /code/public/dev/
Output control:
-t limit results to files of type
END
)
if [[ $1 == --help ]]; then
echo "$usage"
return
fi
extensions=()
while getopts ":ht:" opt; do
case $opt in
h) echo "$usage"; return ;;
t) extensions+=("$OPTARG");;
?) echo "invalid option: -$OPTARG";;
esac
done
shift $((OPTIND-1))
if (( $# == 0 )); then
echo "no search term provided"
return
fi
pattern=$1
shift
if (( $# == 0 )); then
echo "no directories provided"
return
fi
# your directories to search are now the positional parameters
find_cmd=( find "$@" )
or=""
for type in "${extensions[@]}"; do
find_cmd+=( $or -name "*.$type" )
or="-o"
done
find_cmd+=( -exec grep -I "$pattern" + )
"${find_cmd[@]}"
}
Notes
[ $1 = --help ]
will give you syntax error if $1 is blank -- you get [ = --help ]
and the = operator requires 2 operands. bash's [[ ... ]]
is smarter about not dropping operands just because they're empty
- You can build up arrays bit-by-bit with
arr+=("$element")
- you want to use more double quotes for your variables. If the pattern is "hello world", then
grep $pattern file1
will look for the pattern "hello" in files "world" and "file1" -- grep "$pattern" file1
will work as you expect.
- I use
(( ... ))
arithmetic expression for numeric comparisons.
- I build up the find command piece by piece and execute it with
"${arr[@]}"
-- that specific syntax, indexing the array with [@]
and surrounding it with double quotes, is the way you'll want to expand arrays most of the time. That specific syntax will expand the array into elements, but it will keep elements containing whitespace as a single argument.
This includes using "$@"
over $@
-- the former gives you the actual arguments given by the user, the latter gives you all the words in the arguments. A demonstration:
set -- "foo bar" "hello world" # set the positional parameters
printf "%s\n" "$@" | wc -l # print the parameters one per line
# and count the lines -- 2
printf "%s\n" $@ | wc -l # here you get 4
- if
t
is an array, $t
gives you the first element only.