Version 13 of Bourne Shell

Updated 2015-09-02 19:48:07 by pooryorick

the Bourne Shell is one of the two predominant Unix shells.

Reference

Wikipedia
The Traditional Bourne Shell Family , Sven Mascheck, 2001-10-07 - 2014-05-03
Shell Command Language , The Open Group Base Specifications Issue 7 , 2013
sh - shell, the standard command language interpreter , The Open Group Base Specifications Issue 6 , 2004

Implementations

The Heirloom Bourne Shell

Variants

Modern variants of the Bourne Shell include

ash
bash
ksh, by David Korn
a popular Bourne-compatible shell which is also available on Windows.
zsh

Load or Generate a Tcl List

Here are some shell functions to generate a Tcl list from a sequence of values, and to load a Tcl list into an array. They're handy, among other things, for storing and loading structured data.

#! /bin/env bash

: ${TCLSH:=tclsh}

#generate a Tcl list from a sequence of arguments
#example: mylist=$(tcllist one two three '{' )
tcllist () {
    "${TCLSH[@]}" - "$@" <<-'EOF'
        proc main {argv0 argv} {
            puts -nonewline [lrange $argv 0 end]
        }
    if {[catch {
        main $argv0 [lrange $argv[set argv {}] 1 end]
    } cres copts]} {
        puts stderr $copts
        puts stderr $cres
        exit 1
    }
    EOF
}

#load a Tcl list into an array
#example:  tcllist_arr myarray '{one two {three four} five}'
tcllist_arr () {
    eval $1'=()'
    while read -d $'\0'; do
        eval $1'[${#'$1'[*]}]="$REPLY"'
    done < <( "${TCLSH[@]}" - "${@:2:$#}" <<-'EOF'
        proc main {argv0 argv} {
            set list [lindex $argv 0]
            foreach item $list {
                puts -nonewline $item\0
            }
            puts ''
        }
        if {[catch {
            main $argv0 [lrange $argv[set argv {}] 1 end]
        } cres copts]} {
            puts stderr $copts
            puts stderr $cres
            exit 1
        }
    EOF
    )
}

Conversely, here is a Tcl procedure to translate a string containing a sequence of words quoted in Bourne shell syntax into a list It handles arbitrariily-complex shell syntax, including command substitution, so watch out for injection attacks.

proc shtotcl {value {sh bash}} {
    set script "printf 'puts \[lrange \$argv 1 end]' | \$1 - $value"
    return [exec $sh -c $script - [info nameofexecutable]]
}

Example:

shtotcl $env(LDFLAGS)

A more updated version of this code might be found at ycl::format::sh::shtotcl.

Why Bourne Shell Is not Fit for Programming

It is well-known that csh is not fit for programming. Bourne shells also do not rise to the occasion. One big issue is their scoping rules. In the following example, $i in the second function interferes with $i in the first function:

f1 () {
    local i
    for ((i=0 ;i<10 ;i++)); do
        echo $i
        f2
    done
}

f2 () {
    for ((i=0 ;i<5 ;i++)); do
        #do something useful
        :
    done
}

f1

PYK 2015-08-04: In my opinion, one of the biggest reasons not to write programs in the Bourne shell language is that subshells swallow errors. In the following example, hello is not expected to print because the interpreter should exit when the unknown variable, $x is referenced, and indeed it doesn't:

#! /bin/env sh
set -u
echo $x
echo hello

In sh scripting, however, it's extremely common to run commands in a subshell and collect their output. In the following example, hello does print even though the shell is configured as before:

#! /bin/env sh
set -u
echo $(echo $x)
echo hello

Programming in a dynamic language is already adventurous enough. At least choose a sane dynamic language like Tcl. Beware the This is going to be a smallish script so I'll just write it in sh trap.

EMJ 2015-09-02: Well, that's what they do (assuming you meant

#!/bin/env sh

both times and env really is in /bin - not on my system it isn't), but the second one rather misses the point, since it uses command substitution which just runs whatever and substitutes its output so that you actually get a blank line in the above case - after the error message about an undefined variable from the subshell, but the return status of the command (subshell or anything else) is not checked, only the output (nothing in this case) is used. That's how it works, nothing is propagated up from sub-processes. And yes, shell programming (any shell) is way too complicated, but if you're not allowed to use anything that's not already on the system, or that nobody else in the organisation knows...

PYK 2015-09-02: I've fixed the sh-bang line, but my point is exactly that. A Bourne shell may provide a convenient interactive interface, but the behaviour of features such as command substitution make it unfit for writing non-trivial programs. You could even configure the shell to exit with an error status whenever any command invocation fails, and errors will still slide by unnoticed in the case of command substitution:

#! /bin/env sh
set -e
set -u
echo $(echo $x)
echo hello

But switch it up such that command substitution occurs in the context of variable assignment, and suddenly hello isn't printed:

#! /bin/env sh
set -e
set -u
a=$(echo $x)
echo hello

This type of inconsistent behaviour, makes Bourne shells unfit for the purpose of programming. Interactive use, maybe. Programming, no.


dbohdan 2015-09-02: I think there's truth to the following PYK quote from the Tcl chatroom. How Tcl and the POSIX shell respectfully go about performing substitution is what makes one a serious competitor to Lisp and the other difficult merely to loop through a list of file names with.

04:20 < pooryorick> And the secret sauce of Tcl compared to sh is that Tcl doesn't rescan substituted values.
04:20 < pooryorick> In retrospect, I think that's Ousterhout's most remarkable contribution.

See Also

Playing Bourne shell