To scratch a few itches that I had, I wrote a number of bash functions. These are published on Github at http://github.com/cxreg/smartcd
This repository is made of 3 components, each of which are separately useful:
bash_arrays
Bash is a very featureful shell. It has things that many people aren't even aware of. One such feature, added in 2.0, is the ability for shell variables to be real arrays, instead of just strings. Using string variables and IFS (Internal Field Separator, if you don't know, don't ask) to manage multiple values can be very frustrating.
You can initialize array variables with the syntax "name=(value1 ... valuen)", and assign individual elements like "name[subscript]=value". You access them by index like "${name[subscript]}"
$ foo=(bar baz biff)
$ echo ${foo[2]}
biff
$ foo[3]=blah
$ echo ${foo[3]}
$ foo[3]=blah
$ echo ${foo[3]}
blah
However, this syntax is pretty awkward, and almost no convenience functions are supplied. If you've worked with any high level dynamic languages, you'll be familiar with things such as push, pop, shift, unshift, and reverse.
These missing functions are provided by the included file bash_arrays, and are named
Example usage:
$ apush foo bar baz biff
$ alen foo
3
$ apop foo
biff
$ apop foo
baz
$ apop foo
bar
Unfortunately, there isn't a simple way to both modify a shell variable and "return" a value. The only way to capture a non-numeric return value is by executing a sub-shell, which will modify the value only in the sub-shell. The modification is of course lost when that shell exits.
bar=$(apop foo) # $bar has a value, but $foo is unmodified!
To work around this shortcoming, the mutators (functions which modify the array) that also need to return a value (apop and ashift) set a variable which contains the returned value, $_apop_return and $_ashift_return respectively.
apop foo >/dev/null # run in current shell, and quiet output
bar=$_apop_return
bash_varstash
This file provides 2 functions, stash and unstash. stash stores the value of a variable to a temporary location. unstash restores its value from the temporary variable and deletes it. The name of the temporary variable is derived from the current working directory of the shell, which means you'll need to be in the directory you stashed from to unstash properly.
$ export FOO=bar
$ echo $FOO
bar
$ stash FOO
$ export FOO=baz
$ echo $FOO
baz
$ unstash FOO
$ echo $FOO
bar
While this might sound a bit arcane, it is specifically useful with...
bash_smartcd
This file provides a replacement for cd. It runs the specially named ".bash_enter" shell script when it enters a directory, and runs ".bash_leave" when it moves away, using the bash_arrays functions to keep track of things.
Once loaded, it's enabled by simply adding
alias cd=smartcd
to your shell initialization file. All path differences are incrementally inspected when changing directories, so it will "leave" the necessary path elements before "entering" the new ones.
For example:
/foo/bar/baz$ cd /foo/biff
This would "leave" baz, then bar, and then enter biff. /foo is ignored because it is common to both paths.
And now for the motivating reason for all of these libraries...
*drumroll*
When smartcd is combined with stash and unstash, you can achieve something pretty amazing: per-directory environment variables. These can be useful for many things, including automagically modifying PATH, PERL5LIB, ORACLE_HOME, or any number of other settings you might want to tweak when you cd into a development directory or some other special environment. If set up properly, they will be restored to their original values when leaving.
Let's inspect the scripts:
$ cat foo/.bash_enter
echo entering `pwd`
stash FOO
export FOO=baz
$ cat foo/.bash_leave
unstash FOO
echo leaving `pwd`
$ cat foo/bar/.bash_enter
echo entering `pwd`
stash FOO
export FOO=biff
$ cat foo/bar/.bash_leave
unstash FOO
echo leaving `pwd`
Notice that you can stash the same variable multiple times, at different levels. Now, let's set the variable and try it out.
$ export FOO=bar
$ cd foo
entering /home/count/foo
$ echo $FOO
baz
$ cd bar
entering /home/count/foo/bar
$ echo $FOO
biff
$ cd
leaving /home/count/foo/bar
leaving /home/count/foo
$ echo $FOO
bar
This repository is made of 3 components, each of which are separately useful:
- bash_arrays
- bash_varstash
- bash_smartcd
bash_arrays
Bash is a very featureful shell. It has things that many people aren't even aware of. One such feature, added in 2.0, is the ability for shell variables to be real arrays, instead of just strings. Using string variables and IFS (Internal Field Separator, if you don't know, don't ask) to manage multiple values can be very frustrating.
You can initialize array variables with the syntax "name=(value1 ... valuen)", and assign individual elements like "name[subscript]=value". You access them by index like "${name[subscript]}"
$ foo=(bar baz biff)
$ echo ${foo[2]}
biff
$ foo[3]=blah
$ echo ${foo[3]}
$ foo[3]=blah
$ echo ${foo[3]}
blah
However, this syntax is pretty awkward, and almost no convenience functions are supplied. If you've worked with any high level dynamic languages, you'll be familiar with things such as push, pop, shift, unshift, and reverse.
These missing functions are provided by the included file bash_arrays, and are named
- apush - add an element to the end of the array
- apop - remove an element from the end of the array
- ashift - remove an element from the beginning of the array
- aunshift - add an element to the beginning of the array
- areverse - reorder the array backwards
- afirst - print the first element but leave it in place
- alast - print the last element but leave it in place
- alen - print the number of elements in the array
Example usage:
$ apush foo bar baz biff
$ alen foo
3
$ apop foo
biff
$ apop foo
baz
$ apop foo
bar
Unfortunately, there isn't a simple way to both modify a shell variable and "return" a value. The only way to capture a non-numeric return value is by executing a sub-shell, which will modify the value only in the sub-shell. The modification is of course lost when that shell exits.
bar=$(apop foo) # $bar has a value, but $foo is unmodified!
To work around this shortcoming, the mutators (functions which modify the array) that also need to return a value (apop and ashift) set a variable which contains the returned value, $_apop_return and $_ashift_return respectively.
apop foo >/dev/null # run in current shell, and quiet output
bar=$_apop_return
bash_varstash
This file provides 2 functions, stash and unstash. stash stores the value of a variable to a temporary location. unstash restores its value from the temporary variable and deletes it. The name of the temporary variable is derived from the current working directory of the shell, which means you'll need to be in the directory you stashed from to unstash properly.
$ export FOO=bar
$ echo $FOO
bar
$ stash FOO
$ export FOO=baz
$ echo $FOO
baz
$ unstash FOO
$ echo $FOO
bar
While this might sound a bit arcane, it is specifically useful with...
bash_smartcd
This file provides a replacement for cd. It runs the specially named ".bash_enter" shell script when it enters a directory, and runs ".bash_leave" when it moves away, using the bash_arrays functions to keep track of things.
Once loaded, it's enabled by simply adding
alias cd=smartcd
to your shell initialization file. All path differences are incrementally inspected when changing directories, so it will "leave" the necessary path elements before "entering" the new ones.
For example:
/foo/bar/baz$ cd /foo/biff
This would "leave" baz, then bar, and then enter biff. /foo is ignored because it is common to both paths.
And now for the motivating reason for all of these libraries...
*drumroll*
When smartcd is combined with stash and unstash, you can achieve something pretty amazing: per-directory environment variables. These can be useful for many things, including automagically modifying PATH, PERL5LIB, ORACLE_HOME, or any number of other settings you might want to tweak when you cd into a development directory or some other special environment. If set up properly, they will be restored to their original values when leaving.
Let's inspect the scripts:
$ cat foo/.bash_enter
echo entering `pwd`
stash FOO
export FOO=baz
$ cat foo/.bash_leave
unstash FOO
echo leaving `pwd`
$ cat foo/bar/.bash_enter
echo entering `pwd`
stash FOO
export FOO=biff
$ cat foo/bar/.bash_leave
unstash FOO
echo leaving `pwd`
Notice that you can stash the same variable multiple times, at different levels. Now, let's set the variable and try it out.
$ export FOO=bar
$ cd foo
entering /home/count/foo
$ echo $FOO
baz
$ cd bar
entering /home/count/foo/bar
$ echo $FOO
biff
$ cd
leaving /home/count/foo/bar
leaving /home/count/foo
$ echo $FOO
bar

