Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

How to create an alias that actually extends another alias of the same name in Bash?

Why:

I used to have GREP_OPTIONS set on .bashrc to something like this:

GREP_OPTIONS="-I --exclude=\*~"

I also had a script (let us say, setup-java.sh) which I would call before working on some Java projects. It would contain the line:

GREP_OPTIONS="$GREP_OPTIONS --exclude-dir=classes"

If I also use Sass, then I would call setup-sass.sh which contains the line:

GREP_OPTIONS="$GREP_OPTIONS --exclude-dir=\*/.sass-cache"

But GREP_OPTIONS was deprecated and apparently the standard solution is either create an alias or some script...

share|improve this question
    
What about bash functions? – Jakuje Jan 10 at 10:49
2  
I quite agree -- using a function is a much better option than an alias. – Charles Duffy Jan 10 at 17:38
up vote 12 down vote accepted

Bash stores the values of aliases in an array called BASH_ALIASES:

$ alias foo=bar
$ echo ${BASH_ALIASES[foo]}
bar

With parameter expansion we can get the either the last set alias (if exists) or the default value:

alias grep="${BASH_ALIASES[grep]:-grep} -I --exclude=\*~"

Now just do it on setup-java.sh:

alias grep="${BASH_ALIASES[grep]:-grep} -I --exclude=\*~  --exclude-dir=classes"

...and finally on setup-sass.sh:

alias grep="${BASH_ALIASES[grep]:-grep} -I --exclude=\*~ --exclude-dir=\*/.sass-cache"

If the three lines are called, we get what we want:

$ echo ${BASH_ALIASES[grep]:-grep}
grep -I --exclude=\*~ -I --exclude=\*~ --exclude-dir=classes -I --exclude=\*~ --exclude-dir=\*/.sass-cache
share|improve this answer

aliases chain if you end them with spaces.

alias print='printf %s\\n ' hey='"hello, fine fellow" '
print hey

hello, fine fellow

You can write entire scripts that way, if you're crazy enough. Anyway, if you want to extend an alias, then just make sure the alias you want to extend ends in a space, and tack another on.

alias grep='printf "%s " -I --exclude=\*~ '    \
      exdir=' --exclude-dir=classes '          \
      exsass='--exclude-dir=\*/.sass-cache '
grep exdir exsass exdir exsass

-I --exclude=*~ --exclude-dir=classes --exclude-dir=*/.sass-cache --exclude-dir=classes --exclude-dir=*/.sass-cache
share|improve this answer
6  
This is horrifying beautiful. – user1717828 Jan 10 at 2:45
    
Wow, this is awesome. Didn't know this (and probably won't use it much, because I think it goes against the Rule of Clarity) but it is nice to know! Question, though: Why the space at the start of exdir? (Is that just for alignment for aesthetic reasons?) – Wildcard Jan 10 at 10:47
2  
@Wildcard: fnmatch(){ alias fnmatch='case $1 in '; while "${1:+:}" 2>&-; do eval 'fnmatch pattern list ;; esac'; shift; done; unalias fnmatch; }; alias pattern='${1:+*}) ' list=': do stuff '; fnmatch "$@". Doing it with aliases allows you to use the patterns' expansions more directly and more safely. You do need a second context w/ eval when called from within a function, but it's not inherently unsafe as long as the pattern and list names are controlled by you. They can only break in most cases even when they're not unless some attacker knowingly correctly ends your case. – mikeserv Jan 10 at 11:32
1  
I use this pattern in my .bashrc: alias sudo='sudo ', this lets me call all my aliased commands after a sudo. Without the space it wouldn't work – arainone Jan 10 at 17:46
1  
@Wildcard - I wasn't exactly advocating such use, but it's true you can, and it's also true you'd have to be at least a little crazy to try. – mikeserv Jan 15 at 6:02

A function is a better option than an extendable alias here.

grep_options=( )
grep() {
  exec /usr/bin/grep "${grep_options[@]}" ${GREP_OPTIONS} "$@"
}

That way, you have two options to add options to the environment:

  • Amend the grep_options array; this correctly supports options with spaces, literal glob characters, and other corner cases:

    grep_options+=( --exclude-dir=classes --exclude-dir='*/.sass-cache' )
    
  • Use the traditional GREP_OPTIONS scalar variable, despite its pitfalls (see BashFAQ #50 to understand some of these):

    GREP_OPTIONS+=' --exclude-dir=classes '
    

That said, if you want your options to be reflected by grep instances invoked outside the shell, neither an alias nor a function will do. Instead, you'll want a wrapper script placed earlier in your PATH than the real grep command. For instance:

# in ~/.bash_profile
[[ -e ~/bin ]] && PATH=$HOME/bin:$PATH

...and, in ~/bin/grep:

#!/bin/bash

# load overrides to grep_options on GREP_OPTIONS from local dotfiles
source ~/.bash_profile
source ~/.bashrc

# ...and use them:
exec /usr/bin/grep "${grep_options[@]}" ${GREP_OPTIONS} "$@"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.