Search code examples
shellbuilt-inshunit2

Command `builtin` used before `[` commands in all if statements


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:

  1. Collect each line between pipes (called "pipelines") from standard input, which contain 1 or more commands

  2. Break the pipeline into commands

  3. Set up I/O for the pipeline

  4. 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 (), then

    i. 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).


Solution

  • Because the user could have shadowed it with an alias or function, and using builtin bypasses those, but isn't completely portable to old shells.