$#
, which reports the count of arguments, is NEVER an empty string - if you don't specify arguments, $#
evaluates to 0
, which is still a nonempty string (-z
tests for empty strings).
Therefore, [ -z "$#" ]
is always (logically) false.
What you're looking for - using idiomatic Bash - is:
if [[ $# -eq 0 ]]; then ... # -eq compares *numerically*
As anishsane points out in a comment, the POSIX-compliant [ $# -eq 0 ]
would work here as well; generally, though - unless your express intent is to write POSIX-compliant shell code - you're better off sticking with the more predictable, more feature-rich (and marginally faster) Bash-specific constructs.
or, using arithmetic evaluation:
if (( $# == 0 )); then ...
As for why your 2nd snippet caused the if
branch to be entered:
Your misplaced echo "Test"
- due to being placed before the then
keyword, caused the echo
command to be interpreted as part of the conditional.
In other words: the conditional that was evaluated was effectively
[ -z "$#" ]; echo "Test"
, a list of (two) commands only whose last command's exit code determined the outcome of the conditional.
Since echo
always succeeds (exit code 0
)[1]
, the conditional as a whole evaluated to (logical) true, and the if
branch was entered.
[1] gniourf_gniourf points out in a comment that you can make a simple echo
command fail (with exit code 1
), if you use input/output redirection with an invalid source/target; e.g., echo 'fail' > /dev/full
.
(Note that if the redirection source/target is fundamentally invalid - an nonexistent input file or an output file that can't be created / opened (as opposed to, say, an output target that can be opened with write permission but ultimately can't be written to, such as /dev/full
on Linux) - Bash never even invokes the command at hand, as it "gives up" when it encounters the invalid redirection:
{ echo here >&2; echo hi; } >/dev/full # Linux: 'here' still prints (to stderr)
{ echo here >&2; echo hi; } >'' # invalid target: commands are never invoked
)