Reputation: 203665
Both of these commands having the same result makes sense:
$ if 1; then printf "success\n"; else printf "failure\n"; fi
-bash: 1: command not found
failure
$ if $(printf 1); then printf "success\n"; else printf "failure\n"; fi
-bash: 1: command not found
failure
because $(printf "1")
is printing 1
before 1
is then executed in the if
.
Given that, though, I don't understand why these produce different results:
$ if ""; then printf "success\n"; else printf "failure\n"; fi
-bash: : command not found
failure
$ if ; then printf "success\n"; else printf "failure\n"; fi
-bash: syntax error near unexpected token `;'
$ if $(printf ""); then printf "success\n"; else printf "failure\n"; fi
success
Why would the null string output by $(printf "")
be treated differently than the null string coded explicitly as ""
in the first command or the missing argument from the 2nd command? What is it that's being executed in the final command and found to succeed and why?
Update - making sure I get it!
So applying @chepner's answer to the scripts above I've added explanations (please correct me if I got anything wrong):
$ if 1; then printf "success\n"; else printf "failure\n"; fi
-bash: 1: command not found
failure
The shell parses if 1;
, sees something that it expects to be a command but is actually the number 1
and so fails "command not found"
$ if $(printf 1); then printf "success\n"; else printf "failure\n"; fi
-bash: 1: command not found
failure
The shell parses if $(printf 1);
, sees something that it expects to be a command, $(printf 1)
, executes it successfully which outputs 1. The shell sees that something was output (1
) and given any output in this context the shell expects that "something" to also be a command that it should able to execute but is actually the number 1 and so fails "command not found".
$ if ""; then printf "success\n"; else printf "failure\n"; fi
-bash: : command not found
failure
The shell parses if "";
, sees something that it expects to be a command but is actually the string "" and so fails "command not found"
$ if ; then printf "success\n"; else printf "failure\n"; fi
-bash: syntax error near unexpected token `;'
The shell parses if ;
, does not find the command it expected to find in that context and so fails "syntax error"
$ if $(printf ""); then printf "success\n"; else printf "failure\n"; fi
success
The shell parses if $(printf "");
, sees something that it expects to be a command, $(printf "")
, executes it successfully which outputs nothing which the shell accepts as such and so has no new command to execute and so applies the success exit status of the last command it did run ($(printf "")
) to the condition as a whole and so succeeds.
Upvotes: 2
Views: 75
Reputation: 531275
Short answer: the null string output by printf
disappears during the word-splitting process applied to the command substitution before the shell tries to perform command lookup on it.
When the shell reads its input, it needs to read and parse an entire command before it evaluates the command.
After reading if
, the parser is committed to parsing an entire if
statement before it does any evaluation. The next thing it expects is a compound list to serve as the condition. A compound list cannot begin with a semicolon, though, so the parser immediately signals an error when it sees if ;
.
Skipping a little bit of detail, suffice to say that the string $(print "")
does parse as a compound list. No evaluation is required yet, so if $(print ""); then ...
successfully parses.
After parsing is complete, now the shell actually has to evaluate it. The process of doing so is documented under "SIMPLE COMMAND EXPANSION" in the bash
man page, quoted below in its entirety with relevant passages highlighted:
SIMPLE COMMAND EXPANSION
When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.
The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later process- ing.
The words that are not variable assignments or redirections are expanded. If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.
Redirections are performed as described above under REDIRECTION.
The text after the = in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.
If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment. If any of the assignments attempts to assign a value to a readonly variable, an error occurs, and the command exits with a non-zero status.
If no command name results, redirections are performed, but do not affect the current shell environment. A redirection error causes the command to exit with a non-zero status.
If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits. If one of the expansions con- tained a command substitution, the exit status of the command is the exit status of the last command substitution performed. If there were no com- mand substitutions, the command exits with a status of zero.
So, $(print "")
succeeds but expands to an empty list of words. As a result, the condition for the if
statement "executes" by immediately returning with the exit status of the command substitution, resulting in the true branch being taken.
Upvotes: 2
Reputation: 1891
Look at exit codes:
$ 1; echo $?
1: command not found
127
$ $(printf 1); echo $?
1: command not found
127
$ ""; echo $?
Command '' not found
127
$(printf ""); echo $?
0
You probably wanted:
$ if [ 1 ]; then printf "success\n"; else printf "failure\n"; fi
success
$ if [ $(printf 1) ]; then printf "success\n"; else printf "failure\n"; fi
success
$ if [ "" ]; then printf "success\n"; else printf "failure\n"; fi
failure
$ if [ $(printf "") ]; then printf "success\n"; else printf "failure\n"; fi
failure
Note the space after
[
and before ]
Check man [
Upvotes: 1