Prajjwal
Prajjwal

Reputation: 1075

ZSH on Linux doesn't recognize valid options in a shell script

zsh doesn't recognize options set with -o, but only when its in a shebang, and on Linux.

The following script fails on zsh 5.0.2, zsh 5.6, and the latest git:

#!/bin/zsh -o pipefail
thiswillfail | echo 'hello, world'
echo $?
exit

Expected Output

hello, world
/home/prajjwal/script:2: command not found: thiswillfail
127

Actual Output

/bin/zsh: no such option:  pipefail

What works

What I've tried

Aside

While I'm only trying to get pipefail to work, this refuses to work with any other options that I try to set, even though all of them are mentioned in zshoptions.

Upvotes: 3

Views: 3582

Answers (1)

melpomene
melpomene

Reputation: 85887

This is a pitfall of trying to set options on the #! line. The kernel only splits the shebang line at the first space.

When you say

#!/bin/zsh -o pipefail

the command that ends up being executed is "/bin/zsh" "-o pipefail" "/home/prajjwal/script".

The error message says that " pipefail" (note the leading space) is not a valid option, which is correct: Only "pipefail" is valid.

In this case a possible workaround is to cram everything into a single command line argument:

#!/bin/zsh -opipefail

But in general that's not possible and the #! interface doesn't let you pass more than one extra argument, so your choices are to find other workarounds (e.g. using set manually) or to write a wrapper script:

#!/bin/zsh
exec /bin/zsh -o pipefail /path/to/the/real/script

On some systems another alternative is available: env -S. This option is not specified by POSIX and is not available on all systems. It works on systems using the GNU tools (including all standard Linux distributions) and FreeBSD, but not OpenBSD or NetBSD. I couldn't find any information about MacOS.

(Note: GNU env also supports --split-string as an alternative (long) option name, but FreeBSD env does not.)

Usage:

#!/usr/bin/env -S /bin/zsh -o pipefail

This invokes env with a single long argument ('-S /bin/zsh -o pipefail'). Standard option processing treats it as the -S option followed by an argument (' /bin/zsh -o pipefail').

In a simple case like this, env -S splits the argument on spaces/tabs and treats the resulting list as if it had been part of the original command line in the first place:

env -S ' /bin/zsh -o pipefail'
# works the same as:
env /bin/zsh -o pipefail

In less simple cases you'll have to quote some characters (env -S treats spaces, ", ', \, $ specially, among others). In particular, it does not work like shell quoting. For details refer to the manual pages linked above.

Upvotes: 2

Related Questions