I see this throughout shunit2
, which aims for maximum portability across old Bourne-style shell scripts (see source here):
# Determine if `builtin` command exists.
__SHUNIT_BUILTIN='builtin'
# shellcheck disable=2039
if ! ("${__SHUNIT_BUILTIN}" echo 123 >/dev/null 2>&1); then
__SHUNIT_BUILTIN=''
fi
# Some more code ...
# ...and now a check
if ${__SHUNIT_BUILTIN} [ ... ]; then
Isn't [
a special builtin in all Bourne-style shells? (i.e. ash /bin/bash /bin/dash /bin/ksh /bin/mksh /bin/pdksh /bin/zsh /usr/xpg4/bin/sh /bin/sh /sbin/sh
)
If builtin
exists in the shell, why would you ever need to write if builtin [ ... ]; then
? It's literally before every single if statement in the script.
UPDATE:
@tripleee gave the correct answer below: [
could be aliased. For posterity, I'm putting the POSIX shell evaluation order here:
Collect each line between pipes (called "pipelines") from standard input, which contain 1 or more commands
Break the pipeline into commands
Set up I/O for the pipeline
For each command
(a) Split the command into tokens
(b) If first token is a keyword (without quotes or backslashes) and is an opening keyword (e.g.
if
,{
, or(
), theni. Set up internally for a compound command
ii. Read the next command and continue until compound command closed
(c) Check the first token of each command against the list of aliases and if found, substitute the alias and go back to step (a)
(d) Perform tilde substitutions (i.e. replace
~
for$HOME
, if~
present)(e) Perform variable substitutions (e.g.
$variable
)(f) Perform command substitutions (i.e.
$()
's)(g) Perform arithmetic substitutions (i.e.
$(())
's)(h) Use
$IFS
to split the line resulting from steps(e)
through(g)
into more tokens(i) Perform file wildcard expansion (i.e. expand
*
,?
, and[...]
pairs).(j) Search tokens that are commands for the path to their binary using the search order
special built-ins --> functions --> regular built-ins --> $PATH
(k) Run command after setting up I/O redirection
I forgot that step (c) happens way before step (j).