18

I have been working on a shell script to automate some tasks. What is the best way to make sure the shell script would run without any issues in most of the platforms. For ex., I have been using echo -n command to print some messages to the screen without a trailing new line and the -n switch doesn't work in some ksh shells. I was told the script must be POSIX compliant. How do I make sure that the script is POSIX compliant. Is there a tool? Or is there a shell that supports only bare minimum POSIX requirements?

2
  • 3
    This UnixSE question has some good feedback. Commented Dec 1, 2016 at 16:55
  • 1
    Remember that one part of the shell script portability (POSIX-compliance) problem is the shell notation used. The other half of the problem is using the portable (POSIX-compliant) options to the POSIX commands and not accidentally using GNU extensions to the POSIX commands. It is quite hard to avoid such problems on occasion. You'll also need to make a decision on whether such non-POSIX tools as Perl or Python can ever be used. Commented Dec 2, 2016 at 3:52

3 Answers 3

15

POSIX

One first step, which gives you indications of what works or not and why, is to set the shebang to /bin/sh and use the ShellCheck site to analyze your script.
For example, paste this script in the ShellCheck editor window:

#!/bin/sh
read -r a b <<<"$1"
echo $((a+b))

to get an indication that: "In POSIX sh, here-strings are undefined".

As a second step, you can use a shell that is as compatible with POSIX as possible.
One shell that is compatible with most other simple shells, is dash, Debian default system shell, which is a derivative of the older BSD ash.

Another shell compatible with POSIX is posh.

However, dash and/or posh may not be available for some systems.

There is lksh (with a ksh flavor), with the goal to be compatible with legacy (old) shell scripts. From its manual:

lksh is a command interpreter intended exclusively for running legacy shell scripts.

But there is the need to use options when calling lksh, like -o posix and -o sh:

Note that it's strongly recommended to invoke lksh with at least the -o posix option, if not both that and -o sh, to fully enjoy better compatibility to the POSIX standard (which is probably why you use lksh over mksh in the first place) or legacy scripts, respectively.

You would call lksh -o posix -o sh instead of the simple lksh.

Using options is a way to make other shells become POSIX compatible. Like lksh, using the option -o posix, like bash -o posix.

In bash, it is even possible to turn the POSIX option on inside a script, with:

shopt -o posix            # also with: set -o posix

It is also possible to make a local link to bash or zsh that makes both act like an old sh shell. Like this:

$ ln -s /bin/bash ./sh
$ ./sh

There are plenty of alternatives (dash, posh, lksh, bash, zsh, etc.) to get a shell that will work as a POSIX shell.

Portable

However, even so, all the above does not ensure "portability".

Unfortunately, making a shell script 'POSIX-compliant' is usually easier than making it run on any real-world shell.

The only real-world sensible recommendation is test your script in several shells.
Like the list above: dash, posh, lksh, and bash --posix.

Solaris is a world on its own, probably you will need to test against /bin/sh and xpg4/sh.

Followup:

How can I test for POSIX compliance for shell scripts?

Sign up to request clarification or add additional context in comments.

1 Comment

"Unfortunately, making a shell script 'POSIX-compliant' is usually easier than making it run on any real-world shell." Shouldn't that be the other way round? Such as: "Unfortunately, making a shell script 'POSIX-compliant' is usually harder than making it run on any real-world shell."
2

Note:

  • This answer complements user8017719's great answer.

  • As requested in the question, a tool is discussed below: while it does not directly check for POSIX compliance, it runs a given script in multiple shells, notably including /bin/sh.

    • /bin/sh, the system default shell, should not be assumed to support any features other than POSIX-prescribed ones, though in practice it does, to varying degrees, depending on the specific implementation. Therefore, successfully running via /bin/sh on one platform does not guarantee that the script will work on another. Among widely used shells, dash comes closest to being a POSIX-features-only shell.

    • Running successfully in multiple shells is important:

      • if you're authoring a script that needs to be sourced in various shells.
      • if you know that your script will encounter only a limited set of known-in-advance shells.

For a proof-of-the-pudding-is-in-the-eating approach, consider using shall (a utility I wrote), which allows you to invoke a given script or command with multiple shells at once, with feedback about which of the targeted shells the script/command executed successfully with.

If you have Node.js installed, you can easily install it with npm install -g shall (if not, follow the above link to the GitHub repo for manual installation instructions) and then use it as follows:

shall scriptFile

or, with an ad-hoc command:

shall -c '<shell-commands>'

By default, it invokes sh, and, if installed, dash, bash, zsh, and ksh, but you can target any set of shells that you have installed by using the SHELLS environment variable.

Using the example of the echo -n command on macOS to only target shells sh and bash:

$ SHELLS=sh,bash shall -c 'echo -n hi'
✓ sh (bash variant)                       [0.00s]
  -n hi

✓ bash                                    [0.00s]
  hi

OK - All 2 shells (sh, bash) report success.

On macOS, bash (effectively) acts as sh, and while echo -n didn't fail when used with sh, you can also see that -n wasn't recognized as an option when bash ran as sh.

Another macOS example that shows that bash permits certain Bash-specific extensions even when running as sh, such as using nonstandard [[ ... ]] conditionals (assumes that dash - which acts as sh on Ubuntu systems - was installed via Homebrew):

$ SHELLS=sh,bash,dash shall -c '[[ -n nonempty ]] && echo nonempty'
✓ sh (bash variant)                       [0.00s]
  nonempty

✓ bash                                    [0.00s]
  nonempty

✗ dash                                    [0.01s]
  dash: 1: [[: not found

FAILED - 1 shell (dash) reports failure, 2 (sh, bash) report success.

As you can see, Bash running as sh still accepted [[ ... ]], whereas dash, which is a (mostly) POSIX-features-only shell, failed, because POSIX only mandates [ ... ] conditionals (as an alias of test ... commands).

Comments

1

Starting Bash with the --posix command-line option or executing ‘set -o posix’ while Bash is running will cause Bash to conform more closely to the POSIX standard by changing the behavior to match that specified by POSIX in areas where the Bash default differs.

Reference

1 Comment

That's helpful, but the key phrase is more closely: many Bashisms are still available even when Bash is running in POSIX compatibility mode (e.g., [[ ... ]] conditionals, <<<… (here-strings), …), so by itself this won't tell you whether a script is POSIX-compliant. To put it differently: While using Bash with --posix, set -o posix or invoking it as sh eliminates some outright incompatibilities with the POSIX spec., it still permits many Bash-specific extensions that will break with mostly-POSIX-features only shells such as dash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.