Intro

A lot of time I spend in linux comand line. Sometimes I write small scripts which help me in my regular work. Often the scripts use set of the commands and parameters. I do not remember them all. I’ve to look at the source to refresh it in my memory and every time I wish I had completion for my script.

Now I know how! This article is not comprehanive tutorial. It just allows you to start work with bash completion and unfolds two ways how you can use it.

As a case study let’s use the following simple program.

Usage:
  testwar.sh <commnad>

Example:  
  testwar.sh download	# Downloads latest build from CI
  testwar.sh deploy	# Deploys test.war and starts server
  testwar.sh start	# Starts test server
  testwar.sh stop	# Stops test server

And I want to use bash completion for this program.

Completion info in the same file

Look at the following script. It contains both

  • bash completion function which is part of the same script
  • emulation of working code (real functions’ body replaced with echo ‘…’)
Completion in the same script file download: testwar.sh
 1 #!/bin/bash
 2 
 3 function download(){ echo "Downloads war from CI" ; }
 4 
 5 function deploy(){ echo "Deploys war file" ; }
 6 
 7 function start(){ echo "Starts server" ; }
 8 
 9 function stop(){ echo "Stops server" ; }
10 
11 get_functions(){
12   cat $0 | sed -n /^function/p | sed -e "s|function  *\([^(]*\).*|\1|"
13 }
14 
15 show_completion(){
16   # Get current incomplete completion word
17   local cur=$(echo "$COMP_LINE" | sed -e "s|$0 *.* \([^ ]*\)|\1|")
18   # Get number of spaces
19   let num=$(echo "$COMP_LINE" | sed -e "s|[^ ]||g" | wc -m)
20 
21   # Stop show completion if more than 2 words in $COMP_LINE
22   # first word is prog name ($0)
23   # second word is completed command followed by space
24   if [ $num -lt 3 ] ; then
25     compgen -W '`get_functions` FROM_SAME_FILE' -- $cur
26   fi
27 }
28 
29 run_command(){
30   echo "running command '$1'"
31   for i in `get_functions` ; do
32     if [ "$i" == "$1" ] ; then
33       eval $1
34       return 0
35     fi
36   done
37   echo "Error: unknown command '$1'"
38   return 1
39 }
40 
41 do_work(){ [ $# -eq 1 ] && run_command $1 || usage ; }
42 
43 usage(){ echo "Print usage info..." ; }
44 
45 # Entry point
46 [ -n "${SHOW_COMPLETION}" ] && show_completion $@ || do_work $@

The main interest is the function show_completion() which do all the completion job. This function is activated when SHOW_COMPLETION environment variable is set and not empty. You can see last line in code which switches between returning completion info and normal program flow.

The script uses functions which declared with keyword function as commands. So, method get_functions returns all known commands. This information is used both in auto-completion and in script as well to check whether the command provided by user is known one. When you type program name and then press TAB you see

>./testwar.sh
deploy   download   FROM_SAME_FILE   start   stop

As you can see a const FROM_SAME_FILE is added to the list of the completion words. It’s done to distinguish this example from the second one where bash completion is done with help of the shell function, not the command.

To add completion to the current shell session use the command:

complete -C 'SHOW_COMPLETION=true ./testwar.sh' testwar.sh

Completion info in individual file

The following code example behaves similar to previous one. The two major differences are

  • completion info is in separate file
  • it’s more common way when COMPREPLY and COMP_WORDS shell variables are used.
Individual completion file download: testwar-completion.bash
 1 get_functions(){
 2   cat testwar.sh | sed -n /^function/p | sed -e "s|function  *\([^(]*\).*|\1|"
 3 }
 4 
 5 function _test_war_completion(){
 6   # Get current incomplete completion word
 7   local cur=${COMP_WORDS[COMP_CWORD]}
 8   # Show completion if one or two words in command line
 9   if [ ${#COMP_WORDS[*]} -lt 3 ] ; then
10     COMPREPLY=( $(compgen -W '`get_functions` FROM_FUNCTION' -- ${COMP_WORDS[COMP_CWORD]}) )
11   fi
12   return 0
13 }
14 
15 complete -F _test_war_completion testwar.sh

To add completion to the current shell session:

source testwar-completion.bash 

Commom hints

To list all completion bindings

complete -p

System-wide completion

Of course, you can place testwar.sh into the dir listed in your system $PATH. In such a case you can add complete -C 'SHOW_COMPLETION=true testwar.sh' testwar.sh or source testwar-completion.bash in your ~/.bashrc file.

Filenames completion

If, for instance, you need to add filename after the command you can extend completion by predefined options. To add default auto-completion do complete -C 'SHOW_COMPLETION=true ./testwar.sh' -o default testwar.sh

Exit and return

Do not use exit command in completion function. It’ll exit from current shell - I don’t think it’s what you want when pressing TAB. Use return $some-exit-code instead.

Man

Use man bash to learn about complete and compgen builtin functions.

Completion with function
http://stackoverflow.com/questions/10942919/customize-tab-completion-in-shell

I guess it’s enough to start to play with auto-completion.

-= The end =-

Published

01 May 2014

Category

technical

Tags


blog comments powered by Disqus