Setting project-level grep options
Using Zsh’s precmd
The venerable grep has not aged terribly well giving rise to (the admitedly cool) Ack. One handy feature of Ack is being able to set options specifically for a single project by placing a dotfile in the project directory. This is easily mimicked for grep using a handy Zsh feature. (This is also possible in Bash, of course, with a bit more code.)
GREP_OPTIONS
You can set default options for grep in an environment variable named GREP_OPTIONS. For example a nice default to put in your .zshrc file is:
GREP_OPTIONS="--color --exclude-dir=.svn --exclude-dir=.hg --exclude-dir=.git"
export GREP_OPTIONS
Dynamically setting environment variables
Zsh has a handy feature called precmd which is a function that you define in your ~/.zshrc file that is run before each time your prompt is drawn to the screen. In fact you can have several such functions if you put them in an array:
local -a precmd_functions
function grep_options() {
GREP_OPTIONS="--color --exclude-dir=.svn --exclude-dir=.hg --exclude-dir=.git"
export GREP_OPTIONS
}
precmd_functions=( grep_options )
See where this is going? :)
Reading files into Zsh arrays
First-class support for arrays in one thing (of many) that really sets Zsh apart from Bash. Zsh has many internal functions for working with arrays and many internal functions that work with arrays as parameters. (No need for Bash’s IFS bullshit.) You can see many such functions by referencing the following two Zsh manpages:
- zshexpn(1) under “PARAMETER EXPANSION”
- zshparam(1) under “ARRAY PARAMETERS”
The (f) parameter referenced in zshparam(1) reads newline-separated records as values into an array. Another, $(<) reads the contents of a file into a variable. Combined you have a one-liner that reads each line of a file into an array:
local -a opts
opts=( ${(f)"$(< ${HOME}/.grepoptions)"} )
Neat. What if you want to add comments to that file too? Obviously, they shouldn’t be included in the array. You can see in zshexpn(1) that many of the substitutions that work on strings also work on individual array items if given an array. The ${name:#pattern} substitution will remove items from an array that match a pattern. A comment character followed by anything looks like [#]*:
local -a opts
opts=( ${${(f)"$(< ${HOME}/.grepoptions)"}:#[#]*} )
Create a file in your home directory named .grepoptions to hold the options you always want passed to grep and each line will be added to the array:
--color
--exclude-dir=.svn
--exclude-dir=.hg
--exclude-dir=.git
Add new values to an array
Next modify your shell function to also look for a .grepoptions file in the current directory so its contents can be added to the array (note the +=):
local proj_opts=${PWD}/.grepoptions
if [[ -r ${proj_opts} ]] ; then
opts+=( ${${(f)"$(< "${proj_opts}")"}:#[#]*} )
fi
Great. You now have an array containing the aggregate of two .grepoptions files. The last step is to assemble the array back to a string so you can export the GREP_OPTIONS environment variable. Zsh arrays have a join parameter of the form ${(j: :)name} where the character between the colons is the character to join with.
Summary
Putting everything together you should have something similar to the following in your ~/.zshrc:
local -a precmd_functions
function grep_options() {
local -a opts
local proj_opts=${PWD}/.grepoptions
# Grab the global options
opts=( ${(f)"$(< "${HOME}/.grepoptions")"} )
# Grab any project-local options
if [[ -r ${proj_opts} ]] ; then
opts+=( ${${(f)"$(< "${proj_opts}")"}:#[#]*} )
fi
# Assemble and export
GREP_OPTIONS="${(j: :)opts}"
export GREP_OPTIONS
}
precmd_functions=( grep_options )
Since this function is executed as a Zsh precmd the value of the GREP_OPTIONS environment variable will change as you cd around the file system. If you have any other precmd functions simply add them to the precmd_functions array to run them all.
Here is a example shell session:
~ % cd $HOME
~ % echo $GREP_OPTIONS
--color --exclude-dir=.svn --exclude-dir=.hg --exclude-dir=.git
~ % cd ~/path/to/myproject
% cat .grepoptions
# Don't grep any minified JavaScript files
--exclude=*min.js
# Don't grep third-party libs
--exclude-dir=lib
% echo $GREP_OPTIONS
--color --exclude-dir=.svn --exclude-dir=.hg --exclude-dir=.git --exclude=\*min.js --exclude-dir=lib