blogstrapping

Bash Considered Pointless (Or: Pointless Bashing)

The purpose of this essay is essentially to make some key points about interactive command shells and scripting:

  1. Interactive command shells should be optimized for use as interactive command shells.

  2. Programming languages should be optimized for programming.

  3. These design optimization targets often conflict. Pick one when designing a tool: that should be ascendant.

  4. Given that tools should be optimized for their core purposes, use the best tool for the job.

  5. Bash is pretty much never the best tool for the job.

I will address these issues by commenting on particular use cases and criteria used to select the tools one might choose to suit the interactive command shell and scripting use cases.

Interactivity

There are two major types of interface people tend to use in the Unix world, of course: the graphical user interface and the command line interface. One of the many reasons Unix-like operating systems can be great is the fact that both of these interface styles are not just available, but very flexible and powerful (for some definition of "power"), and they can be combined in very beneficial ways. My personal favorite approach to user environment design is to use a tiling and tabbing window manager within which I am probably using a (very) small number of GUI applications and a large number of terminal emulators at any given time. Your mileage may vary, and the customizability of the user environment in Unix-like systems ensures that you can choose a different arrangement of these UI styles to get maximum mileage out of the system.

I hope it is uncontroversial among readers of this essay that UI customizability is a good thing, at least up to the point where your user environment starts making too-significant trade-offs of other benefits for the customization you get. Making a case for that is well outside the range of this piece of writing, but if you feel the need for some convincing perhaps you should check out another essay (that I have not yet finished writing), Customization Is Good.

Given the premise that customizability of user interfaces is a good thing, consider the options for shell customization. The most obvious criteria that come to my mind for how you may use to select a shell look something like this:

The most popularly used shells in the Unix world, as far as I'm aware, are:

In truth, fish is not one of the most popularly used shells in the Unix world (or anywhere), but it is included here for reasons that may become important later on. I am intentionally avoiding shells that are not essentially available for installation "everywhere", too, for some very biased definition of "everywhere".

Note that "sh" in this case refers to any of a number of different shells roughly complying with the Open Group's "Single Unix Standard" specification of a shell that must be available on any OS that can be certified as complying with that standard and, thus, eligible for certification to use the UNIX trademark (note the all-capital letters), where such shells reside at /bin/sh on the system's filesystem. While a given Unix-like operating system's developers and distributors may or may not particularly care about SUS certification, this particular part of the standard is pretty important to ensure portability of common shell scripts (I'll come back to that later), so pretty much every system with any pretensions of being Unix-like comes with an implementation of sh. I'll mostly just refer to that class of shells as though it is a single shell here, for brevity's sake.

Now that we've established some criteria for selecting the shell you wish to use interactively, a lineup of shells from which to choose, and the idea that customization for user interfaces is generally a good thing, we can get down to the nitty gritty details of what shells to use.

Keep in mind that many of the evaluations of different shells below are based on my own anecdotal experience, so take that for what it's worth, try shells out to get your own experience with them as much as you feel the need, and form your own conclusions. If you have some relevant information that can help me refine my understanding of some of these issues, please use the contact page to let me know.

Customizability

There are varying levels of customizability within each shell, generally accomplished by editing a configuration file such as ~/.shrc, ~/.cshrc, or something similar. In fact, depending on the specific configurations you want, you may have two or more different configuration files. The least configurable option is probably sh, shortly followed by csh. Moving from most customizable to least customizable, the overall ranking looks something like this:

  1. fish
  2. Z shell
  3. bash
  4. mksh
  5. pdksh
  6. tcsh
  7. C shell
  8. sh

The customizability of anything other than the C shell and sh is enough to suit most people's needs, as simply choosing one of these shells is itself probably the most significant shell customization choice to make at this point. The first two options in this list, fish and the Z shell, are so much more customizable than any of the alternatives that they stand alone in a class by themselves, so if you want some kind of really amazing levels of customization, those two offer so much customization that people who prefer one of the other six shells (or pretty much any other Unix shell, for that matter) might consider it too much. I, for instance, consider it "too much" only because getting that much customization out of it comes at the cost of making the shell implementation bigger, and thus typically buggier, less stable, and slower.

Dependencies

Having many, or large, dependencies can be problematic for a shell. It tends to result in poor stability, for instance, as changes to any of the dependencies (or breakage of them) can break the shell that depends on them. One of the reasons that some Unix-like systems default to sh as the standard user shell for the root account is the fact that, if some mounted filesystem that contains some dependencies does not mount properly or otherwise fails, sh in /bin/sh is probably still on the root filesystem and working fine.

I am not actually aware of any distinct, necessary dependencies for any shells in that list other than Bash, fish, and the Z shell. All three of these depend on iconv, which is a problem dependency at times. The Z shell nominally depends on ncurses, though I think it can build without it and still be an amazingly useful shell (someone correct me if I'm wrong), but ncurses is not the most horrible dependency in the world anyway and tends to be very stable. Both Bash and fish also depend on gettext, which is bloated, buggy, prone to instability problems on upgrades for large numbers of other programs, and generally just something I would like to avoid. You may not consider these dependencies really problematic for an unprivileged user account, as long as you do not use any of these shells for the root account, though you should know that in many Linux distributions the implementation of sh located in /bin/sh is really just a symlink to the Bash executable file, most likely executed in an sh-compatibility mode.

Features

There are pretty much three big reasons to consider the availability of features in a shell.

One is that you may consider some features non-negotiable for your interactive command shell of choice. The C shell and tcsh, for instance, lack the obvious way to use Unix pipeline redirects separately for the STDERR stream -- namely, treating STDERR as a first-class citizen the same as STDOUT. It can be done, but deepending on how you wish to redirect them, separating STDERR from STDOUT can get pretty ugly. For instance, the usual way to redirect STDERR separately to a file in sh while keeping STDOUT showing on the screen, and the work-around for it in tcsh, are exemplifed as follows:

$ command 2> filename.txt

This example, for sh, executes command (some command that produces both STDOUT and STDERR output) and uses the shell's file descriptor 2, which is by default STDERR, with a redirect to the desired destination of that error output. It leaves the STDOUT stream to do its thing without molestation, presumably to print to the pseudo-terminal in which you entered this command line..

$ (command > /dev/$tty) >& filename.txt

This example, for tcsh, executes command and redirects it to the current pseudo-terminal from within a subshell. This may seem a bit pointless, except that when you redirect that way you are redirecting only STDOUT, not STDERR. This separates the two streams, which means all you have left in the parent shell is STDERR, which you can redirect outside the subshell by using >& to redirect both STDERR and what's left of STDOUT in the parent shell at that point (which is "nothing") to the file whose name you specify, filename.txt in this case.

The point here is not to pick on csh and tcsh, particularly, but simply to show that a lacking feature can have interesting consequences and, depending on your common use patterns, may or may not have a notable effect on the convenience provided by your shell of choice. Balancing this, to some extent, is the fact that tcsh offers functionality that bash still lacks. Various bits of functionality whose absence you may regard as deal-breakers can thus come into play when choosing an interactive shell, and the usual loser here is sh.

Another big reason to consider the availability of features in a shell is that you might really want some set of features that are nice to have, but not necessarily non-negotiable. For these purposes, some shells may have some of the features you want, and others may have different features you want, so you may have to make a sacrifice of some for the sake of others. The most likely shell to give you all the options you could want is probably the Z shell. There are some features that only fish offers, though most people who know enough about shells to care about uncommon features of shells do not care about the features only fish provides (such as a web interface for color configuration -- yes, really).

A third is that the more features get bolted on, the more complex it gets; the less stable it may be; the more buggy it may get; and the less efficiently it may operate. This includes "efficiency" of the user interface, as too many features may make it difficult to navigate documentation quickly, or result in obtuse presentation of features in the manner of some of the more torturously lengthy and memory-confounding command line options names in some GNU utilities.

Licensing

Of the listed shells, there are basically four options for general licensing.

Bash is strongly copyleft-licensed, under the terms of the GPL (version 3, as of this writing); fish is also GPLed, though I am not sure what version of the GPL it uses.

The public domain Korn shell implementation, pdksh, is nominally dedicated to the public domain. Some jurisdictions (France is a favorite example) do not recognize the ability of a copyright holder to dedicate something to the public domain before its normal legal term of copyright expires, and there are some questions about the legal effectiveness of dedicating something to the public domain even in places that are generally regarded as allowing public domain dedication, so there may be some issues with the "public domain" status of pdksh, I suppose. Worse, pdksh reportedly includes some GPLed components, even if the main pdksh project is nominally dedicated to the public domain. Step warily, I suppose.

The standard shell, sh, is actually a bunch of different implementations of roughly SUS-compliant shell, which may vary widely in licensing. Check with your favored operating system's sh implementation for license terms.

Everything else is distributed under some flavor of copyfree license. This may be important to you, depending on any desire to redistribute, your licensing preferences, your use case, and your working conditions. I prefer copyfree licensing, and my favorite shell options at this time fall within the copyfree licensed options, so I'm pretty happy in this regard.

Resources

CPU, RAM, storage, and time efficiency are the four main resource usage concerns. In at least some implementations, sh should be the lightest weight in two or three of those categories (though probably not where sh is just a symlink to Bash, of course). I would guess that fish lies at the opposite end of the spectrum, and is probably a worse efficiency offender than all the rest of the options. I have, unfortunately, not done any extensive benchmarking (or any at all, really), but from what I have heard mksh is (surprisingly) smaller on disk and in RAM than tcsh, which is itself a pretty lightweight shell, I think (while the C shell's executable on FreeBSD is in fact the same file, via hardlink, as tcsh), and the Z shell seems to be a significantly more gluttonous consumer of these resources than mksh and tcsh. This seems to be (mostly) borne out by the simplest of checks on a FreeBSD system:

$ wc `which bash`
    1532   26709  783600 /usr/local/bin/bash
$ time ./test.sh
    0m14.12s real     0m13.55s user     0m0.56s system

$ wc `which mksh`
     608    9355  278648 /usr/local/bin/mksh
$ time ./test.sh
    0m6.04s real     0m6.04s user     0m0.00s system

$ wc `which tcsh`
     650   11753  382248 /bin/tcsh
$ time ./test.csh
    0m25.47s real     0m19.15s user     0m6.24s system

$ wc `which zsh` 
    1382   21097  648120 /usr/local/bin/zsh
$ time ./test.sh
    0m5.30s real     0m4.66s user     0m0.63s system

The files used for this are (with shebang lines for respective shells during tests) . . .

test.csh:

set j = 1
while ( $j < 100000 )
  @ j++
end

test.sh:

j=0
while [ $j -lt 100000 ]; do
  j=$(($j+1))
done

The numbers indicate that for performing the same operations zsh and mksh are easy winners, though of course as benchmarks go these are incredibly simplistic. The tcsh numbers are notably higher than those for bash, but they are closer to each other than either of them is to either mksh or zsh, separating these four shells into two distinct classes of performance. Watching CPU load in top while these ran, it looked like bash and tcsh both reached above 90% by the time they were done, while mksh and zsh both got above 30%, though it's likely the rate of increase in CPU load was similar and the fact mksh and zsh finished much faster accounts for the lower CPU load.

Memory usage looked something like this, with all four shells being pretty comparable except for mksh, the clear winner:

  SIZE    RES COMMAND
17440K  3808K bash
 9912K  1840K mksh
17532K  3688K tcsh
17624K  3952K zsh

If I had to guess, just based on the stuff each shell does, I would say that Z shell configured to do a lot of nifty dynamic stuff, making effective use of its many features and high level of customizability, can probably use more CPU resources than all the rest of them (except perhaps fish, especially when the browser is used for some configuration tasks).

An apparently more idiomatic way to do that while loop in mksh looks like this:

integer j=1
while (( j < 100000 )); do
  let ++j
done

That finishes much more quickly, but consumes a lot more RAM.

Of course, microbenchmarks suck, and mine suck more than most in this case, so take it all with a grain of salt. The upshot is that, for the four shells above, mksh clearly does very well compared to the others, in each benchmark either handily beating all of them or being roughly equivalent to the best of the rest of them; zsh alternates between doing well and poorly; bash and tcsh (and thus csh) do consistently poorly; and bash does especially poorly in terms of size on disk (arguably the least important for common desktop and server use, though possibly of much greater import in embedded systems).

In the end, though, the version of sh on FreeBSD beats them all bloody in every category except for a modest win by mksh in memory usage.

$ wc `which sh`
     235    4717  142952 /bin/sh
$ time ./test.sh
    0m2.50s real     0m2.50s user     0m0.00s system

  SIZE    RES COMMAND
14504K  2408K sh

Responsiveness

How "snappy" an interface feels to the user -- how quickly it seems to react to input, and provide expected output -- is an important criterion in software selection for most users. In the case of shells, any discernible hesitation can be a fatal flaw in some users' estimations. I have not actually had the dubious pleasure of developing any meaningful experience with fish in this regard; it simply does not offer anything of sufficient interest for my preferences to justify giving it any really meaningful evaluation in actual use. There are two other shells in the list examined here that differ enough from the rest of them in this regard to justify special mention, however.

In my rather extensive use of the Z shell over a period of around two years, I found that it hesitated a little bit on a semi-regular basis. I found this somewhat distracting sometimes, and rather annoying. It was not a lot, but it did hesitate often enough for it to be regarded as hesitation "on a regular basis". When I deleted that operating system off my laptop and replaced it with a fresh FreeBSD install, I stopped using the Z shell, in large part because of that one factor (and in some small part because I was comfortable with tcsh for most purposes, having chosen FreeBSD as my preferred OS for several years by that point, and in some slightly less small part because I wanted to give mksh a try, which I had not yet really used with any seriousness at that time).

I also have rather extensive experience with Bash as an interactive command shell, both as my first user shell on Unix-like systems quite a few years back, and as the initial shell I used as my primary user shell on the system where I ended up using the Z shell instead after a little while, among other brushes with it. What my experience of Bash has taught me about responsiveness is that it is dog-slow. Hesitation does not just occur "on a regular basis": it is the norm with Bash. In addition to being very frequent, it is also often pretty significant when it happens, much worse than I have experienced with the Z shell, even on the same laptop with less software installed (and this is not some circa 1999 system, either: it is a Core i5 system with four gigabytes of RAM when I got it, and six now). Apart from GUI applications like web browsers and PDF readers, it is easily the slowest piece of user-facing software I have experienced on this laptop, and I use a lot of stuff that doesn't fit in that "GUI applications like web browsers and PDF readers" category.

All the rest of the shells I have used are much quicker than bash -- even MS Windows shells like cmd and PowerShell, or various programming language REPLs. I, for one, find Bash performance pretty intolerable, though I suppose I may simply have a very low tolerance level. I am sure that many Bash aficionados never even notice the kinds of hesitations that drive me batty; they would, likewise, surely not notice the hesitations I have experienced in Z shell, and would therefore find the "responsiveness" criterion for shell selection irrelevant in all ways.

Technical benchmarks for "user responsiveness" are essentially impossible to collect, as that is a subjective measure, relegating its measurement to the realm of focus groups and user surveys. Even so, some of the results of the benchmarks in the Resources section above might lend some insight into why bash is, in my experience, such a turkey in the realm of responsiveness.

Syntax

There are two major syntactic styles in the shell world. One is the csh style, and the other is the sh style. People can argue endlessly about which is better. I happen to have a (marginal) preference for the csh style (see the test.csh and test.sh while loops above for a direct comparison via tiny code snippet), though that preference is not strong enough to overcome other considerations that prompt me to choose a shell from the sh family for interactive use (though without ksh options, I still might choose tcsh). The options for csh syntax include the C shell, the basic option for that style (and the shell referred to by "csh"), and tcsh, its slightly more advanced and feature-filled descendant. The options for sh syntax include everything else in that list.

I glossed over something a little bit. There is one shell that fits neatly in neither category: fish, also known as the "friendly interactive shell". Its syntax is largely sh-style, but it has some significant, and not-strictly-compatible, differences.

Ubiquity

Some people make an argument to the effect that, if some tool is "everywhere", they should get very familiar with that tool so they will never lack for the ability to make the most of the tool. For instance, many vi users (usually actually Vim users, but close enough for these purposes, mostly) refer to the ubiquity of vi on Unix-like systems as one strong reason to prefer it over something like GNU Emacs. Some people make a similar argument for their favorite shell choices.

The C shell is on every default install of a FreeBSD system. A Korn shell implementation is on every OpenBSD system by default. Bash is on the vast majority of Linux distributions by default, and on Apple MacOS X, though years ago the latter used tcsh; bash even comes with some Unix emulation environments for MS Windows. So far, it sounds like the winner of the ubiquity argument is Bash -- right? Bash users are also the most vocal users of the ubiquity argument that I have observed. "Ubiquity" is not an accurate term, however, as none of these shells is close enough to being "everywhere" to really make that argumet very compelling for those who have to move across different systems on a regular basis.

Of course, the one shell option more ubiquitous than Bash is sh. It's on various BSD Unix systems and every Linux distribution I have encountered (even if only in the form of a slightly behaviorally-modified symlink to Bash). If you have a Unix-like system, the chances against it being absent are overwhelming for any random one of you reading this. For pretty much all intents and purposes, sh is more truly ubiquitous than all the rest of the mentioned shells put together.

Apart from that, the ubiquity argument is pretty much null and void for plain ol' unprivileged user interactive command shell selection. See above, regarding the value of customizability for user interfaces; we should choose our interactive shells to suit our preferences and needs.

Conclusions

If you want something with lots of features and heavy customizability, use the Z shell (or maybe fish).

If you want something that avoids dependency hell, avoid Bash (and maybe fish or the Z shell).

If you want something whose licensing will not impose any restrictions, avoid Bash and fish (and maybe pdksh).

If you want something efficient, lightweight, and snappy, avoid Bash and the Z shell (and fish), and maybe (t)csh.

If you want something with csh syntax, use the C shell or tcsh.

If you want something found everywhere, use sh.

You may notice three things here that are particularly interesting:

  1. You should almost certainly avoid fish, period.

  2. I tend to mention fish in parentheses, as an afterthought. See the note about it below.

  3. About the only reason to use Bash that remains is either ideological devotion to the GNU/GPL/FSF orthodoxy or simply not wanting to make any decisions about what shell to use when using one of the vast majority of Linux distributions. There is one other argument that might be raised: scripting. I will address that below.

In short, if you care at all about shell choice for reasons other than wanting license incompatibility problems and the GNU Project to take over the world, and you are currently using Bash, it is time to learn a different shell for interactive use. While my current favorite is mksh, your preferences and needs may vary substantially, and I understand that and respect well-reasoned choices that differ from my own. You should use what suits you best. I just believe that, for any practical, technical reasons that might apply, Bash is never the right answer.

A Note About The Friendly Interactive Shell

I am still not entirely convinced that fish is not an elaborate prank. I have a very difficult time imagining anyone who knows and cares much about the command line environment choosing fish, and I have a very difficult time imagining anyone who does not care much about the command line environment having any particular motivation to make a conscious shell choice at all. This makes it, at most, seem like a weird trap for people who care a lot about the command line environment, based perhaps on some misguided urge to achieve "leet" status of some kind, but do not actually know much of anything about the command line interface in general and shells in particular.

I suppose fish might be the result of some kind of terribly misguided, amateurish project gone horribly awry and spinning far out of control, perhaps a bit like PHP in that regard, but (as I already said) I'm still not convinced fish is not just a big joke.

A Note About The Public Domain Korn Shell

I am not terribly familiar with the ins and outs of pdksh, and as such I do not really know what technical benefits might prompt one to choose it over mksh. That decision is, at this time, left as an exercise for the reader.

Scripting

There are basically three kinds of shell scripts you might write:

One is the kind that will never be used anywhere else -- probably not even on another computer you own. This will almost certainly be a script that you only need a handful of times, and maybe only once, which is probably a known condition when you first write it.

The second is the kind that will, in some form, end up being used somewhere else, because the problem it solves is general enough that it will be useful in circumstances other than the immediate here-and-now use. In that case, you should to some degree optimize your scripting language choice for portability.

The third is the kind that really should be a program, and not a shell script.

For these purposes, when I say "script" I mean something that just automates the performance of tedious tasks, where a "shell script" is a script that automates tasks performed in the shell; when I say "program" I mean something that performs nontrivial operations involving data, algorithmic logic, and other tasks outside of typical shell usage, assuming a fairly low bar for "nontrivial".

As with interactive command shell use, a choice must be made when choosing what tool to use for shell scripting. A very common way to make this choice is to refuse to make a conscious choice, simply defaulting to using the syntax of the interactive command shell the user most often uses. I believe this is a mistake, a poor exercise of judgment -- or, more accurately, a failure to exercise judgment at all.

A few important criteria spring immediately to mind for what language should be chosen to write scripts:

Immediacy

Much of the attention of this essay has been, and will be, on choosing "the right tool for the job" in a technical sense. There is also something to be said for choosing "the right tool for right now", which often suffers under the weight of nontechnical constraints. These are sometimes of overriding importance, so that you may find yourself struggling with some frustratingly bad mismatch between the capabilities of the tool at your disposal and the task you wish to accomplish. Ideally, this should never happen, but we do not live in an ideal world.

Sometimes, these problems are purely social, as in the case of having a middle-manager breathing down your neck, demanding that you use one of the corporation's bureaucratically specified Approved Programming Languages. Other times, they are more constrained by the laws of physics and the limitations of biological humanity, as in the case that there is some need to accomplish a task today rather than next week and you simply do not have time to learn a different language to use for accomplishing this task. In either of these cases, you should almost certainly just use the language dictated by circumstances rather than technical suitability to the job at hand. In the first case, the reason you should probably do that is to avoid losing your job. In the second case, the reason you should probably do that is so you get the task done while it is still helpful to do so.

I suppose there may be instances where the job or the task at hand is not more important than using "the right tool for the job", but I certainly will not tell you this is a majority of the time, or even a statistically significant percentage of the time. You, personally, are probably in a much better position than me to decide what sacrifices to make in your personal circumstances.

This does not, of course, mean you have carte blanche to write off any alternatives as unimportant forever. What it means is that, if the opportunity ever arises, you should find the time to learn a better tool for that kind of job in the future. This is a matter to handle with rational management of Priorities (that should eventually become a link to an essay I have not yet finished writing), not with blind adherence to entrenched biases.

Portability

Portability is, in a sense, the scripting (and, more broadly, programming) equivalent to what I mean by "ubiquity" for interactive command shells in the Interactivity section above. While I would argue that ubiquity in an interactive command shell is generally not a very important criterion for shell selection, except under very rare conditions, portability is much more important for scripting.

A popular rule of thumb is that if you are performing a tedious and repetitive task more than twice, you should automate it. (D-Tools CEO Adam Stone reportedly phrased it "Anything that you do more than twice has to be automated. I would rather have [my employees] honestly going skiing than do any automated repetitive task.") Of course, that is a rule of thumb, and not an absolute law. Take things on a case-by-case basis but, when in doubt, lean toward automation. Sometimes you should automate it even if you perform the task only once. You might learn a lesson more valuable than the extra time spent writing a script to automate it, and it might even be the case that you will encounter a tedious and repetitive task that takes longer to accomplish the tedious and repetitive way, even just once, than it would take to automate it.

As a corollary to that rule, I would suggest that if you then must use the script at least two times more than the initial planned uses -- that is, if you write something to use four times, but you end up using it at least two more times -- you should make it portable, if portability has any meaning whatsoever for that tool. Obviously, a tool whose only purpose is to interact somehow with a .cshrc file's specific quirks as they differentiate it from other shell configuration files such as your .shrc file probably does not need to be written in a more-portable language than that of the C shell (though there are other reasons to avoid using the C shell language for scripting). The problem with that corollary is that you should have made it portable before you even used it the first time, because otherwise there may be a lot of rewriting involved in making it portable.

As a corollary to the corollary, then, it seems obvious to me that unless there is some overriding reason to do otherwise, you should always write your scripts with some reasonable optimization for portability. This essentially means two things:

  1. If there is some way to do something in a way that is not specific to your particular computing environment and circumstances, so that people using other computing environments and working under other circumstances can use it without modification, and that way of doing things is not notably more troublesome than doing it the nonportable way, you should do it the portable way. It is often the case you should do it the portable way even if it is more notably troublesome to do it that way, in fact.

  2. All else being roughly equivalent, you should use tools optimized for portability. That is, if there is no overriding reason to use a less portable tool, use the more portable tool. Such overriding reasons tend to involve a tool's suitability to the task at hand. If a nail will just pull out of the pieces of wood you wish to fasten together when the finished product is finished, you should probably use a screw instead, even though nails can be driven by hammers, rocks, and even other pieces of wood (conceivably), while screws pretty much require a screwdriver.

I leave satisfying condition one to you. Condition two, however, is quite relevant to this essay.

Given the relative ubiquity of various shells, as described in the Interactivity section above, it should be quite obvious that the most portable shell scripts are those written for sh. Of course, Bash is probably the second most ubiquitous, but that is a much more distant second place than many Linux aficionados imagine. Some GNU Project devotees may feel that Bash should be propagated by any means possible in service to the ideological cause of "Free Software" as defined by Richard Stallman and the Free Software Foundation, but I feel that choosing Bash for one's scripts specifically because it punishes people for using a different open source (or "Free Software") shell as a way to manipulate them into using Bash is kind of hypocritical coming from someone who advocates for software "freedom". This rejection of choosing a tool based on license advocacy desires (or other forms of advocacy) because of how it induces others to use it based on licensing is not universal, but applies well to choice of language for shell scripts because of the fairly universal conditions of Unix shell scripting and the fact that your sh implementation might even be a symlink to Bash.

As a result, there are really only two reasons that sound good to me, given what I have already said, for choosing any other shell language than sh for shell scripts. One is "immediacy", as already covered. The other is suitability. In a moment, we will find that is not quite as strong an argument as you might think in many cases.

Suitability

The suitability of a tool to the task at hand, in most cases, trumps immediacy and portability concerns -- as long as the suitability gap is sufficiently wide. If one tool is orders of magnitude better suited to a task than another, it probably makes sense to use the more-suitable tool than the less-suitable tool, even if the latter is far more portable and you know the tool better. Learn to use a hammer when you want to pound nails, because a screwdriver just isn't going to do the job safely and well (or so I've heard; I have not been so stubbornly screwdriver-oriented as to ignore a hammer when it comes time to drive nails).

I have, many times, encountered a Bash user claiming that the reason he writes his shell scripts for Bash, not caring whether they are sh-compatible, is the fact that Bash offers more (and better) programming features than sh. If we were living in a world of only two choices (as many people seem to think we do), that might be a good argument, but in the real world we have many choices, and assuming Bash is justified because it offers more programming features than sh is assuming a false dilemma. We have other choices and, in cases where programming features above and beyond what sh provides are necessary, many of those other choices are much better. In fact, some of them are not only technically better suited to tasks where something more than sh is needed, but also more portable. Unfortunately, in my experience, portability arguments are entirely lost on people who doggedly insist that they must use Bash for shell scripting, and they sometimes even get angry at the idea that they should ever consider the possibility that someone using a Unix-like system where Bash isn't available (or would have to be installed just for the use of a script the person wrote) should be accommodated in the design of a shell script that might be useful for many people.

My take on the matter is quite simple, actually.

Given that sh offers the basic capabilities Unix users have come to expect from their shells when automating shell tasks (notably including conveniently separate redirection capabilities for STDERR and STDOUT, which csh syntax shells lack), the most portable shell available is also sufficiently well suited to the task of simple shell automation, and as such sh should be the shell scripting language of choice regardless of your preferred interactive command shell. In any cases where more advanced programming capabilities than sh provides are needed, you are now talking about writing programs, and not mere shell scripts. There are even shell scripts you could write in sh that require advanced enough programming language constructs that they should be written in an actual programming language -- that is, a language designed first and formost for programming, rather than as an interactive command shell with programming facilities bolted onto it for the sake of scripting the shell environment -- even though they still may not quite rise to level of being called by a more momentous term than "script". In short, if sh is not a suitable tool for the job at hand, you simply should not be using a shell language to write the script or program you wish to write anyway.

The four most obvious languages that come to mind as better choices, once sh ceases to be suitable to the task at hand, are all more portable than any shell, including sh, in fact (and are listed here in alphabetical order):

Obviously, the choice of which of these languages (or any other non-shell languages you might prefer) to choose is a matter for language warriors and, apart from the narrow realm of shell preference, not really relevant to the purposes of this essay. Another option that might make sense is a much more C-like scripting language, if such a thing were more widespread, but somehow it simply has not arisen in broad distribution. Perl is closer in many ways than the C shell, and has the advantage of being designed as a programming language rather than an interactive command shell with some programming facilities bolted on for scripting purposes (to say nothing of the other flaws of the csh family of shells for scripting purposes), and the various "Interpreted C" implementations are mutually incompatible, not supported by a strong community, and unpopular in the extreme.

Design Optimization

This is a fairly distinct matter, mentioned in the introduction to this essay, that deserves some brief explanation of its own.

The design of an interactive command shell is best accomplished with the purpose of making it a good interactive command shell held in mind as the primary aim of the effort. The design of a programming language should generally be accomplished with the purpose of making it a good programming language held in mind as the primary aim of the effort. Each implies some approaches to doing things that do not apply to the other.

For instance, programming languages tend to benefit from very unified principles of syntactic and semantic philosophy. This also tends to yield a certain amount of simplicity in the basic structure of the language, making it easier to learn quickly and use consistently. More sophisticated functionality might be added by way of libraries, for instance, so that those capabilities exist but do not clutter up the core language. Unfortunately, such concerns tend to make the syntax a bit annoying to use at the command line in an interactive manner, as in the case of trying to use a Scheme REPL as an interactive command shell, because structural cues that are helpful to the writing of software (e.g. Scheme's parentheses) can be very annoying to use after a while.

By contrast, interactive command shells tend to benefit from a much more practical, fast-and-loose approach to design philosophy, with less clear divisions between different types of tokens. An example of this in action is the way that any program in your execution path can be invoked in a manner syntactically identical to the way shell builtin commands are invoked. In essence, the more tools you put in your execution path, the bigger the language.

Such concerns for language design optimization conflict with each other -- these, and many more examples of how one or the other family of languages (programming languages or shell languages). Any attempt to make one of them more like the other will tend to undermine its suitability to the former task. This is, in fact, a trap into which Bash has fallen, with various programming facilities built into the language never quite living up to the promise of how nice they are to have in a programming language, and interfering with the smooth design of the interactive command shell language itself.

Final Thoughts

I have made arguments to the effect that Bash is kind of like the duck-billed platypus of the shell world. It tries to be and do too much, and ultimately fails at all of it, ending up as just another ugly, ungainly weirdo that lumbered out of the GNU Project. The major difference between Bash and the platypus in this regard is that the platypus, despite all appearances to the contrary, actually has some current ecological niche to which it is well-suited. Bash is a dinosaur that should long since have gone extinct, but its popularity is maintained by the propagandizing efforts of the GNU project and the same kind of fear of change that keeps many MS Windows users from trying out open source Unix-like operating systems in the first place.

I know there are many people who will surely object to the lines of reasoning employed within this essay -- people deeply attached to their Bash usage, I expect. Some people may have rational objections to some of my lines of argument as they are currently composed, or suggestions for how to make this better. I encourage such people to write me via the contact page here at blogstrapping. For those who just wish to engage in trollish or shriekingly indignant contradictions rather than helpful discussion, I encourage them to discuss the matter on reddit (where I have a reasonably high chance of seeing it) or Hacker News (where I will probably not see it unless someone directly contacts me to mention it because of the somewhat lacking response notification facilities of the venue).

Acknowledgements

When someone going by the name of OldCoder cut such a discussion short because he had to go to work, he asked me to collect my thoughts on the matter in a form that could be posted online for him to read. While this essay is probably much more of a grand undertaking than he envisioned, it is still ultimately inspired by him, and for that I thank him.

I would also like to thank the missus for reading this thing two or three times at various stages of completeness. Her help in refining what I write is always appreciated, even in cases where (like this) there is simply some weakness in the way something is written and no obvious way to fix it. Any such weaknesses in this essay are completely my fault, and not hers, especially considering she is not much of a shell connoisseur in the first place (though she seems reasonably happy with mksh).

Really Final Thoughts

Ultimately, I hope this helps some people realize what I did when I changed the working title of this essay from Bash Considered Harmful to Bash Considered Pointless: worse than merely causing problems, Bash actually has no continuing excuse to exist apart from inertia. I believe quite strongly that you should wean yourself off Bash, if you know what is good for you, and if you are actually using it regularly in the first place.

If you are not using Bash regularly, perhaps this essay will help you shore up your belief that you have made the right decision.