How and why does using redirection or writing files within an if statement affect exit code?

In zsh

echo 'a string' > test.txt
echo $?
0

and

[[ $(echo 'a string') ]]
echo $?
0

whereas

[[ $(echo 'a string' > test.txt) ]]
echo $?
1

another example

curl -so 'curl-8.2.1.tar.gz' https://curl.se/download/curl-8.2.1.tar.gz
echo $?
0

or

[[ $(curl -so 'curl-8.2.1.tar.gz' https://curl.se/download/curl-8.2.1.tar.gz) ]]
echo $?
1

My questions:

  1. Is this because of output redirection? If no, what is causing this?
  2. The commands get executed successfully: a string appears in test.txt and curl downloads the file to the output file I specified, why does the evaluation result in false?
  3. Is there a sane way to handle this in scripting? Let’s say I want to execute some command if the previous one executed successfully (but returned false still), how should one go about it? I could add a second check to see if the line appeared or the file was downloaded, but then the evualation of successful command execution wouldn’t be necessary in the first place.

Some more examples for completeness (readability vs. "correctness"?):

if ( $(echo 'a string' > text.txt) ); then echo yes; else echo no;fi
yes
if (( $(echo 'a string' > text.txt) )); then echo yes; else echo no;fi
no
if $(echo 'a string' > text.txt); then echo yes; else echo no;fi
yes
if echo 'a string' > text.txt; then echo yes; else echo no;fi
yes
Asked By: pming

||

$?

In the PARAMETERS SET BY THE SHELL section of man zshparam, or info zsh 'Parameters Set By The Shell' you’ll see that, $? is the exit status returned by the last command.

$(...)

In man zshexpn or info zsh 'Command Substitution' you’ll see:

COMMAND SUBSTITUTION

A command enclosed in parentheses preceded by a dollar sign, like
'$(...)', or quoted with grave accents, like ..., is
replaced with its standard output, with any trailing newlines deleted.

So for instance, the expression $(echo 'a string') would be replaced with the output of this command (a string<newline>) stripped of the trailing newline characters, which gives a string. But the command inside $(echo 'a string' > test.txt) would produce no output to the standard output, so the result of that expression is an empty string.

[[ ... ]]

In the man page of zshmisc or info zsh 'Conditional Expressions', you’ll see:

Conditional Expressions

A conditional expression is used with the [[ compound command to
test attributes of files and to compare strings. Each expression can
be constructed from one or more of the following unary or binary
expressions:

[…]

For compatibility, if there is a single argument that is not
syntactically significant, typically a variable, the condition is
treated as a test for whether the expression expands as a string of
non-zero length.

And now for your examples

So in the following line, the output of the command inside the $( ... ) is the string a string<newline> and the expansion of $(...) itself will therefore be a string, which is a non-zero length string, thus the [[ ... ]] evaluation will return true (0 exit status).

[[ $(echo 'a string') ]]

But in the following command, since you have a redirection, the expansion of $( ... ) will be a zero-length string, and the [[ ... ]] evaluation will return false (exit status 1, being a number other than 0).

[[ $(echo 'a string' > test.txt) ]]

Similarly, the exit status of the following command is the exit status of the curl command.

curl -so 'curl-8.2.1.tar.gz' https://curl.se/download/curl-8.2.1.tar.gz

And the manpage of curl will state under what condition it returns a non-zero (failure) exit status.

But in the next example, the exit status would depend on whether the curl command had any output (success) or didn’t have any output (failure).

[[ $(curl -so 'curl-8.2.1.tar.gz' https://curl.se/download/curl-8.2.1.tar.gz) ]]

I’ll stop here, but you can continue reading the manual, for example the meaning of (( ... )) in the ARITHMETIC EVALUATION section of man zshmisc (or info zsh 'Arithmetic Evaluation')

Answered By: aviro

I conclude from this that using [[ ... ]] is not suitable for evaluating successful command execution, unless successful command execution is understood in a way such that the command’s output results in a non-zero string. For the purpose of verification in zsh scripting, the right way would be to

  1. (use if command; then ...; [else ...;] fi as stated by muru’s comment)
  2. run the command on its own and check its exit code with $? afterwards, and / or
  3. verify if the actual output of the command, for example by examining the contents of the files that were generated by it
Answered By: pming
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.