Tuesday, June 30, 2009

Episode #49: There's No Place Like %HOMEPATH%

Ed.bat >> \\commandlinekungfu.com\2009\06\episode-49-theres-noplace-like-homepath.html

It was so long ago, yet I remember it like it was yesterday. We were just a bumptious young group of shell jocks, plying our trade, trying to make the world a better place. Life was simple then, and fun. Faithful readers may recall, with a certain nostalgia, Episode #28, a friendly little ditty about environment variables. We focused on methods for setting them, displaying them, and interacting with a couple of them, namely those associated with the PATH, the current username, and the prompt. Ahhhhh... the good old days.

Well, although times have changed and we're all a little worse for the wear, I'd like to build on and extend the ideas from that episode. This time around, we'll list our favorite variables that we didn't cover before and show some useful applications of them.

As I mentioned in Episode #28, the "set" command lists environment variables, we can expand a variable name into its value by surrounding it with percent signs (%var%), and the variable names themselves are case insensitive. Just to make it easier to type, I almost always refer to my variables in all lower case, even though many are specifically defined with upper case or mixed case.

One of the more useful variables we have is homepath, which is a reference to our current user's home directory. We don't have the nifty ~ to refer to our home directory as our bashketeer friends do, but we can get similar functionality with:

C:\> cd %homepath%
C:\Users\Ed>

Another useful environment variable is computername, which stores the name of the local machine. In some environments, the local computer's hostname is a byzantine mess of obtuse nonsense that is hard to type. Yet, we can always refer to it simply using %computername% in our commands. For example, here's how we can make a null SMB connection with ourselves, referring to our computer name:

C:\> net use \\%computername% "" /u:""
The command completed successfully.

C:\> net use \\%computername% /del
\\DOOFENSHMIRTZ was deleted successfully.

While the output of the set command shows us all of our statically set variables, there are some other very useful variables that aren't static, such as %cd% (which is the current working directory), %date% (which is the current date, used in Episode #48), and %errorlevel% (which is an indication of whether the previously executed command had an error, as I mentioned in Episode #47). I'd like to discuss two more dynamic variables in more detail, because they can be pretty darned useful: %time% and %random%.

The %time% variable displays the current time down to one-hundredths of a second. We can use that to get a feel for how long it takes a given command or series of command to run, sort of emulating the Linux time command. Back in Episode #21, I actually resorted to using the Cygwin time command to measure how long it took to run the command "dir /s /b C:\ | findstr /i vmware > nul". Instead of relying with Cygwin, I could have used the %time% variable as follows:

C:\> cmd.exe /v:on /c "echo !time! & (dir /s /b C:\ | findstr /i vmware > nul)
& echo !time!"

9:07:34.54
9:07:40.45

In this command, I'm invoking a cmd.exe with /v:on to turn on delayed environment variable expansion so that my time will expand to its current value when I call it (as described in Episode #48). Otherwise, it'll always be the same value it contains when I hit Enter. I tell my cmd.exe to run the command (/c) that will echo !time! (remember, delayed variable expansion requires us to refer to !var! and not %var%). It then runs whatever we have inside the parens (), which is the command whose running time we want to determine. You don't really need the parentheses, but I included them to help offset the command we are timing. Then, after our command is finished running, I display the current time again. While this will not do the math for us of showing the delta in times like the Linux time command (that would require a little script to parse the output), it does give us a decent feel for how long it took to run a command, without having to install Cygwin.

And, that gets us to %random%, a variable that expands to a random number between 0 and 32767. We can turn that into a random number between 0 and whatever we'd like (less than 32767) by making our shell do math with the set /a command (described in Episode #25), applying modulo arithmetic using the % operator:

To create a random number between 0 and 99:

C:\> set /a %random% % 100
42
To create a random number between 1 and 10:

C:\> set /a %random% % 10 + 1
7
And, finally, to flip a coin:

C:\> set /a %random% % 2
1
That last one should save you some change.

Now, let's see what Hal.sh has up his sleeve.

Hal won't get sucked in:

Hal.sh? Ed, I refuse to participate in these twisted little scenarios with you anymore. I would have thought you'd be tired after last night! And by the way, that French Maid outfit does nothing for your legs, big guy.

So, hmmm, let's see. Oh yeah! Shell fu!

Just to quickly give you the shell equivalents of Ed's rogue's gallery of Windows variables, I present:

$ echo $HOME
/home/hal
$ echo $HOSTNAME
elk
$ echo $PWD
/tmp
$ echo $RANDOM
29597
$ echo $(( $RANDOM % 10 + 1 ))
3

There's no variable in bash to tell you the time because we have the "date" command. With a formatting options, we can get date to give us the time to nanosecond precision:

$ date +%T.%N
10:41:29.451948717

We can even incorporate the time into our shell prompt and/or history trail:

$ export HISTTIMEFORMAT='%T  '
$ export PS1='(\t)$ '
(10:49:53)$ history
1 10:49:34 export HISTTIMEFORMAT='%T '
2 10:49:53 export PS1='(`date +%T`)$ '
3 10:49:56 history
(10:49:56)$

HISTTIMEFORMAT supports the standard escape sequences used by the strftime(3) library call. Usually, this means your choices are a little more limited compared to all of the various options available to the date command on most Linux systems (no %N, for example) but are typically more than sufficient. Notice that the bash shell has also has built-in date/time escape sequences that can be used in prompts-- like the "\t" in our example above.

But there are a bunch of other shell variables that I find useful. For example, CDPATH is a list of directories the shell will search through when you use a non-absolute path with the cd command. For example:

$ export CDPATH=.:$HOME
$ ls
$ cd stuff
/home/hal/stuff
$

".:$HOME" is the most common setting for CDPATH, but if you have a directory that you commonly access-- your music or DVD collection, a programming projects directory, etc-- adding that directory to CDPATH can save you a lot of typing.

Then there are all the common shell variables that are used by various programs that you might execute:

$ export EDITOR=emacs
$ export VISUAL=emacs
$ export PAGER=less
$ export TMPDIR=$HOME/.tmp

EDITOR and VISUAL are the programs that should be invoked when a program (like "crontab -e" for example) wants to allow you to edit text, and PAGER is your preferred screen reader. Many (but not all) programs will also use the value of TMPDIR as a place to put temporary files. This helps you avoid the risks of world-writable shared directories like /tmp and /var/tmp.

And, of course, many programs in the Unix environment have their own special shell variables. For example, $DISPLAY for X applications, $SSH_AUTH_SOCK when you're using ssh-agent, and so on. Also there are variables that allow you to configure your preferred program defaults-- like the "export LESS='-eMqw'" setting I have in my .bashrc file.