Unshelled

I’ve spent the last couple of posts dissecting a shell script, one of my most popular posts is about shell scripting, and yet I really hate shell scripting.

I was thinking about this last night as I added updates to this small rewrite of Marco Arment’s feed subscriber counting script. Then this morning a tweet arrived from John Siracusa:

@drdrang Just say no to shell scripting.
  — John Siracusa (@siracusa) Sat Sep 29 2012 8:18 AM CDT

John is, famously, a Perl programmer, and Perl programmers tend to believe that every shell script could be shorter, clearer, more portable, and written faster if it were a Perl script. For was it not Larry Wall himself who spake:

It’s easier to port a shell than a shell script.

As someone who spent about a decade programming in Perl, I can’t say I disagree with this anti-shell point of view.

The Unix toolbox is so beguiling, so powerful, that it’s easy to start out thinking you’ll just pipe a few commands together and the script will practically write itself. And it does… at first.

But then comes the point—not always, but often enough—where you realize that to make the script truly useful you’ll need to add branching or looping. And if you’re like me, this is the point at which you curse yourself for starting it as a shell script.

Should I be using single square brackets or doubled square brackets? Or is it double parentheses? Is this where you can’t have a space after the opening bracket or where you absolutely must have a space after the opening bracket? Is this the place where you need that stupid semicolon between the if and the then? I know for loops don’t end with rof and do blocks don’t end with od, so why do if blocks end with fi?

Now I’m sure I’d be able to remember these things if I wrote more shell scripts, but that’s the problem: I don’t, and I don’t think I ever will. The shell just doesn’t have the numerical tools or data structures I need in my normal work, so I don’t get the practice in shell scripting that get in Python (and used to get in Perl). But it’s not just the lack of practice. Shell syntax for things other than simple pipelines is just plain weird—not like other languages and not obvious in and of itself.

Perl probably is the best of both worlds when it comes to cobbling together shell-like scripts. Its backticks allow you to commandeer Unix commands and pipelines when that makes sense, but it also has better math, regular expression, data structure, and branching and looping support.

So why didn’t I redo the the feed subscriber counting script in Perl? Well, first, I just wanted to tweak it a little, not rewrite it from scratch. Second, it didn’t have any looping or branching, so I never reached that “oh shit” moment. Finally, it seemed wrong to make such huge changes to someone else’s script.


4 Responses to “Unshelled”

  1. Flavin says:

    But how much faster could this pipe chain have been written if you had started from the outset writing Python?

  2. Jamie says:

    I think I come from a somewhat similar background.

    For me, Perl (I’m slowly moving to Python, too) is where the action happens, if that makes sense. Shell is for initiation, glue that holds other things together for cron, lightweight things that control system boot, etc.

    Python is too strict to replace bash, from a systems perspective. Perl is too heavyweight. If you did something boneheaded with your system late at night, like, say, tried to move /usr to a different disk under Solaris, you’re going to be pretty happy about bash. Not that anything like this ever happened to me, nope. And it was the coffee crash, and anyway the system was up in the morning, and nobody had increased premiums from that aneurism.

  3. Wuffers Lightwolf says:

    I write both shell and Ruby scripts primarily. Here’s how I decide what language to write a script I want in:

    If it deals with files, shell.

    If it deals with strings, Ruby.

    Shell scripts are great for writing to/reading from files. All you need to write to a file is just command > file, and reading from a file: cat file | command (I know about <. I don’t like it. Seems backwards to me.)

    It’s also fairly easy to write DSL-style things in shell scripts since functions don’t use parentheses and everything is a string. For instance, I have a script that creates files and directories based on templates, and the files can have placeholders that look like: __PLACEHOLDER__. These templates can have a setup.sh file, and if it contains something like: add_placeholder date, it will replace every instance of __DATE__ in the files in the template with a date that the user inputs.

    Once you get used to the kinda bad syntax of shell scripts, they can be an awfully powerful tool, and most of the time I will start out writing one instead of a Ruby script if I think Ruby isn’t the right thing for the job.

  4. Aristotle Pagaltzis says:

    If it deals with files, shell.

    If it deals with strings, Ruby.

    Yup. Pretty much. (I’d say Perl instead. Whichever language you prefer in this class will do.) It is not looping or branching that exposes the shell’s weaknesses, it is string operations. If you have to do any but the absolutely most trivial munging of strings, that is when shell falls to pieces.