2

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
1
  • 1
    One thing that may apply here, from the Conditional Expressions part of the zsh documentation: 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. In other words, [[ $var ]] is the same as [[ -n $var ]]. It is recommended that the second, explicit, form be used where possible.
    – Gairfowl
    Sep 12 at 16:12

2 Answers 2

4

$?

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')

2
  • This explanation is comprehensible. However, regarding the curl example, it does not seem entirely correct. When removing the "-s" flag (silent = no output), curl outputs a progress meter and this does not return 0. Only when both flags, -s and -o, are removed, the condition returns 0, even though its output seemingly is the same.
    – pming
    Sep 13 at 9:54
  • 1
    @pming Notice that "A command enclosed in parentheses preceded by a dollar sign [...] is replaced with its standard output". The progress meter goes to standard error, not to standard output. When you use the -o flag, curl will (as stated in its man page) "Write output to <file> instead of stdout.", so the expansion will be a zero-length string. When you remove the flag, curl will do what it states in the manual: "If not told otherwise, curl writes the received data to stdout.", so than the expression is expanded to the data received by curl.
    – aviro
    Sep 13 at 10:19
0

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
1
  • 1
    If the command's exit status can be relied on, then what you should use is justif command args ; then ... ; fi, e.g., if grep -q something some/file; then some other thing ; fi
    – muru
    Sep 13 at 10:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .