Saturday, December 12, 2009

Bash Commandline editing

Back when I was at Lone Star Ruby Conference, Adam Stacoviak, a friend of mine that I met there, told me that I needed to do a screencast about bash. I didn't act on it until recently, at church, we had a lesson about how important it is to share with others what we know. That was enough to get me off my rear end and put something together.

One of my goals was to make it concise, trying to avoid wasting a lot of time saying things that didn't really contribute to the concepts conveyed. I've got to tell you, trying to produce a high-quality screencast is a lot of hard work! I managed to produce one that is only 13 minutes long, so I can't imagine the amount of effort that goes into producing a one-hour long screencast -- writing the script, recording this material, editing, polishing, and ensuring that the information is clear. If you have ever pirated a screencast, you are a really, REALLY big jerk :-) they deserve to get paid every penny they earn. (I'm going to be giving mine away for free, so you don't have to worry about that here. If it benefits you, pay it forward and do share something you know with the world)

I had the honor of publishing it through a site that my friend Eric Berry operates, teachmetocode.com, joining the ranks of a few other fine gentleman as publishers there. I recorded it with Camstasia studio for the Mac, having received a sponsorship license through teachmetocode.com. Overall, it did the job, but I think it lacked a lot of key features that would have made editing less tedious. (Like being able to group clips together, for example)

Click here to view the screencast: Bash Command Line Editing

Friday, February 20, 2009

How to escape arguments in bash

Today I was trying to write a convenience wrapper script that ran commands remotely on a server (for one of the people we work with). But, for some reason, ssh handles arguments in a completely suprising (and annoyingly inconvenient) manner that completely ignores quotes.

For example:

timcharper@timcharper:~ $ ssh my_server grep "4 5 6" ./
grep: 5: No such file or directory
grep: 6: No such file or directory

Grrr…. this made me feel ANGRY. Because of this, I couldn’t use the “$@” trick to splat all the arguments on the end and just move on with life. But it’s cool, because I’ve got a thick table to bang my head against, combined with an overly-aggressive problem-solving drive that won’t accept no for an answer.

So, I came across an awk trick to escape every character in a string, and another trick to iterate over arguments. Combining the two techniques did the trick, and I can now properly pass command line arguments through an ssh wrapper script. Since it depends on bash and awk, and bash and awk are on EVERY POSIX system out there, it’s a winner.

The working script:

#!/bin/sh
CMD=""

for (( i = 1; i <= $# ; i++ )); do
  eval ARG=\$$i
  CMD="$CMD $(echo "$ARG" | awk '{gsub(".", "\\\\&");print}')"
done

ssh my_server cd /path/to/app \&\& RAILS_ENV=production $CMD

Surprisingly, this is quite bullet broof, and properly escapes strings and preserves arguments like:

./remote.sh grep "hello there" . -R
./remote.sh grep "So I says to the typewriter, \"Hey, I'm in quotes\"" . -R
./remote.sh grep "\"" . -R

I wish somebody had posted a solution like this for me to find, so, here you go. If you came here looking for this solution, I probably saved you an hour of your life, and a lot of stress to boot. You’re welcome.

Have you ever wasted more than an hour on a trivial problem like this? Is there an easier way to do this?