patternbashMinor
Shortcut script for elusive grep command
Viewed 0 times
scriptshortcutelusivegrepforcommand
Problem
This is an example of my filesystem:
/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:
Additionally, sometimes I am only interested in certain file types. Then the command becomes:
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 :)
With this and a couple more shortcut scripts, I can (relatively) quickly find anything I'm looking for:
```
search-all-code()
- /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()
Solution
I'd lean towards
Notes
-
I build up the find command piece by piece and execute it with
-
This includes using
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 file1will look for the pattern "hello" in files "world" and "file1" --grep "$pattern" file1will 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
tis an array,$tgives you the first element only.
Code Snippets
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[@]}"
}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 4Context
StackExchange Code Review Q#49102, answer score: 3
Revisions (0)
No revisions yet.