Bash-Completion, an example

Hello

I've followed several bash-completion threads, usualy none were answered, because of obvious reasons. (of which i'm just aware since a day or a few)
Non-the-less, because i was writing scripts, i never considered it to be possible to add this functionality.

Also, because i though that my 'script idea' for which i wanted bash-completition seemed quite challenging, since it is based upon files within several folders, using the folders as arguments to the actual script, i thought it was too difficult.
I was terribly wrong! :slight_smile: :cool:

First of all, read into: An Introduction to Programmable Completion
Then look into some existing ones in: /usr/share/bash-completion/completions or /etc/bash_completion.d

Once you understand that it is only checking the current(last) and previous 'argument', comparing them with the first (the application), the achievment seems alot easier.

For this example, you should know, that the 'application' in this case, has these 'arguments':
(The find command lists all items found in directory 'menu', sed replacing 'menu' with 'ds' and every '/' with a space, removing 'default' items, since they will not be shown in tab-completion)
So for the application 'ds' i have these arguments, which are based on folders and files.

find menu|sed s,"/"," ",g|sed s,menu,ds,g|grep -v default

ds
ds prj
ds prj edit
ds prj rpm
ds prj rpm changelog
ds prj rpm edit
ds prj rpm make
ds prj rpm list
ds prj rpm add
ds prj git
ds prj git edit
ds prj git commit
ds prj git status
ds prj git upl
ds prj git add
ds prj ks
ds prj ks edit
ds prj ks make
ds prj ks add
ds prj list
ds ssh
ds ssh make
ds ssh info
ds ssh setup
ds make
ds review
ds new
ds new manpage
ds setup
ds add

NOTE: What in this example is named 'cur' and 'prev', could be named anything else, or would not even need to be set, as they simply represent: $2 (prev) and $3 (cur).
You can check yourself, by simply adding:

echo "1$1 2$2 3$3 4$4 5$5"

Into any existing function within that file, also this helps debuging.
Speaking of which, the easiest way to test new changes, is to source the file providing the bash-completition code.

Anyway, so given above list, and knowing that it only checks $2 (prev) and $3 (cur), it is the simplest to work with a case statement.
The $2 (cur) is used if you are currently writing an argument.
The $3 (prev) is when you are looking for new words.
So we need a case statement for $2 (cur) and for $3 (prev).

This said, we need to decide wether we can use a single list several times, read out the values of a directory, or decide upon even diffrent circumstances and call even another function.
To get started, i decided to set a specific list to some of the entries, and share a single list for multiple entries.
So you have both, and its simpler to handle, as the list applies to them, and i took care that the given words expect the same values, so those lists can be shared.

Anyway, so looking at the application 'ds', we see its first line of arguments would be: prj new ssh add review setup
Looking at the 'pre' argument word 'prj', its values would be: ks git rpm edit list
And so forth...
So one simply makes a list for each of the entry.

Luckily, we can use the 'string mechanism' used here, to use regular bash commands.
So, instead of a list of words inside the quotes after an -W, you could use something like:

$(grep <pattern> <file>)
# or
$(cd /some/dir ; ls)

And when all this is put together, it looks like this:
(This is an example of my first completion-task, and may not be a perfect one, but simple and working)

# bash completion for dev-scripts
# file: /etc/bash_completion.d/dev-scripts_compl.bash
# 2014.11.21 by sea, based on blkid
# ---------------------------------

_dev-scripts_module()
{
#
#	Variables
#
	local cur prev OPTS DIR
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[COMP_CWORD-1]}"
	OPTS="-3 -6 -h -v"
	DIR="$HOME/.config/dev-scripts/prjs"
#
#	Action
#
	# This completes the custom entries from $DIR
	# But only use this, if 'prev' was one using entries from $DIR
	# This list is dynamicly (automaticly) updated
	case $prev in
	"-"[36v]|add|make|list|edit|review)
		case $cur in
		[a-zA-Z]*)	COMPREPLY=( $( compgen -W "$(cd $DIR 2>/dev/null && echo $cur*)" -- "$cur" ) ) 
				return 0
				;;
		esac
		;;
	esac
	
	# This completes the word you are currently writing
	# These need manual maintainance
	case $cur in
		-*)	COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
			return 0
			;;
		a*)	COMPREPLY=( $(compgen -W "add" -- $cur) )
			return 0
			;;
		c*)	COMPREPLY=( $(compgen -W "changelog commit" -- $cur) )
			return 0
			;;
		e*)	COMPREPLY=( $(compgen -W "edit" -- $cur) )
			return 0
			;;
		g*)	COMPREPLY=( $(compgen -W "git" -- $cur) )
			return 0
			;;
		i*)	COMPREPLY=( $(compgen -W "info" -- $cur) )
			return 0
			;;
		k*)	COMPREPLY=( $(compgen -W "ks" -- $cur) )
			return 0
			;;
		l*)	COMPREPLY=( $(compgen -W "list" -- $cur) )
			return 0
			;;
		m*)	COMPREPLY=( $(compgen -W "make manpage" -- $cur) )
			return 0
			;;
		n*)	COMPREPLY=( $(compgen -W "new" -- $cur) )
			return 0
			;;
		p*)	COMPREPLY=( $(compgen -W "prj" -- $cur) )
			return 0
			;;
		r*)	COMPREPLY=( $(compgen -W "review rpm" -- $cur) )
			return 0
			;;
		s*)	COMPREPLY=( $(compgen -W "setup ssh" -- $cur) )
			return 0
			;;
	esac
	
	# This shows a list of words applying to your last argument
	# These need manual maintainance
	case $prev in
	# This lists the entries of current path.
	#	'-c')
	#		local IFS=$'\n'
	#		compopt -o filenames
	#		COMPREPLY=( $(compgen -f -- $cur) )
	#		return 0
	#		;;
		"-"[36v]|add|make|list|edit|review)
			COMPREPLY=( $( compgen -W "$(cd $DIR 2>/dev/null && echo *)" -- "$cur" ) ) 
			return 0
			;;
		rpm)
			COMPREPLY=( $(compgen -W "add changelog edit list make" -- $cur) )
			return 0
			;;
		ks)
			COMPREPLY=( $(compgen -W "add edit make" -- $cur) )
			return 0
			;;
		prj)	
			COMPREPLY=( $(compgen -W "git ks rpm edit list" -- $cur) )
			return 0
			;;
		git)
			COMPREPLY=( $(compgen -W "add commit edit status upl" -- $cur) )
			return 0
			;;
		ssh)
			COMPREPLY=( $(compgen -W "info make setup" -- $cur) )
			return 0
			;;
		new)
			COMPREPLY=( $(compgen -W "manpage" -- $cur) )
			return 0
			;;
		ds)
			COMPREPLY=( $(compgen -W "new prj ssh add make review setup" -- $cur) )
			return 0
			;;
		'-h')
			return 0
			;;
	esac
}
# Actualy make it available to the shell
complete -F _dev-scripts_module ds

Now to test this, all you would need to do is this, in order:

  1. Save the above text as: ./dev-scripts_compl.bash
  2. text source ./dev-scripts_compl.bash
  3. text touch ./ds ; chmod +x ./ds
  4. text ./ds<hit-the-tab>

NOTE:
When testing, you obviously would not get an output as the screenshot shows, as the file created by 'touch' is empty.
But the completition will work, as it depends on the ./dev-scripts_compl.bash, just checking for an executeable named 'ds'.

Hope this helps you to get started,
enjoy your bash-completions :slight_smile: