{"id":282,"date":"2016-10-13T02:25:46","date_gmt":"2016-10-13T01:25:46","guid":{"rendered":"http:\/\/www.benningtons.net\/?p=282"},"modified":"2017-02-09T00:37:24","modified_gmt":"2017-02-09T00:37:24","slug":"bash-completion","status":"publish","type":"post","link":"https:\/\/www.benningtons.net\/index.php\/bash-completion\/","title":{"rendered":"Bash Completion"},"content":{"rendered":"<p>Command-line completion is a wonderful thing. If you get bored of typing a long filename (for example) you can just hit tab and it will complete the name for you. If the shell can&#8217;t be certain of which filename you wanted then you can hit tab again and either (depending on the shell) get a list of matching names or toggle through a list of possibilities.<br \/>\nSome may say that&#8217;s just being lazy but I prefer to see it as being efficient and surely the less typing you do then the fewer mistakes you&#8217;ll make?<br \/>\nThe only downsides of command-line completion are a) that it lulls you into thinking really long filenames are ok and b) that you&#8217;ll find yourself hitting tab out of habit whenever you find yourself having to type a long name, which can be annoying when you&#8217;re not even at a command-line.<br \/>\n<nl><br \/>\nWhilst working on a small project to create some shell commands of my own I realised that completion didn&#8217;t work for them. So I investigated further and was impressed by how easy completion was to configure and how you can use it to do more than just complete the current word.<br \/>\nHere is a summary of my findings whilst using the Bash shell on Debian 8 (Jessie).<br \/>\n<nl><\/p>\n<p><b>Setup:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">Wherever you&#8217;re using Bash you&#8217;ll probably find basic completion already configured. But for more advanced programmable completion you&#8217;ll need to install the <b>bash-completion<\/b> package.<br \/>\n<nl><br \/>\nWhen starting a terminal session, one of the Bash startup scripts (either <b>\/etc\/bash.bashrc<\/b> for all users or your own <b>~\/.bashrc<\/b>) will configure completion by running <b>\/usr\/share\/bash-completion\/bash-completion<\/b>.<br \/>\nCompletion rules for individual commands can be found within the files in <b>\/usr\/share\/bash-completion\/completions\/<\/b> and in <b>\/etc\/bash-completion.d\/<\/b>. The former appears to be for commands that came with your distro and the latter for subsequent additions.<br \/>\n<nl><br \/>\nSo having looked at the examples already in these directories I decided to try and create one for my project. As I&#8217;m not changing any existing files then I don&#8217;t appear to be at risk of breaking anything. My new addition is called <b>ce_complete.sh<\/b> and I&#8217;m placing it in <b>\/etc\/bash-completion.d\/<\/b>. To do so I need to use sudo so that system changes are made by the root user.<br \/>\n<nl><br \/>\nNote that after making any changes to these files you need to reload your Bash completion settings. This can be done by closing your terminal session and opening a new one (to cause the startup scripts to run) or you could simply re-run the completion config script with <b>. \/etc\/bash-completion<\/b> (which just runs the <b>\/usr\/share\/bash-completion\/bash-completion<\/b> script). Note the space in <b>. \/<\/b> at the start of that command. That ensures the script is run by your current process and not in a spawned process that would reload its own completion rules instead of your own.<br \/>\n<nl><br \/>\nNow let&#8217;s look at the contents of my completion script as I add more features.<br \/>\n<nl><\/p>\n<p><b>For commands:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">I can&#8217;t say I&#8217;m comfortable with the use of completion on commands. Commands generally have very short names so completion shouldn&#8217;t be necessary and if you&#8217;ve forgotten the full command name then typing a few letters and then hoping for completion to fill-in the rest is dangerous.<br \/>\nHowever, Bash provides command completion by checking all the directories listed in the <b>PATH<\/b> variable to find a matching file. Use <b>echo $PATH<\/b> to see what those directories are and to test it try typing <b>c<\/b> followed by two tabs to get a list of all commands starting with &#8216;c&#8217;.<br \/>\nOne of my own commands is called <b>clice<\/b> and I&#8217;ve placed the executable file in <b>\/usr\/local\/bin\/<\/b> so it&#8217;s now listed amongst all those commands that start with &#8216;c&#8217;.<br \/>\nI can also type <b>cli<\/b> followed by tab to complete the command name but I don&#8217;t recommend it.<br \/>\n<nl><br \/>\nLong name command options however can be a pain to type and they should be descriptive enough to make completion safe. So <code>cp --str<\/code> followed by a tab will expand to <code>cp --strip-trailing-slashes<\/code> which is a useful time saver.<br \/>\nI realise that cp doesn&#8217;t need the full name and that <code>cp --str<\/code> is unambiguous enough for it to complete the task but it&#8217;s reassuring to complete the full name and confirm that I&#8217;ve picked the right option.<br \/>\n<nl><br \/>\nAs every command appears to complete their long name options then I feel I should do the same for my <b>clice<\/b> command. Even though those options are currently just the standard <code>--version<\/code> and <code>--help<\/code>.<br \/>\nTo achieve this my <b>\/etc\/bash-completion.d\/ce_complete.sh<\/b> file now looks like this:<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n_ce_clice()\r\n{\r\n\tlocal cur prev words cword\r\n\t_init_completion || return\r\n\r\n\tlocal opts=&quot;--help --version&quot;\r\n\tCOMPREPLY=( $( compgen -W &quot;$opts&quot; &quot;$cur&quot; ) )\r\n\r\n\treturn 0\r\n}\r\ncomplete -F _ce_clice clice\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">[Remember that any such changes will only take effect once you have restarted your terminal session or reloaded completion settings (see above).]<br \/>\nStarting at the bottom of this code snippet is the <b>complete<\/b> command that links my command <b>clice<\/b> with the function <b>_ce_clice<\/b>. So whenever I now type clice and press tab, bash will call the function that makes up the rest of this code snippet.<br \/>\nThe <b>_ce_clice<\/b> function starts in the same way as all other completion functions by declaring some local variables and then initialising them. Returning to the command line should that initialisation fail.<br \/>\nThose variables are:<br \/>\n<b>cur<\/b>=what&#8217;s been typed of the current word,<br \/>\n<b>prev<\/b>=the previous word in the command-line,<br \/>\n<b>words<\/b>=the command name and<br \/>\n<b>cwords<\/b>=a count of previous words on the command-line<br \/>\n<nl><br \/>\nThe boilerplate for Bash completion functions then has a gap where I declare a local variable <b>opts<\/b> and load it with a space separated list of my command options before the boilerplate continues.<br \/>\nThe function provides a response back to the command-line by loading the <b>COMPREPLY<\/b> variable with a list of valid completion results. If there is only one result then that will be inserted into the command-line.<br \/>\nTo generate that list of results the <b>compgen<\/b> function is used to list all my command options in variable <b>opts<\/b> that match the partially typed word in variable <b>cur<\/b>.<\/p>\n<p><nl><\/p>\n<p><b>For filenames:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">In addition to the <b>clice<\/b> command my project includes a shell script for managing how I edit programs. The script is called <b>ce_edit.sh<\/b> which I place in <b>\/usr\/local\/bin\/<\/b> but to make it easier to use I have aliased it to the command name <b>edc<\/b> by adding <b>alias edc=&#8217;. ce_edit.sh&#8217;<\/b> to my bash startup script <b>~.bashrc<\/b>. Note the space again in <b>=&#8217;. ce<\/b> to ensure this command runs in my current process and not a spawned process.<br \/>\n<nl><br \/>\n<b>ce_edit.sh<\/b> takes the filename that I wish to edit as an argument, so command-line completion would be very helpful. To set this up I added the following code to the same <b>ce_completion.sh<\/b> script that my previous <b>clice<\/b> command uses. I could use separate completion scripts but I prefer to keep everything related to my project together. As my completion commands call different completion functions then there is no confusion<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n_ce_edit()\r\n{\r\n\tlocal cur prev words cword\r\n\t_init_completion || return\r\n\r\n\tCOMPREPLY=( $( compgen -f ${cur} ) )\r\n\r\n\treturn 0\r\n}\r\ncomplete -F _ce_edit edc\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">As you can see, this contains the same boilerplate code with a couple of changes. Firstly <b>compgen<\/b> is using a <b>-f<\/b> option to generate a list of files in the current directory that match the partially typed filename in the <b>cur<\/b> variable.<br \/>\nSecondly note that the <b>complete<\/b> command at the bottom is linking my aliased <b>edc<\/b> command to this new function and that it does not use the full script name of <b>ce_edit.sh<\/b>.<br \/>\n<nl><br \/>\nI can now type <b>edc ce_c<\/b> followed by a tab to get <b>edc completion.sh<\/b> which is really useful.<br \/>\n<nl><\/p>\n<p><b>Beyond word completion:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">I could have stopped there but, having noticed that these completion functions are just Bash scripts, I wondered if I could do more than just complete the current word on the command-line?<br \/>\n<nl><br \/>\nCompletion for the <b>edc<\/b> command works well but I need to be in the right directory in the first place.<br \/>\n<nl><br \/>\nAs a naming convention I give each project a two letter prefix (<b>ce<\/b> for my &#8220;command-line coding ecosystem&#8221; project), I keep all the code for each project in a seperate directory (<b>~\/Code\/CE\/<\/b> for this project) and I start each filename with the same project prefix (e.g. <b>ce_completion.sh<\/b> and <b>ce_edit.sh<\/b> for this project). So the source code for <b>clice<\/b> can be found in <b>~\/Code\/CE\/ce_clice.c<\/b><br \/>\n<nl><br \/>\nSo in theory I could be in any directory and type <b>edc ce_c<\/b> followed by a tab and my completion function should have enough detail to know where I need to go and which file I want to edit.<br \/>\nI&#8217;ve implemented this with the following additions to the <b>_ce_edit<\/b> completion function.<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n_ce_edit()\r\n{\r\n\tlocal cur prev words cword\r\n\t_init_completion || return\r\n\r\n# Do we have at least two letters to determine the project directory?\r\n\tif [[  ${#cur} &gt; 1 ]]\r\n\t then\r\n\t\tSOURCEDIR=&quot;\/home\/ben\/Code\/$(echo ${cur:0:2}|tr [a-z] [A-Z])&quot;\r\n\t\tif [ -d &quot;$SOURCEDIR&quot; ]                          # does the project directory exist?\r\n\t\t then\r\n\t\t\tcd ${SOURCEDIR}\/\r\n\t\t\tCOMPREPLY=( $( compgen -f ${cur} ) )\r\n\t\tfi\r\n\tfi\r\n\r\n\treturn 0\r\n}\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">Some may shudder at the thought of a completion function changing your working directory but this works a treat and saves a lot of time when editing code at the command-line.<br \/>\nYou may have noticed that I haven&#8217;t managed to get the function to resolve <b>~<\/b> to my home directory so I&#8217;ve had to type that in full for now.<br \/>\n<nl><br \/>\nThe <b>echo<\/b> and <b>tr<\/b> commands convert the first two letters at the start of a filename to uppercase for the directory name.<br \/>\nThis can&#8217;t resolve the project directory unless two letters are typed. For completeness I should look to enable a single letter, say &#8216;c&#8217;, to resolve to CE or to a list of all projects starting with &#8216;c&#8217;.<br \/>\nThat&#8217;s on my #TODO list but in the meantime I&#8217;m impressed by how easy Bash completion was to setup for my project.<br \/>\n<nl><br \/>\nAll code for this and my other projects can now be found on my GitHub account: <a href=\"https:\/\/github.com\/agben\">https:\/\/github.com\/agben<\/a><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Command-line completion is a wonderful thing. If you get bored of typing a long filename (for example) you can just hit tab and it will complete the name for you. If the shell can&#8217;t be certain of which filename you wanted then you can hit tab again and either (depending on the shell) get a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[9],"tags":[28,10],"_links":{"self":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/282"}],"collection":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/comments?post=282"}],"version-history":[{"count":11,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/282\/revisions"}],"predecessor-version":[{"id":293,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/282\/revisions\/293"}],"wp:attachment":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/media?parent=282"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/categories?post=282"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/tags?post=282"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}