mirror of
https://github.com/flokoe/bash-hackers-wiki.git
synced 2024-11-25 15:53:41 +01:00
203 lines
8.5 KiB
Markdown
203 lines
8.5 KiB
Markdown
---
|
|
tags:
|
|
- bash
|
|
- shell
|
|
- scripting
|
|
- portability
|
|
- POSIX
|
|
- portable
|
|
---
|
|
|
|
# Portability talk
|
|
|
|
The script programming language of BASH is based on the Bourne Shell
|
|
syntax, with some extensions and derivations.
|
|
|
|
If scripts need to be portable, some of the BASH-specific syntax
|
|
elements should be avoided. Others should be avoided for all scripts,
|
|
e.g. if there is a corresponding POSIX(r)-compatible syntax (see
|
|
[obsolete](../scripting/obsolete.md)).
|
|
|
|
Some syntax elements have a BASH-specific, and a portable[^1]) pendant.
|
|
In these cases the portable syntax should be preferred.
|
|
|
|
|construct|portable equivalent|Description|Portability|
|
|
|---------|-------------------|-----------|-----------|
|
|
|`source FILE`|`. FILE`|include a script file|Bourne shell (bash, ksh, POSIX(r), zsh, …)|
|
|
|`declare` keyword|`typeset` keyword|define local variables (or variables with special attributes)|ksh, zsh, …, **not POSIX!**|
|
|
|`command <<< WORD`|`command <<MARKER`\n`WORD`\n`MARKER`|a here-string, a special form of the here-document, avoid it in portable scripts!|POSIX(r)|
|
|
|`export VAR=VALUE`|`VAR=VALUE`\n`export VAR`|Though POSIX(r) allows it, some shells don't want the assignment and the exporting in one command|POSIX(r), zsh, ksh, …|
|
|
|`(( MATH ))`|`: $(( MATH ))`|POSIX(r) does't define an arithmetic compund command, many shells don't know it. Using the pseudo-command `:` and the arithmetic expansion `$(( ))` is a kind of workaround here. **Attention:** Not all shell support assignment like `$(( a = 1 + 1 ))`! Also see below for a probably more portable solution.|all POSIX(r) compatible shells|
|
|
|`[[ EXPRESSION ]]`|`[ EXPRESSION ]`\nor\n`test EXPRESSION`|The Bashish test keyword is reserved by POSIX(r), but not defined. Use the old fashioned way with the `test` command. See [[web/20230315170826/https://wiki.bash-hackers.org/commands/classictest]]|POSIX(r) and others|
|
|
|`COMMAND < <( …INPUTCOMMANDS… )`|`INPUTCOMMANDS > TEMPFILE`\n`COMMAND < TEMPFILE`|Process substitution (here used with redirection); use the old fashioned way (tempfiles)|POSIX(r) and others|
|
|
|`((echo X);(echo Y))`|`( (echo X); (echo Y) )`|Nested subshells (separate the inner `()` from the outer `()` by spaces, to not confuse the shell regarding arithmetic control operators)|POSIX(r) and others|
|
|
|
|
## Portability rationale
|
|
|
|
Here is some assorted portability information. Take it as a small guide
|
|
to make your scripts a bit more portable. It's not complete (it never
|
|
will be!) and it's not very detailed (e.g. you won't find information
|
|
about how which shell technically forks off which subshell). It's just
|
|
an assorted small set of portability guidelines. *-Thebonsai*
|
|
|
|
!!! warning "FIXME"
|
|
UNIX shell gurus out there, please be patient with a newbie like me
|
|
and give comments and hints instead of flames.
|
|
|
|
### Environment (exported) variables
|
|
|
|
When a new value is assigned to an **existing environment variable**,
|
|
there are two possibilities:
|
|
|
|
The *new value* is seen by subsequent programs
|
|
|
|
- without any special action (e.g. Bash)
|
|
- only after an explicit export with `export VARIABLE` (e.g. Sun's
|
|
`/bin/sh`)
|
|
|
|
Since an extra `export` doesn't hurt, the safest and most portable way
|
|
is to always (re-)export a changed variable if you want it to be seen by
|
|
subsequent processes.
|
|
|
|
### Arithmetics
|
|
|
|
Bash has a special compound command to do arithmetic without expansion.
|
|
However, POSIX has no such command. In the table at the top, there's
|
|
the `: $((MATH))` construct mentioned as possible alternative. Regarding
|
|
the exit code, a 100% equivalent construct would be:
|
|
|
|
# Bash (or others) compound command
|
|
if ((MATH)); then
|
|
...
|
|
|
|
# portable equivalent command
|
|
if [ "$((MATH))" -ne 0 ]; then
|
|
...
|
|
|
|
Quotes around the arithmetic expansion `$((MATH))` should not be
|
|
necessary as per POSIX, but Bash and AT&T-KSH perform word-splitting on
|
|
aritrhmetic expansions, so the most portable is *with quotes*.
|
|
|
|
### echo command
|
|
|
|
The overall problem with `echo` is, that there are 2 (maybe more)
|
|
mainstream flavours around. The only case where you may safely use an
|
|
`echo` on all systems is: Echoing non-variable arguments that don't
|
|
start with a `-` (dash) and don't contain a `\` (backslash).
|
|
|
|
Why? (list of known behaviours)
|
|
|
|
- may or may not automatically interpret backslash escpape codes in
|
|
the strings
|
|
- may or may not automatically interpret switches (like `-n`)
|
|
- may or may not ignore "end of options" tag (`--`)
|
|
- `echo -n` and `echo -e` are neither portable nor standard (**even
|
|
within the same shell**, depending on the version or environment
|
|
variables or the build options, especially KSH93 and Bash)
|
|
|
|
For these, and possibly other, reasons, POSIX (SUS) standardized the
|
|
existance of [the `printf` command](../commands/builtin/printf.md).
|
|
|
|
### Parameter expansions
|
|
|
|
- `${var:x:x}` is KSH93/Bash specific
|
|
- `${var/../..}` and `${var//../..}` are KSH93/Bash specific
|
|
- `var=$*` and `var=$@` are not handled the same in all shells if the
|
|
first char of IFS is not " " (space). `var="$*"` should work
|
|
(except the Bourne shell always joins the expansions with space)
|
|
|
|
### Special variables
|
|
|
|
#### PWD
|
|
|
|
[PWD](../syntax/shellvars.md#PWD) is POSIX but not Bourne. Most shells are
|
|
*not POSIX* in that they don't ignore the value of the `PWD`
|
|
environment variable. Workaround to fix the value of `PWD` at the start
|
|
of your script:
|
|
|
|
pwd -P > dev/null
|
|
|
|
#### RANDOM
|
|
|
|
[RANDOM](../syntax/shellvars.md#RANDOM) is Bash/KSH/ZSH specific variable
|
|
that will give you a random number up to 32767 (2^15-1). Among many
|
|
other available external options, you can use awk to generate a random
|
|
number. There are multiple implementations of awk and which version your
|
|
system uses will depend. Most modern systems will call 'gawk' (i.e.
|
|
GNU awk) or 'nawk'. 'oawk' (i.e. Original/Old awk) does not have the
|
|
rand() or srand() functions, so is best avoided.
|
|
|
|
# 'gawk' can produce random numbers using srand(). In this example, 10 integers between 1 and 500:
|
|
randpm=$(gawk -v min=1 -v max=500 -v nNum=10 'BEGIN { srand(systime() + PROCINFO["pid"]); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
|
|
|
# 'nawk' and 'mawk' does the same, but needs a seed to be provided for its rand() function. In this example we use $(date)
|
|
randpm=$(mawk -v min=1 -v max=500 -v nNum=10 -v seed="$(date +%Y%M%d%H%M%S)" 'BEGIN { srand(seed); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
|
|
|
*Yes, I'm not an `awk` expert, so please correct it, rather than
|
|
complaining about possible stupid code!*
|
|
|
|
# Well, seeing how this //is// BASH-hackers.org I kinda missed the bash way of doing the above ;-)
|
|
# print a number between 0 and 500 :-)
|
|
printf $(( 500 * RANDOM / 32767 ))
|
|
|
|
# Or print 30 random numbers between 0 and 10 ;)
|
|
X=0; while (( X++ < 30 )); do echo $(( 10 * RANDOM / 32767 )); done
|
|
|
|
#### SECONDS
|
|
|
|
[SECONDS](../syntax/shellvars.md#SECONDS) is KSH/ZSH/Bash specific. Avoid it.
|
|
Find another method.
|
|
|
|
### Check for a command in PATH
|
|
|
|
The [PATH](../syntax/shellvars.md#PATH) variable is a colon-delimited list of
|
|
directory names, so it's basically possible to run a loop and check
|
|
every `PATH` component for the command you're looking for and for
|
|
executability.
|
|
|
|
However, this method doesn't look nice. There are other ways of doing
|
|
this, using commands that are *not directly* related to this task.
|
|
|
|
#### hash
|
|
|
|
The `hash` command is used to make the shell store the full pathname of
|
|
a command in a lookup-table (to avoid re-scanning the `PATH` on every
|
|
command execution attempt). Since it has to do a `PATH` search, it can
|
|
be used for this check.
|
|
|
|
For example, to check if the command `ls` is available in a location
|
|
accessible by `PATH`:
|
|
|
|
if hash ls >/dev/null 2>&1; then
|
|
echo "ls is available"
|
|
fi
|
|
|
|
Somewhat of a mass-check:
|
|
|
|
for name in ls grep sed awk; do
|
|
if ! hash "$name" >/dev/null 2>&1; then
|
|
echo "FAIL: Missing command '$name'"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
Here (bash 3), `hash` also respects builtin commands. I don't know if
|
|
this works everywhere, but it seems logical.
|
|
|
|
#### command
|
|
|
|
The `command` command is used to explicitly call an external command,
|
|
rather than a builtin with the same name. For exactly this reason, it
|
|
has to do a `PATH` search, and can be used for this check.
|
|
|
|
For example, to check if the command `sed` is available in a location
|
|
accessible by `PATH`:
|
|
|
|
if command -v sed >/dev/null 2>&1; then
|
|
echo "sed is available"
|
|
fi
|
|
|
|
[^1]: "portable" doesn't necessarily mean it's POSIX, it can also
|
|
mean it's "widely used and accepted", and thus maybe more
|
|
portable than POSIX(r
|