Copyright © 2020 by Thomas E. Dickey

SH versus Portability


The Open Group's documentation for the shell utility describes the syntax for Case Conditional Construct like this:

The format for the case construct is as follows:

    case word in
        [(]pattern1) compound-list;;
        [[(]pattern[ | pattern] ... ) compound-list;;] ...
        [[(]pattern[ | pattern] ... ) compound-list]


The ";;" is optional for the last compound-list.

The left parentheses “(” is optional in scripts, but a standard shell utility must accept scripts using either syntax.


While autoconf makes no particular use of the shell's case statement, the shell fragments in the autoconf macros do use the feature. For instance, a quick count gives some numbers:

But grep shows something interesting in the “new” source:

lib/autoconf/general.m4:1852:case $$1_os in *\ *) $1_os=`echo "$$1_os" | sed 's/ /-/g'`;; esac
lib/autoconf/general.m4:1953:  case $CONFIG_SITE in @%:@((
lib/autoconf/general.m4:1986:    case $cache_file in
lib/autoconf/general.m4:2010:    case $ac_val in #(
lib/autoconf/general.m4:2012:      case $ac_var in #(
lib/autoconf/general.m4:2015:      case $ac_var in #(
lib/autoconf/general.m4:2024:    case $as_nl`(ac_space=' '; set) 2>&1` in #(
lib/autoconf/general.m4:2080:dnl at the same time, avoid filename limitation issues in the common case.
lib/autoconf/general.m4:2081:        case $cache_file in #(
lib/autoconf/general.m4:2189:# with an unquoted here-doc, but avoiding a fork in the common case of
lib/autoconf/general.m4:2193:# but quadrigraphs are fine in that case.
lib/autoconf/general.m4:2439:[[case "(($ac_try" in
lib/autoconf/general.m4:2935:case $ac_cv_[]_AC_LANG_ABBREV[]_decl_report in
lib/autoconf/general.m4:3059:[case " $LIB@&t@OBJS " in

Contrast that with older autoconf (2.52):

acgeneral.m4:3685:  case $[1] in
acgeneral.m4:3699:  case $[1] in
acgeneral.m4:3775:  case "$ac_config_target" in
acgeneral.m4:3971:  case $ac_file in
acgeneral.m4:3992:  case $srcdir in
acgeneral.m4:4008:[  case $INSTALL in
acgeneral.m4:4028:      case $f in
acgeneral.m4:4131:  case $ac_file in
acgeneral.m4:4213:  case $ac_file in
acgeneral.m4:4229:      case $f in

and ask, why that #( comment is used. The answer is that the comment helps a text-editor program to show its user where a matching parenthesis is in the file. The case statement uses parentheses. The lines of the shell script between case and esac have patterns followed by “)” (a right-parenthesis). A left parenthesis is optional, and omitted from the autoconf sources.

If you take the time to research the source history, you might notice that the odd comments were added by developers who use vim. However, the feature which is addressed predates vim.

Interestingly enough, vi has had some lisp influence:

This was a documented option of vi, first mentioned in 2BSD, and carried forward into subsequent versions. For example, see The vi User's Handbook (1984), page 43 which says:

If lisp ... makes it easier for you to edit lisp programs. Indents code appropriately for lisp (if you have autoindent set), and modifies the (), {}, [[ and ]] commands to have meaning for lisp. Enables the == (formatted print) operator for S-expressions. Also see showmatch (page 45).

and on page 45, showmatch is documented

If sm ... in Input Mode, whenever you type a ) or }, the cursor will move to the matching ( or { for 1 second if it is on the screen.

As a result, vi users expect to be able to readily find the match for a parenthesis. The fact that the feature was more important for lisp development than, say C, was not really relevant. But generalizing to editing other types of files (such as the mixture of M4 and Bourne shell in autoconf) would need a better tool than vi.

Someone might suppose that Emacs does this. After all, the early autoconf developers used Emacs, and would have relied upon its special features. But bear in mind that very few Emacs users are tool developers. While Emacs has its own equivalent to showmatch (blink-matching-paren), its sh-script.el cannot reliably determine if a parenthesis is in a comment or in a quoted string.

If a better tool is not available, developers use workarounds. I used a special comment in shell scripts to handle this, as early as September 1997 when I began the my-autoconf archive, e.g.,

case $host_os in #(vi
        AC_CHECK_LIB(mytinfo,tgoto,[LIBS="-lmytinfo $LIBS"])

The idea was not original to me (I recall that it was suggested by Paul Fox). For the cases I noticed, the autoconf developers began doing this in April 2006. Others' experience may vary.


Adding a special comment with the left-parenthesis to simplify editing was a nuisance because if an instance was overlooked in a long case-statement, it was not possible to find the matching parentheses. However the workaround was useful because autoconf macros use nested “[” and “]” along with nested parentheses. If one of those is incorrectly nested, the error may not be apparent.

In 2015, another developer pointed out that the shell feature with the left-parenthesis in case patterns was standard. Because it had been a standard feature for a long time (about 20 years), it seemed about time to use it. I made this change to the macro:

2015-04-12  Thomas E. Dickey  <>

        eliminate unnecessary "#(vi" markers in autoconf macros by using "(" to begin
        case-statement cases (suggested by Jens Schweikhardt)

        modified for ncurses --with-extra-suffix option

and used it in xterm patch #318.

A few months later, when doing test-builds before ncurses 6.0, I noticed that this did not work well with Solaris 10 (whose /bin/sh does not conform to the standard). This system uses a (modified) BSD shell utility by default, while providing a set of standard utilities in a separate directory to assert compliance with the XPG4 standard.

Solaris 10 was released in 2005, and was superceded by Solaris 11 in 2010. Solaris 11.1 (October 2012) changed /bin/sh into a symbolic link pointing to ksh93.

Since Solaris 10 provided a workable shell in /usr/xpg4/bin, a short discussion in ncurses 6's release notes explaining how to build on that platform was the appropriate choice.

Just documenting it does not make the problem go away (for some people, at least):