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:
- 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. - What is considered "best practices" in terms of writing shell scripts that will run on any platform? (ok, this is sort of open-ended)
-
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
Best Answer
The kernel refuses to execute such scripts and returns ENOEXEC, so the exact behavior depends on the program you run such a script from.
In glibc, functions
execv()
orexecve()
just return ENOEXEC. Butexecvp()
hides this error code and automatically invokes /bin/sh. (This is documented in exec(3p).)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 skipenv
in that case.)(Unfortunately, even
/bin/sh
is not always the same. The GNU autoconf program has to deal with many different quirks.)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 runsexec zsh "$0" "$@"
orexec 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.