« July 2006 | Main | September 2006 »

August 28, 2006

Thanks, Hans, Again...

I don't use KDE, but noblank supports it. I wasn't about to install KDE to test a tiny script, so I did my tests on a machine at work. Somehow when I merged in KDE support, I got rid of the sleep 60 turning the loop into a busy wait. Once again, we have Hans Fugal to thank for correcting my carelessness. I've quietly fixed my previous post. Let's just keep this our little secret.

August 27, 2006

noblank: Part Deux

When I logged in this afternoon, I discovered that Hans Fugal had left me a little feedback:

(16:50:28) Hans: nice script, BUT...
(16:51:00) Hans: you cut off stdin
(12:07:44) Stuart Jansen: good point

My new version fixes that problem, as well as removing the dependency on a Linux style /proc (download file):

#!/bin/bash
kscreensaver=$(dcop kdesktop KScreensaverIface isEnabled 2>/dev/null)
if [ -n $kscreensaver ]; then
  dcop kdesktop KScreensaverIface enable false &>/dev/null
fi
(exec "$@") <&0 &
PID=$!
( while : ; do
    xscreensaver-command -deactivate &>/dev/null
    gnome-screensaver-command --poke &>/dev/null
  done
  if [ -n $kscreensaver ]; then
    dcop kdesktop KScreensaverIface enable $kscreensaver &>/dev/null
  fi
  sleep 60
) &> /dev/null &
wait $PID
kill $!

August 26, 2006

noblank: A Generic Screensaver Preventer

A while ago, I posted an extremely simplistic script for preventing screensaver activation while using gxine. Ever since then, I've felt increasingly embarrassed about leaving a more generic version as "an exercise for the reader". It's time to make amends.

I call the following script noblank. (I also considered calling it noss.) It should be used similarly to nohup. For example, to avoid an annoying interruption while watching a movie:

noblank mplayer dvd://
The beauty of this solution is that it can also be used while leisurely reading a good book:
noblank acroread Alice_in_Wonderland.pdf

And so, without further ado, I present noblank (download file):

#!/bin/bash
kscreensaver=$(dcop kdesktop KScreensaverIface isEnabled 2>/dev/null)
if [ -n $kscreensaver ]; then
  dcop kdesktop KScreensaverIface enable false &>/dev/null
fi
(exec "$@")&
PID=$(jobs -p)
( while [ -d /proc/$PID ] &>/dev/null ; do
    xscreensaver-command -deactivate &>/dev/null
    gnome-screensaver-command --poke &>/dev/null
  done
  if [ -n $kscreensaver ]; then
    dcop kdesktop KScreensaverIface enable $kscreensaver &>/dev/null
  fi
)&
wait $PID

Update: 27 Aug 2006 I've uploaded a new version of my script.

August 18, 2006

The watch & pgrep Commands

Today I found myself using watch to debug a hairy ConTeXt problem. ConTeXt includes a script called texexec that wraps pdfetex and the various other TeX engines. Older version of ConTeXt could find my project local font map, but newer versions could not. Both used the same version of pdfetex, but newer versions include a Ruby version of texexec instead of the original Perl script. Obviously the two files were very different, so I couldn't just diff them. A few minutes browsing both scripts didn't turn anything up, so I decided to just run them and see what options were being passed to pdfetex. Which brings us to this pretty little command:

watch 'cat -A /proc/$(pgrep pdfetex)/cmdline'

The watch command will periodically re-run a command and display its output. I was first introduced to it as a useful way to monitor a software RAID array as it is rebuilding:

watch cat /prod/mdstat

Since then, I've periodically found myself using it to monitor some other process. For example, monitoring the progress of a large file as it moves from one filesystem to another (note the use of a single ampersand):

mv /media/usbdisk1/foo.dat /media/usbdisk2 & watch df -h
Or monitoring who is currently logged into the system:
watch w

Eagle eyed readers may have noticed the use of single quotes around the cat command in my original example:

watch 'cat -A /proc/$(pgrep pdfetex)/cmdline'
Single quotes were necessary because I wanted watch to run pgrep every time it ran cat.

So what does pgrep do? As you can probably guess from the previous example, it returns the PID of every process with a certain name. In this case, I was taking advantage of the fact there would be only one pdfetex running at a time.

showpath (a rare $IFS sighting)

How do you view the contents of your $PATH? If you're like most people, you just say:

echo $PATH

In one of my previous jobs, our servers had a hideously long and complicated $PATH. Complicated things break, so every once in awhile I'd find myself grovelliing through it trying to figure out what was happening. That didn't happen more than a couple of times before I decided that I needed an easier solution. And thus was showpath born:

function showpath {
   local IFSBAK=$IFS
   local DIRS=""
   IFS=':'
   echo 'PATH:'
   for i in $PATH
   do
       DIRS="$DIRS
       $i"
   done
   echo $DIRS | sort
   IFS=$IFSBAK
}

The most interesting part of this script is probably $IFS. When the shell interprets a command, it actually tokenizes twice. The second tokenization happens after variable expansion. Most of the time, it splits on whitespace*. However, as the example above shows, you can change that by assigning a different value to $IFS. In the case of showpath I wanted to iterate over each directory in $PATH so I set $IFS to split on colons.

*In my experience, the second tokenization is one of the most common causes of shell script bugs. Performing tokenization a second time can be useful, but few people expect it. Hence Stuart's Rule of Shell Variables: Always put variables in double quotes (unless you have a very good reason not to).

August 16, 2006

Knobtweakers

If you're not already familiar with Knobtweakers, you should be. It's a great source for free, high quality electronic music. My recent favorites include Nils Petter Molvaer and The Polish Ambassador. Remember to show your thanks by buying from the artists you love.

Knobtweakers was created by Eric Hamilton (aka, dilvie), a Salt Lake resident. Be sure to check out his music too. There's some great stuff there, including a fun electronic version of Carol of the Bells.

August 15, 2006

Shell Scripting a Side-by-Side Printed PDF Comparison

Today I needed to compare two similar PDFs page by page. Doing so on the screen is possible but awkward. Instead, I wanted to be able to print and write on them. I didn't want to shuffle papers or take alot of desk space. I wanted one half of the first page to have the first page of one PDF, the other half of the page to have the first page of the other PDF, and so forth. Thanks to the wonders of the Linux command line, doing so was not only possible but free and relatively easy.

My script is pretty tailored to my specific situation, so I won't be including it inline. Instead, I've made it an attachment for the curious to download .

Instead, let me describe the most interesting parts:

mktemp
Creating temporary files in a shell script is common. Doing so correctly isn't as common. If you always use the same filename, two copies of the script can't be run at the same time. Even if you use $$ or the current date and time in the filename, there's still a race condition leading to security risks. Happily, using mktemp isn't just one of the most secure options, it's also incredibly easy.
pdftk
As the pdftk Web site explains it, "If PDF is electronic paper, then pdftk is an electronic staple-remover, hole-punch, binder, secret-decoder-ring, and X-Ray-glasses."
mpage
Some print drivers and some programs allow you to print multiple pages on a single piece of paper. With mpage you can do the same with Postscript documents. Since Postscript is the native Linux printing language, it's possible to do it with any document and any printer.
pdftops & pdf2ps
Both convert PDF to Postscript. pdftops is part of poppler-utils, formerly part of Xpdf. pdf2ps is part of Ghostscript. I've generally had better results with pdftops, although not always. (For a couple minutes of fun, try to figure out which version of Ghostscript you're using. There's a handful of forks floating around.)

August 10, 2006

Northern Nevada Road Signs

This is a response to Peter Abilla's post describing a trip though northern Nevada to Tahoe. Peter stayed the night in Battle Mountain but apparently doesn't know an interesting bit of its history. Back in 2001 the Washington Post declared Battle Mountain the armpit of America.

I'm sure they weren't happy about it, but eventually they must have accepted it because for a couple of years driving west to Battle Mountain there was a billboard that said:

Battle Mountain
Voted the armpit of America
by the Washington Post
"We didn't know you were looking!"

Don't believe me? Google has evidence that others have seen it too.

As for Lovelock, my mother has lived there for about six years now. Sadly, I can't provide much insight into the meaning of “Welcome to Lovelock. In Lovelock, we lock your love.”

I can only observe that small town will do strange things in the hopes of attracking tourism. After all, I lived seven years in a North Dakota town that thought a giant cement buffalo was a tourist attraction. (Yes, it is anatomically correct. Yes, brilliant pranksters occasionally think it the height of hilarity to make it look like the bowel system is also functional.)

August 9, 2006

:vimgrep + :copen == :-)

As some people are already aware, for some time now I've been working on a new publishing framework for Guru Labs training materials. One of the major benefits of the change is that we can now use the world's most efficient text editor (Vim) to create our courseware.

While reviewing an import of our Sendmail and Postfix coverage, I noticed that the capitalization of each was inconsistent. Happily, scanning thousands of pages and fixing everything was faster than writing this blog entry.

Taking Postfix as an example, here's the problem: sometimes lower case is correct, sometimes uppercase is correct. When referring to a configuration file, service or RPM, lower case is correct. When referring to the project or collection of tools, upper case is correct. Sadly, no sed magic could get me through this one quickly.

My boss, Bryan Croft, is the local Vim guru. He showed me how to use :vimgrep to get a list of files containing a certain pattern. I wanted to find every instance of lower case "postfix" without a "." before it or a "/" after. In other words:

:vimgrep |[^.]postfix[^/]|g *

The results of a :vimgrep search are stored in the "quickfix" list. This list can be displayed in a second Vim window by

:copen

The nice thing about this list is that it shows each match in context. Instances that are obviously correct can be ignored. Pressing enter on an instance that looks incorrect will open the file in the original Vim window and move the cursor to the exact match. Because this is seperate from the highlighting done by a /sendmail I can concentrate on Postfix but have an active highlight to draw attention to Sendmail references nearby.

People insterested in learning more should also read:

:help quickfix
:help lvimgrep
:help lopen

August 8, 2006

sed -n '/start/,/end/ p'

Yesterday, I needed to search roughly a thousand files to see if a certain string occurred between two other strings on different lines. I could have written a Perl/Python/Ruby script to do this. Heck, I could have done it with a one-liner, but that would have required shifting mental gears. Instead I chose a solution that required little more than a knowledge of vi and some simple command line tools.

Vi and sed are both based on ed. Most of the things you can do at the : prompt in vi you can also do with sed. One cool thing you can do is addressing. For example, the following vi command will search for the word "cat" and replace it with the word "dog", but only if it occurs between a line containing "foo" and a line containing "bar":

:/foo/,/bar/s/cat/dog/g

What would that look like with sed? I'll show you in a moment, but first I want to explain why my next version is more complicated.

I find extended regular expressions easier to work with, so I'm going to use sed -e. It'd probably be best to handle both "Cat" and "cat". Because I'm performing multiple search and replace commands, I also need to use { } for grouping. I only want to replace "[Cc]at" with "[Dd]og" if the word is by itself, not if it's a substring of another word, so I'm going to need to use the word anchor \b. And so, without further ado, here's a sed command that does all that:

sed '/foo/,/bar/ { s/\bcat\b/dog/g ; s/\bCat\b/Dog/g }' file.txt

In the case of my problem yesterday, I didn't need to do any search and replace. I just needed to print every line between the two pattern. Because I didn't care about the name of the file where the pattern occurred. The solution was a simple as:

sed -n '/foo/,/bar/ p' *.dat| grep --color=auto something

If I had cared about which files contained the pattern, I could have done something like this:

for I in *.dat
do
  (sed -n '/foo/,/bar/ p' "$I" | grep something) &> /dev/null &&\
    printf "\n-----\n$I\n-----\n" && \
    sed -n '/foo/,/bar/ p' "$I"
done

I don't feel like explaining that particular command, so deciphering it is left as an excercise for the reader.