Version 21 of exec magic

Updated 2004-02-29 16:31:33

Purpose: explain how best to start an executable script under Unix. From news:comp.lang.tcl See also: DOS BAT magic

But before you invest some of your precious time checking out what's in store at DOS BAT magic, be informed that you can use Cygwin's port of sh instead, and avoid some DOS Hell.


 #!/bin/sh
 # This line continues for Tcl, but is a single line for 'sh' \
 exec wish "$0" ${1+"$@"}

(beginning of original message)

 Subject: Re: Documentation error (was: Why "$@" and not $@ ?)
 From: [email protected]
 Date: 1998/07/07
 Newsgroups: comp.lang.tcl
 John> But back to the topic ... Maybe it would help if we could get a
 John> good writeup of just how and why it works, and twist arms to
 John> install the description in various highly-visible places.  I'd
 John> do it myself, but I'm not at all confident that I could write a
 John> correct description of it all. Maybe I should give it a try, and
 John> post it for criticism ...

The incantation looks like this:

        exec wish "$0" ${1+"$@"}

Here's how it works:

Each argument to sh is numbered $1, $2, $3, ... $9.

$0 is the name of the script. We put it in quotes to preserve whitespace and other weird characters.

The special variable $* expands to all the arguments. However, it expands *unquoted*, meaning that spaces will be lost in the expansion.

This means that the naive:

        exec wish "$0" $*

will not work properly if any argument has a space in it. Likewise, using "$*" will fail because that will expand to a single argument.

The special form "$@" was introduced (the quotes are part of it) to work around this problem. This form expands to all the arguments, but properly (and individually) quoted. So why doesn't the following attempt work?

        exec wish "$0" "$@"

The reason is that some versions of sh have a bug. If there are no arguments, these broken sh's will still expand "$@" into a single empty argument. Oops.

The sh syntax ${VAR+VALUE} expands to VALUE if $VAR is set, and nothing otherwise. We abuse this syntax to work around the buggy sh implementations. Recall that the first argument is named $1. If it exists, then we use "$@"; otherwise we do nothing:

        exec wish "$0" ${1+"$@"}

The above is my interpretation. No doubt further refinements and clarifications are in order.

Tom


DKF - On some platforms, it is important to put a space between the #! and the /bin/sh (or whatever) so that magic file numbering works. More sophisticated versions of this trick are needed when you've got a plethora of Tcl versions installed and your script cares which one it gets. These extensions are pretty much obvious to a shell programmer though...

LV - note that the above is shell specific ; if you use something other than Bourne/korn shell, or older versions of these two, your experiences may differ in what works. Certainly if you move off into more futuristic shells, you may find that none of these features work.

RS - But I think there is a habitual agreement that /bin/sh can always be expected, so it's safest to base portable scripts on that.

LV - But at least on Linux these days, it is my understanding that /bin/sh is really either ksh or bash... if one finds the above magic doesn't work, just drop by here and let us know what is going on and perhaps someone can help find new magic for that platform.

DKF - All unix shells come from two lineages: Bourne Shell and C-Shell. The two are almost completely incompatible with each other at a syntactic level (though most modern shells derive from both, at least feature-wise) so when you ask for /bin/sh, you will always get something that knows how to handle Bourne syntax (which is all the above trick depends on). Correspondingly, when you ask for /bin/csh, you always get something that knows C-Shell syntax. The put-a-space-after-the-#! trick is a deeply obscure feature of Unix kernel magic processing; you only get to find these things out by reading background material for autoconf...


More commentary appears in [L1 ].


Donal Fellows contributed in c.l.t. an extended version of the magic code, that can call either tclsh or wish:

 #! /bin/sh
 # If the script's name starts with tk, use wish... \
 case "`basename $0`" in *tk*) exec wish "$0" ${1+"$@"}
 # ... and otherwise use tclsh. \
 ;; *) exec tclsh "$0" ${1+"$@"};;
 # That is all. \
 esac

DKF notes in passing: Did I? I don't remember that; it's really quite clever. I'm impressed with myself...


Wish has a few options like -s that it snatches from the command line before setting up argv. If you want those to be visible for your script (and not for wish itself), insert doubledash (which ends wish's argument processing):

        exec wish "$0" -- ${1+"$@"}

Note that if you do that, on Unix systems your script will no longer handle X windows options (like -geometry and -display) automatically.


RS found that the first line

 #!/usr/bin/env tclsh

also works (starts the first tclsh in PATH) quite nicely on his Linux, Solaris, and Cygwin installations - so he will use this instead of exec magic in the future.

Note that this actually does the same as using /bin/sh. That trick (abusing the different comment handling of sh and tclsh is a trick) with /bin/sh does nothing else than setting up the environment (including PATH) so that tclsh is a valid command that actually runs something. If you look at man env you will see that env does nothing else than setting up the environment and execute the command given to it, along with remaining arguments. Using #!/usr/bin/env tclsh is just the correct way to achieve this (simple) goal. It is meant for exactly the task in question. - joh


Some people run into problems if the file was created under DOS first and then used on Linux. The problem is this. Say you have a file that looks like this:

 #! /bin/sh
 # This line continues for Tcl, but is a single line for 'sh' \
 exec wish "$0" ${1+"$@"}

or some variation. However, originating in DOS/Windows, the lines are all terminated by carriage return (^M character). When the user executes the file, s/he might get the error: ksh: /tmp/err.tcl: not found

The trailing carriage return causes the command shell problems before it ever reaches tcl. The user will however think there's a tcl problem - so this is one of the things to check.


I hope it is ok to edit this. I am a beginner at tcl and programming (half an hour's playing with it). However I found that I could get a gui tcl script (the example that was given for buttons) to run from a double click in konqueror on Mandrake 9.2 by right clicking on the file and chosing "open with" and then chosing "wish". I wondered if this would be useful information for other newbies?

FW: Good to know, I suppose. Yes, you can edit anything you want - that's the point :)


FW: So let's be clear - does the #!/usr/bin/env tclsh method have any disadvantage whatever over exec magic?


D. McC: Almost as soon as I read this three weeks ago, I scrapped the "exec magic" at the beginning of the Tcl/Tk programs I've written and inserted #/usr/bin/env wish at the beginning of my working versions instead. They work fine. SnackAmp, WaveSurfer, and FileRunner also have no problem with this line substituted for "exec magic" on my Mandrake Linux 9.2 system. The only disadvantage I see is that Konqueror no longer thinks these programs are shell scripts, so I can't run them with a double-click from Konqueror any more. I can easily live without that. So I say, good-bye forever to evil-looking exec magic!


Arts and crafts of Tcl-Tk programming