If you're a power Linux user, you probably spend a lot of time in the Bash shell. But with the addition of some functions, autocompletion, configuration tweaks and shortcuts, we reckon we can make you even more productive. You doubtless already have aliases set up in your .bashrc file – shortening commands that you use frequently. Aliases are limited, though, in that (at least in Bash and related shells) you can't pass arguments in from the command line. Functions enable you to do this, so you can minimise your typing further still.
To help you speed up LDAP searches a bit you could use this:
function lds() { ldapsearch “($1=$2)”; }
either on the command line or in your .bashrc. Then type:
lds cn jkemp
and you'll do an LDAP search for any entries with a cn value of jkemp (ldapsearch “(cn=jkemp)” ). This saves lots of typing, and the nuisance of remembering which way round the “ and ( go.
Here's a section of an example .bashrc file, showing a couple of functions and some history setup alterations.
You can use a function to do pretty much anything you can write a shell script to do. You can even get complicated and self-recursive. The following code will multiply an arbitrary-length set of numbers (eg times 3 4 5 will output 60). Note the need to escape the * in the line that calls expr; without this, the shell will expand * to the list of all files, rather than treating it as the multiplication operator.
function times() { if [ -z “$2” ]; then echo $1 else a=$1 shift b = `times $@` echo `expr $a \* $b` fi }
Another trick you'll already be familiar with is hitting tab to auto-complete filenames and commands. This is great, but there are a few ways to extend it further. The first part of this isn't exactly autocompletion, but it helps you locate directories more easily. $CDPATH does for cd what $PATH does for executables. In other words, for a directory in your $CDPATH, you can type just its name and Bash will take you straight there – saving you from having to type the full path.
So, I regularly want to go to the “research” subdirectory of my website. My $CDPATH looks like this:
.:/home/jkemp:/local/www/html/research
So I can just type cd research and I'm there. Note that you need to include the initial full stop (.) in the $CDPATH, or you won't be able to cd to subdirectories without putting ./ at the start of them.
However, this can be a nuisance if I want to go to a subdirectory elsewhere called research-notes. If I'm in the parent of that, and I type research, Tab and hit Enter without concentrating, I'll end up in /local/www/html/research by accident. You'll need to concentrate to make good use of this one!
OK, so $CDPATH can be useful. But the major improvement to the regular autocompletion is programmable completion. You can write completion functions all by yourself, but to avoid extensive wheel-reinvention, I recommend downloading /etc/bash_completion from www.caliban.org/bash (also available as a package for most distros), and then sourcing that file in your .bashrc (with the line source /etc/bash_completion).
Done? OK, open a new terminal window, then try typing ssh at the command line, then hit Tab. You'll be shown a list of hosts that you might want to ssh to, based primarily on your known_hosts file. A bunch of other commands will do the same thing – try traceroute or ftp.
What about extending this to enable host autocompletion for scripts of your own? If you take a look at /etc/bash_completion, it has a bunch of subroutines beginning with an underscore (such as _known_hosts), and then a line that looks like this:
complete -F _known_hosts traceroute ping telnet host ssh
So if you want to add your command to that list, just add it on the end after ssh, re-source the file, and you should be good to go.
-F is an option that identifies the function to use for completion – anything returned by the function (here, _known_hosts) is a possible completion option. -C is a similar option that executes a command and takes the output from that as possible completion. So what if you want to write your own completion function? Here's a very basic sample function that you can add to /etc/bash_completion, which looks up a partial hostname in LDAP.
_ldapcomplete() { COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} output=`ldapsearch -Q “(&(cn=$cur*) (objectClass=ipHost))” cn | grep ^cn:` name=${output#* } COMPREPLY=( ${COMPREPLY[@]} $name ) return 0 } complete -F _ldapcomplete ssh
The first line sets up the value that'll be used for the completion return (COMPREPLY). Next, $cur is set to the value that's been passed into the function, ie, what's already been input on the command line. The COMP_WORDS variable is set up earlier in /etc/bash_completion for use by all functions. So $cur is what we're trying to complete.
The ldapsearch line looks for an entry in the LDAP directory whose name starts with our existing input, and which is a host-type object, and then strips out all the extraneous LDAP information. $output will look like:
cn: hostname
which is no good for completion as it stands. So the line after that splits on whitespace and takes the second half of $output, to give just hostname. This value is put into COMPREPLY, and the function finishes. In fact, this case deals with only one value, so the line could be
COMPREPLY=$name
But to match the rest of the file, and to allow for later improvement, we treat it as an array.
To use the function, the complete -F _ldapcomplete ssh line is needed: this tells the shell that when using ssh. This particular function should be used for hostname completion. I've already mentioned the major drawback of this function: it doesn't deal with multiple return possibilities, so it will successfully autocomplete only when LDAP returns a single value (otherwise it produces errors). This is your chance to experiment a bit; try fixing it so that it deals properly with multiple returns.
Custom command-line completion in action - this looks up LDAP hosts when Tab is pressed.
The Bash history is very useful to avoid having to remember complicated commands. Ctr+R searches backwards through your history incrementally (so to find something referring to a filename that starts with dsl, type dsl then hit Ctrl+R), or the arrow keys will move backwards through the history line by line. However, the existence of multiple identical commands can be really annoying when you're searching through your history. Try setting this:
export HISTCONTROL=ignoreboth
...to leave consecutive duplicate commands out of your history altogether. This variable can also take other values: erasedups will remove all matching commands, not just ignore consecutive ones; and ignorespace will ignore lines that begin with a space.
If you often have multiple terminal windows open at the same time, try adding this line to your .bashrc file:
shopt -s histappend
This makes multiple shells all write to the same history file – avoiding the problem whereby you log out of multiple shells and only get the history of the last one. You can also expand your history with the line:
export HISTSIZE=1000000 HISTFILESIZE=1000000
...so that you have access to a much larger chunk of your past command history.
As you can see, there's a lot more to Bash than you might have been aware of. It's well worth spending some time with the man page – it's surprisingly easy to read. Keep learning new things and improve your shell experience further!