name: "Builtins: command" cases: - name: "Basic command usage" ignore_stderr: true stdin: | echo "Executing echo built-in" command echo "Hello" echo "Executing ls using name" command ls -d / echo "Executing ls using absolute path" command $(which ls) -d / echo "Executing non-existent command by name" command non-existent echo "Executing non-existent command by path" command /usr/bin/non-existent - name: "Non-executable command" ignore_stderr: true stdin: | mkdir subdir echo "Before executing" ./subdir echo "After executing: result=$?" echo "Before and-or list execution" ./subdir || true echo "After and-or list execution: result=$?" - name: "command -v" stdin: | echo "PATH: $PATH" echo "[echo]" command -v echo echo "[non-existent]" command -v non-existent || echo "1. Not found" echo "[/usr/bin/non-existent]" command -v /usr/bin/non-existent || echo "2. Not found" - name: "command -v -p" # On NixOS, `command -p`'s default "standard utilities" path diverges between # brush and the oracle. brush derives it from glibc's confstr(_CS_PATH), which # nixpkgs patches to include the Nix profile (so `cat` is found there), whereas # nixpkgs' bash resolves a default path that excludes the Nix profile (so it # finds nothing). This reflects where NixOS keeps its standard utilities, not a # brush behavior difference, so it can't be reconciled against the oracle. incompatible_os: ["nixos"] stdin: | unset PATH echo "[no -p]" command -v cat echo "[-p]" command -v -p cat - name: "command -v with full paths" stdin: | echo "[cat]" command -v cat echo "[\$(command -v cat)]" command -v $(command -v cat) - name: "command -v with multiple operands" stdin: | echo "[resolved builtins]" command -v echo true pwd echo "rc=$?" echo "[skipped miss]" command -v echo non-existent pwd echo "rc=$?" echo "[all misses]" command -v non-existent also-non-existent echo "rc=$?" - name: "command -V with multiple operands" ignore_stderr: true # miss wording on stderr differs between brush and bash stdin: | command -V echo true echo "rc=$?" command -V non-existent || echo "not found" - name: "command -V" ignore_stderr: true stdin: | command -V echo command -V ls command -V $(which ls) command -V non-existent || echo "1. Not found" command -V /usr/bin/non-existent || echo "2. Not found" - name: "command with --" stdin: | command -- ls - name: "command with --help" stdin: | command ls --help - name: "command forwards expanded declaration assignments" known_failure: true # TODO: command builtin improvements required stdin: | value=world command declare declared=$value command export exported=$value declare -p declared exported - name: "nested wrappers resolve ordinary and declaration targets" known_failure: true # TODO: command builtin improvements required stdin: | value=world command builtin echo ordinary=$value command builtin declare declared=$value builtin command echo reverse=$value declare -p declared - name: "command expands assignment-looking argument for ordinary builtin" stdin: | value=world command echo argument=$value - name: "command expands assignment-looking argument for external command" stdin: | value=world command sh -c 'printf "<%s>\n" "$1"' sh argument=$value - name: "command expands assignment-looking command name" stdin: | value=world command missing=$value 2>/dev/null printf 'status=%s last=<%s>\n' "$?" "$_" - name: "command with -p" ignore_stderr: true # In case it fails on the oracle as well, ignore the specific error message. # See "command -v -p" above: `command -p`'s default utility path resolves # differently on NixOS for brush (glibc confstr) vs. the oracle (nixpkgs bash). incompatible_os: ["nixos"] stdin: | unset PATH command -p -- ls # # Regression coverage: `command` does not change whether an argument is an assignment word, so # its arguments undergo ordinary word expansion and the nested builtin recognizes any assignment # syntax that survives. # - name: "command does not preserve assignment splitting semantics" known_failure: true # TODO: command builtin improvements required ignore_stderr: true stdin: | v="x y" command declare b=$v declare -p b y - name: "command forwards a value with no field separators intact" known_failure: true # TODO: command builtin improvements required stdin: | value=world command declare declared=$value command readonly fixed=$value declare -p declared fixed - name: "Function shadowing command receives its arguments" stdin: | command() { echo "shadow command: [$*] count=$#"; } command echo hi unset -f command - name: "command -v finds aliases, keywords, and functions" stdin: | shopt -s expand_aliases alias al="echo hi" f() { :; } command -v al; echo "rc=$?" command -v if; echo "rc=$?" command -v f; echo "rc=$?" command -v echo; echo "rc=$?" - name: "command -V describes aliases, keywords, and functions" stdin: | shopt -s expand_aliases alias al="echo hi" f() { :; } command -V al; echo "rc=$?" command -V if; echo "rc=$?" command -V f; echo "rc=$?" command -V echo; echo "rc=$?" - name: "command -v/-V with multiple names" stdin: | # Normalize away the leading "shell: line N:" that the oracle prefixes onto diagnostics. strip() { sed 's/^.*command:/command:/'; } shopt -s expand_aliases alias al="echo hi" f() { :; } echo "[all found]" command -v al f if echo; echo "rc=$?" command -V al f if echo; echo "rc=$?" echo "[some found]" command -v nonexistent f 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" command -v f nonexistent 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" command -V f nonexistent 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" echo "[none found]" command -v nope1 nope2 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" command -V nope1 nope2 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" - name: "command -v/-V output is reusable/quoted for odd alias values" stdin: | shopt -s expand_aliases alias q="echo 'it'\''s" alias q2='a "b" $c' alias q3="'" alias q4="a''b" command -v q command -v q2 command -v q3 command -v q4 command -V q - name: "command -v/-V honors expand_aliases" stdin: | strip() { sed 's/^.*command:/command:/'; } alias za="ls" shopt -u expand_aliases command -v za; echo "rc=$?" command -V za 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" shopt -s expand_aliases command -v za; echo "rc=$?" - name: "command -v/-V: last of -v/-V wins" stdin: | command -v -V echo command -V -v echo command -vV echo command -Vv echo command -V -v nonexistent; echo "rc=$?" - name: "command options given more than once" stdin: | command -vv echo; echo "rc=$?" command -v -v echo; echo "rc=$?" command -pp echo hi; echo "rc=$?" command -vV -v echo; echo "rc=$?" - name: "command with an unknown option fails with status 2" ignore_stderr: true stdin: | command -Z echo hi; echo "rc=$?" command -v -Z echo; echo "rc=$?" command --v echo; echo "rc=$?" - name: "command passes options after the command name on" stdin: | command echo -v x command printf '%s\n' -v command -v echo -v; echo "rc=$?" command -v -- echo; echo "rc=$?" - name: "command -V reports hashed commands" stdin: | # Normalize away the temporary directory the test runs in. scrub() { sed -E "s|$PWD||g"; } mkdir -p bin printf '#!/bin/sh\n:\n' > bin/mytool chmod +x bin/mytool PATH="$PWD/bin:$PATH" hash mytool command -v mytool | scrub command -V mytool | scrub - name: "command -v/-V report path names as given" stdin: | printf '#!/bin/sh\necho hi\n' > prog chmod +x prog command -v ./prog; echo "rc=$?" command -V ./prog; echo "rc=$?" - name: "command -v with non-executable and non-file path names" stdin: | strip() { sed 's/^.*command:/command:/'; } mkdir subdir touch plainfile command -v ./subdir; echo "rc=$?" command -V ./subdir 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" command -v ./plainfile; echo "rc=$?" - name: "command -v with empty name" stdin: | strip() { sed 's/^.*command:/command:/'; } command -v ""; echo "rc=$?" command -V "" 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" - name: "command -p searches the default path" # See "command -v -p" above: on NixOS the oracle's default utility path finds no # standard utilities at all, so the `sh` lookup below can't be reconciled there. incompatible_os: ["nixos"] stdin: | strip() { sed 's/^.*command:/command:/'; } echo "[builtins are still found]" command -p -v echo; echo "rc=$?" command -p -V echo; echo "rc=$?" echo "[externals come from the default path]" # bash's default path is compiled in and brush asks the C library for it, so the two # can name different directories on the same host; only the basename is comparable. dirless() { sed 's#/[^ ]*/#/#'; } command -p -v sh | dirless; echo "rc=${PIPESTATUS[0]}" command -p -V sh | dirless; echo "rc=${PIPESTATUS[0]}" echo "[PATH is ignored]" mkdir -p bin printf '#!/bin/sh\n:\n' > bin/mytool chmod +x bin/mytool PATH="$PWD/bin:$PATH" command -v mytool > /dev/null; echo "rc=$?" command -p -v mytool; echo "rc=$?" echo "[missing name]" command -p -V definitely-not-a-real-command 2>&1 | strip; echo "rc=${PIPESTATUS[0]}" - name: "command -V reports a PATH-found command with an absolute path" stdin: | # Normalize away the temporary directory the test runs in. scrub() { sed -E "s|$PWD||g"; } mkdir -p d1 printf '#!/bin/sh\n:\n' > d1/ex chmod +x d1/ex touch d1/only PATH="d1:$PATH" echo "[a non-executable in PATH is still reported]" command -v only; echo "rc=$?" command -V only | scrub; echo "rc=${PIPESTATUS[0]}" command -v ex; echo "rc=$?" command -V ex | scrub; echo "rc=${PIPESTATUS[0]}" echo "[a \".\" entry contributes no \"./\" to the absolute path]" printf '#!/bin/sh\n:\n' > here; chmod +x here PATH=".:$PATH" command -v here; echo "rc=$?" PATH=".:$PATH" command -V here | scrub; echo "rc=${PIPESTATUS[0]}" echo "[...but explicit and hashed paths are left alone]" printf '#!/bin/sh\n:\n' > prog; chmod +x prog command -V ./prog; echo "rc=$?" hash -p rel/path hp command -V hp; echo "rc=$?" - name: "command -v/-V with no operands" stdin: | command -v; echo "rc=$?" command -V; echo "rc=$?" - name: "command -v/-V skips a disabled builtin" stdin: | # Normalize away the temporary directory the test runs in. scrub() { sed -E "s|$PWD||g"; } mkdir -p bin printf '#!/bin/sh\n:\n' > bin/echo chmod +x bin/echo PATH="$PWD/bin:$PATH" # `echo` is disabled for the rest of this block, so report through printf. enable -n echo command -v echo | scrub; disabled_v=${PIPESTATUS[0]} command -V echo | scrub; disabled_V=${PIPESTATUS[0]} enable echo printf 'rc=%s rc=%s\n' "$disabled_v" "$disabled_V" command -v echo; echo "rc=$?" - name: "command -v/-V reports an alias ahead of a same-named function" stdin: | f() { :; } shopt -s expand_aliases alias f='echo A' command -v f; echo "rc=$?" command -V f; echo "rc=$?" - name: "command -p -v/-V still honors the hash table" stdin: | hash -p /nonexistent/cat cat command -v cat; echo "rc=$?" command -p -v cat; echo "rc=$?" command -p -V cat; echo "rc=$?" - name: "command passes on declaration expansion in POSIX mode" known_failure: true # TODO(command): in POSIX mode, command doesn't expand c=$x as the declaration utility after it would stdin: | set -o posix v="x y" command declare b=$v command command declare c=$v declare -p b c - name: "command passes on declaration expansion only to the next word" known_failure: true # TODO(command): in POSIX mode, command doesn't expand c=$x as the declaration utility after it would stdin: | set -o posix v="x y" command -p declare d=$v command -- declare e=$v declare -p d e - name: "command -v runs nothing, so expands no declarations" ignore_stderr: true stdin: | set -o posix v="x y" command -v printf b=$v; echo "rc=$?" - name: "command drops a special builtin's fatal errors in POSIX mode" ignore_stderr: true stdin: | set -o posix command export --bogus echo "still running: $?" - name: "command reports a forwarded builtin's errors as the builtin's own" known_failure: true # TODO(command): a forwarded builtin's errors are attributed to command too stdin: | strip() { sed -E 's/\x1b\[[0-9;]*m//g; s/^[^ ]*: line [0-9]+: //; s/^error: //'; } { command export -Z; } 2>&1 | head -n 1 | strip - name: "command's program that stops becomes a job" known_failure: true # TODO(signals): a program that stops isn't noticed, nor made a job ignore_stderr: true stdin: | set -m command sh -c 'kill -STOP $$; echo resumed' echo "command: $?" jobs >jobs.txt; echo "named: $(grep -c 'command sh -c' jobs.txt)" fg >/dev/null; echo "fg: $?" rm jobs.txt - name: "command drops a special builtin's special properties for the errors it returns" ignore_stderr: true stdin: | set -o posix command . /nonexistent echo "after .: $?" command source /nonexistent echo "after source: $?" command trap "echo" BOGUS echo "after trap: $?" - name: "command passes --help on to the builtin it runs" stdin: | command cd --help >/dev/null 2>&1; echo "rc=$?" [[ "$(command cd --help; echo .)" == "$(help cd; echo .)" ]] && echo "same as help cd"