Defensive BASH Programming

If you write any Bash code that lasts more than a day, you should definitely read “Defensive BASH Programming” and follow the advice, if you haven’t already.  It covers the following:

  • Immutable global variables
  • Everything is local
  • main()
  • Everything is a function
  • Debugging functions
  • Code clarity
  • Each line does just one thing
  • Printing usage
  • Command line arguments
  • Unit Testing

All that with code examples and explanation of importance.


dotfiles – your unofficial guide to dotfiles on GitHub

Warning: you will lose a lot of sleep if you follow the link below. :)

No matter how well you know Vim, bash, git, and a whole slew of other command line tools, I promise you, you’ll find something new, something you had no idea existed, something that will help you save hours and hours of your life by shaving off a few seconds here and there on the tasks you perform on a daily basis, in the repositories link to from this site.

I think I’ve spent most of my Sunday there and my dotfiles are so different now that I’m not sure I should commit and push them all in one go.  I think I might need to get used to the changes first.

Some of the things that I’ve found for myself:

  • PHP Integration environment for Vim (spf13/PIV).
  • myrepos – provides a mr command, which is a tool to manage all your version control repositories.
  • bash-it – a community Bash framework.
  • Awesome dotfiles – a curated list of dotfiles resources.

… and a whole lot of snippets, tips, and tricks.

P.S.: Make sure you don’t spend too much time on these things though :)

Shell parameter expansion : default values for shell script parameters

When writing shell scripts, it’s often useful to accept some command line parameters.  It’s even more useful to have some defaults for those parameters.  Until now I’ve been using if statements to check if the parameter was empty, and if it was, to set it to the default value.  Something like this:



if [ -z "$DB_HOST" ]

if [ -z "$DB_NAME" ]

if [ -z "$DB_USER" ]

echo "Connecting to the database:"
echo "Host: $DB_HOST"
echo "Name: $DB_NAME"
echo "User: $DB_USER"
echo "Pass: $DB_PASS"

It turns out there is a much more elegant way to do this with shell parameter expansion.  Here is how it looks rewritten:



echo "Connecting to the database:"
echo "Host: $DB_HOST"
echo "Name: $DB_NAME"
echo "User: $DB_USER"
echo "Pass: $DB_PASS"

This is so much better. Not only the script itself is shorter, but it’s also much more obvious what is going on.  Copy-paste errors are much less likely to happen here too.

I wish I learned about this sooner.

Troubleshooting with /dev/tcp and /dev/udp

Imagine you are on a freshly installed Linux machine with the minimal set of packages, and you need to test network connectivity.  You don’t have netcat, telnet, and your other usual tools.  For the sake of the example, imagine that even curl and wget are missing.  What do you do?

Well, apparently, there is a way to do this with plain old bash.  A way, which I didn’t know until today.  You can do this with /dev/tcp and /dev/udp. Here is an example verbatim from the Advanced Bash-Scripting Guide:

# /dev/tcp redirection to check Internet connection.

# Script by Troy Engel.
# Used with permission.       # A known spam-friendly ISP.
TCP_PORT=80                # Port 80 is http.
# Try to connect. (Somewhat similar to a 'ping' . . .) 
echo "HEAD / HTTP/1.0" >/dev/tcp/${TCP_HOST}/${TCP_PORT}

: <<EXPLANATION If bash was compiled with --enable-net-redirections, it has the capability of using a special character device for both TCP and UDP redirections. These redirections are used identically as STDIN/STDOUT/STDERR. The device entries are 30,36 for /dev/tcp: mknod /dev/tcp c 30 36 >From the bash reference:
    If host is a valid hostname or Internet address, and port is an integer
port number or service name, Bash attempts to open a TCP connection to the
corresponding socket.

if [ "X$MYEXIT" = "X0" ]; then
  echo "Connection successful. Exit code: $MYEXIT"
  echo "Connection unsuccessful. Exit code: $MYEXIT"

exit $MYEXIT


Explain Shell

Here’s a good resource for all of those who is trying to learn shell and/or figure out complex commands with lots of parameters and pipes – Explain Shell.


You just paste the command and hit the “Explain” button, and the site will decompose the command into parts, providing relevant parts from the manual pages.  There are a few examples to try it out on too.

Accessing current username in sudo scripts on CentOS

I got a bit of a puzzle at work today.  I had a script that was executed as another user via sudo, but I wanted to access the original username in the script, to know who was executing it.  Sudoers manual suggest working with “Defaults env_keep“.  Looking into the /etc/sudoers, I noticed that $USERNAME variable was whitelisted (in line #3 below):

Defaults env_reset

So, I tried to use the $USERNAME variable in my script but it was coming up with empty results.  That made me look deeper into default Bash initialization, and I found out that $USERNAME variable setup wasn’t a part of it.  However, $LOGNAME was (in /etc/profile).  I think, so few people actually use it that nobody noticed or bothered about it until now.  Anyway, the solution now was obvious – simply add $LOGNAME variable to the sudo white list.  Appending this line to the above env_keep ones did the job:

Defaults    env_keep += "LOGNAME"

There. In hopes it will help future generations…

P.S.: All that happened on a more or less default installation of CentOS 6.3, but I’m sure other Red Hat based distributions have a similar issue.

P.P.S.: If your script is ALWAYS invoked via sudo, also have a look at $SUDO_UID, $SUDO_GID, and $SUDO_USER variables.

Color builder for 256-color terminal

Color builder for 256-color terminal

With most Linux terminals now supporting 256 colors, a tool like this one is mighty useful in building the escape sequences.  For off-line use, all code is available in GitHub repository, and all logic behind the calculations is explained in this Habrahabr post (albeit, in Russian).

Bash directory bookmarks

While reading through the comments to this Habrahabr article (in Russian), I came across an excellent tip for the directory bookmarks in bash shell.  Here’s how to set it up.

Firstly, add the following lines to your .bashrc or .bash_profile file:

# Bash Directory Bookmarks
# From:
alias m1='alias g1="cd `pwd`"'
alias m2='alias g2="cd `pwd`"'
alias m3='alias g3="cd `pwd`"'
alias m4='alias g4="cd `pwd`"'
alias m5='alias g5="cd `pwd`"'
alias m6='alias g6="cd `pwd`"'
alias m7='alias g7="cd `pwd`"'
alias m8='alias g8="cd `pwd`"'
alias m9='alias g9="cd `pwd`"'
alias mdump='alias|grep -e "alias g[0-9]"|grep -v "alias m" > ~/.bookmarks'
alias mload='source ~/.bookmarks'
alias ah='(echo;alias | grep "g[0-9]" | grep -v "m[0-9]" | cut -d" " -f "2,3"| sed "s/=/ /" | sed "s/cd //";echo)'

Secondly, if you are already using ~/.bookmarks file for anything, change the two references to it in the above lines to some other file.  It’s where your directory bookmarks will be stored.

Thirdly, if you prefer to save the bookmarks between your bash sessions, add the “mload” command to the end of your .bashrc or .bash_profile file, and “mdump” to your .bash_logout file.

Start a new bash shell and you are all set.

Using this setup is extremely easy.  Navigate to the directory that you want to bookmark, and save it under the numbered bookmark:

$ cd /var/www/html
$ m1

When you want to navigate back to that folder, simply call the numbered bookmarks:

$ g1

If you need to refresh your memory, issue “ah” command (think: aliases help), and it will print out the list of your current numbered bookmarks with the directory paths associated with them.

In case you need more than nine bookmarks, simply extend the lines in your .bashrc or .bash_profile file to run through more numbers, or use some other bookmark naming convention that works for you.


P.S.: A quick Google search points to the author’s page, which links to a more advanced solution – bashmarks.