bash scripts should work on any machine that has the
bash shell installed, unless you use
bash features that are too new for the version of the shell on the other system, like for example, trying to use name references (
declare -n), associative arrays (
declare -A), or the
%(...)T format of
printf (or any of a number of other different things) with the default
bash on macOS (which is ancient; just install a newer
bash with Homebrew in that case). Your scripts may also fail if they use external tools not available on some other system, or features of tools that may not be implemented, or that are implemented differently.
The most portable shell language on POSIX systems is the
sh shell. This is described here: https://pubs.opengroup.org/onlinepubs/9699919799/idx/shell.html
bash shell (and others) implement
sh, with extensions. Some shells, like
zsh, are not even trying to be
sh shells at all (but
zsh provides a degree of "
--emulate sh), while shells like
dash does try to be more pure to the POSIX
sh standard (but still with a few extensions).
In general, shell aspects that relate to syntax and built in features should be "portable" as long as the script is always executed by the same shell (and version of the shell, to some degree), even if that shell happens to be the
fish shell. This is the same type of "portability" that you have with Python, Perl and Ruby scripts. There should however always be an
sh shell available (usually installed as
/bin/sh), which is usually
ksh running in some form of "compatibility mode".
The most portable utilities on a POSIX system are the POSIX utilities. These are described here: https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html
GNU utilities, the ones in
coreutils, implement the POSIX utilities (which, as user schily points out in comments, does not mean that they are POSIX-compliant), and then expands upon them with features that are mainly for convenience. This is also true for the corresponding utilities on other Unix systems. Also note that the GNU
coreutils package on Linux does not contain all utilities that POSIX specifies and that utilities such as
awk, and others, which are all POSIX utilities, are packaged separately.
As long as you stay with utilities that are POSIX and use their POSIX behaviour (in terms of options etc.), in scripts that use POSIX
sh syntax, you are likely to be "mostly portable" across most Unix systems. But also note that there are non-POSIX utilities and non-POSIX extension to POSIX utilities that are still fairly portable, because they are commonly implemented. Examples of common non-POSIX utilities are
gzip. Examples of non-POSIX extensions to POSIX utilities that are common to find is the
-iname predicate of
find and the fact that
sed often can be made to understand extended regular expressions using
You'll learn what extensions in implementations of what tools, running on what Unices, behaves in what way, by testing by yourself (in virtual machines for example), and by reading questions and answers on this site. For example, How can I achieve portability with sed -i (in-place editing)?
Also note that there are instances when the POSIX standard leaves a behaviour "unspecified", which mean that the same utility on two different Unix systems, using a POSIX option, may behave differently. An example of this is found here in Why permission denied upon symbolic link update to new target with permissions OK?
The disadvantage of trying to write pure POSIX shell code is that you miss out on some really useful features. I once tried to write the POSIX equivalent tests for
find that GNU's
-readable predicate tests for, which wasn't easy, and trying to signal a process by name using
grep instead of
pkill is a topic of a few too many questions on this site. Likewise, trying to "stay POSIX", you would need to stay away from really useful things like
perl, and you would not be able to do much of any administrative task portably (adding users, manage backups, transfer files between systems, etc.)
In general I prefer a pragmatic approach to getting things done, while understanding that my code may have to be modified to support other systems later (if it's ever used on other systems), rather than a purist approach. This does not stop me from writing scripts that I know will have as few portability issues as possible (I wouldn't use unportable semantics everywhere). But that's my personal opinion.