Writing shell scripts that will run on any shell (using multiple shebang lines?)

8,018

Solution 1

Which shell executes scripts when there's no shebang line (#!/path/to/shell) at the beginning? I assume /bin/sh but I can't confirm.

The kernel refuses to execute such scripts and returns ENOEXEC, so the exact behavior depends on the program you run such a script from.

  • bash 4.2.39 – uses itself
  • busybox-ash 1.20.2 – uses itself
  • dash 0.5.7 – runs /bin/sh
  • fish 1.23.1 – complains about ENOEXEC, then blames the wrong file
  • AT&T ksh 93u+2012.08.01 – uses itself
  • mksh R40f – runs /bin/sh
  • pdksh 5.2.14 – runs /bin/sh
  • sh-heirloom 050706 – uses itself
  • tcsh 6.18.01 – runs /bin/sh
  • zsh 5.0.0 – runs /bin/sh
  • cmd.exe 5.1.2600 – looks at you funny.

In glibc, functions execv() or execve() just return ENOEXEC. But execvp() hides this error code and automatically invokes /bin/sh. (This is documented in exec(3p).)

What is considered "best practices" in terms of writing shell scripts that will run on any platform? (ok, this is sort of open-ended)

Either stick to sh and only POSIX-defined features, or just go full bash (which is widely available) and mention it in your requirements if distributing it.

(Now that I think of it, Perl – or perhaps Python – would be even more portable, not to mention having a better syntax.)

Always add the shebang line. If using bash or zsh, use #!/usr/bin/env bash instead of hardcoding the shell's path. (However, the POSIX shell is guaranteed to be at /bin/sh, so skip env in that case.)

(Unfortunately, even /bin/sh is not always the same. The GNU autoconf program has to deal with many different quirks.)

Is it possible to write a script that tries to use zsh and falls back to bash if zsh is not available? I've tried putting two shebang lines, like below, but it just errors with bad interpreter: /bin/zsh: no such file or directory out if I try it on a machine without zsh.

There can only be one shebang line; everything after the newline character isn't even read by the kernel, and treated as a comment by shells.

It's possible to write a script that runs as #!/bin/sh, checks which shell is available, and runs exec zsh "$0" "$@" or exec bash "$0" "$@" depending on the result. However, the syntax used by bash and zsh is so different in various places that I would not recommend doing this for your own sanity.

Solution 2

1) The current shell you're running in. (whatever shell that may be)

2) Stick with the same shell type (bash/dash/ash/csh/whatever your flavor) and make sure your "supported platforms" install the shell you wish to use by default. Also, try to use commonly available commands on systems. Avoid machine-specific options.

3) There's not really an "if-then-else" logic to the interpreter directive. You should specify a shell that should exist on all systems you wish to support... i.e. #!/bin/bash or specify a generic #!/bin/sh as long as your script is fairly generic across all shells.

Share:
8,018

Related videos on Youtube

swrobel
Author by

swrobel

Updated on September 18, 2022

Comments

  • swrobel
    swrobel over 1 year

    I've just started getting deeper into shell scripting, and I've always just thrown my script in a file, marked it chmod +x and then done /path/to/script.sh and let whatever interpreter is the default have its way with it, which I assumed was zsh because that's what I used for my shell. Apparently it seems that it's just /bin/sh by default, even if I execute the script from a zsh prompt, because I started putting zsh-specific stuff in my scripts and it's failing unless I run zsh /path/to/script.sh.

    To get to the point, here are my questions:

    1. Which shell executes scripts when there's no shebang line (#!/path/to/shell) at the beginning? I assume /bin/sh but I can't confirm.
    2. What is considered "best practices" in terms of writing shell scripts that will run on any platform? (ok, this is sort of open-ended)
    3. Is it possible to write a script that tries to use zsh and falls back to bash if zsh is not available? I've tried putting two shebang lines, like below, but it just errors with bad interpreter: /bin/zsh: no such file or directory out if I try it on a machine without zsh.

      #!/bin/zsh

      #!/bin/bash

  • user1686
    user1686 over 11 years
    @SWrobel: I just tested these shells while writing the answer. And yes, it's mostly for compatibility: before #! was introduced, almost all scripts were written for the Bourne shell.
  • swrobel
    swrobel over 11 years
    how did you trace what each shell was actually calling when it received ENOEXEC?
  • user1686
    user1686 over 11 years
    @SWrobel: Using strace -f -e fork,clone,execve. Some shells went on to exec /bin/sh after failure; others interpreted the script themselves.
  • user1686
    user1686 over 11 years
    @SWrobel: Another method is to run a script that consists of readlink /proc/$$/exe.
  • user1686
    user1686 over 11 years
    @SWrobel: ...ah, and note that strace displays kernel syscalls, not (similarly-named) libc functions.
  • G-Man Says 'Reinstate Monica'
    G-Man Says 'Reinstate Monica' almost 7 years
    See also Stéphane Chazelas’s answer to Which shell interpreter runs a script with no shebang? (cross-site duplicate of sub-question #1)
  • sch
    sch almost 7 years
    Precision: /bin/sh doesn't have to be POSIX compliant. POSIX does not specify the paths of commands. On Solaris 10 and older, /bin/sh was a Bourne shell, so not a POSIX shell. The POSIX shell was (and still is in Solaris 11 where /bin/sh is ksh93, so a lot more POSIX compliant than the Bourne shell) in /usr/xpg4/bin/sh.
  • sch
    sch almost 7 years
    For csh and tcsh, the behaviour depends on whether the script starts with # (in which case they invoke themselves instead of sh to interpret the script). That goes back to the time when csh supported comments but not the Bourne shell, so a # was a hint that it was a csh script.