0

When I try the following expression in bash I get a strange error message:

[: -lt: unary operator expected

First the function definition

some_func () {
  (( 3 + 5 ))
}

And the expression

[ $(some_func) -lt 10 ] && true

I guess the problem is mixing operators like -lt with command substitution and/or arithmetic expansion?

The exit code $? is 2 and the message is an unary op was expected.

ibuprofen
  • 2,890
von spotz
  • 435

3 Answers3

6

It should be

some_func() {
  echo "$(( 3 + 5 ))"
}
[ "$(some_func)" -lt 10 ]

Why it failed

$(some_func) expands to the output of the function* (minus its trailing newline), which however outputs nothing. Therefore, the test becomes

[ -lt 10 ]

In its most basic forms, the [ test accepts 1 to 3 parameters. Since above there are 2 parameters, Bash expects the 1st one to be an unary operator. -lt is binary, hence the error message.

Had you quoted the expansion as appropriate with

[ "$(some_func)" -lt 10 ]

The error would be "integer expression expected" because the test would have the empty string:

[ "" -lt 10 ]

And, unless under non-trivial circumstances, && true is redundant.

*Since the expansion is unquoted, the output also undergoes word splitting and filename expansion. These shouldn't play a part in the present example, though as long as $IFS has not been changed from its default value.

Quasímodo
  • 18,865
  • 4
  • 36
  • 73
4

$(cmd) gets the standard output of cmd¹, so for that to expand to the result, you need cmd to output it:

some_func() {
  echo "$(( 3 + 5 ))"
}
[ "$(some_func)" -lt 10 ]

as others have already said. However, that means some_func is run in a subshell environment, so any modification to variables or anything else will be lost afterwards.

For instance, there's no point doing:

counter=0
incr() { echo "$((++counter))"; }
while [ "$(incr)" -le 10 ]...

as $(incr) will always expand to 1 (as counter will only be incremented in the subshell).

For a function to return a numerical result via an arithmetic evaluation, you'd need a shell with support for math functions like ksh93 or zsh. bash won't do.

In zsh:

counter=0
incr() (( ++counter ))
functions -M incr

while (( incr() <= 10 )); do print $counter done

In ksh93:

function .sh.math.incr i {
  # ksh93 functions must take at least one argument
  (( .sh.value = (counter += i) ))
}
while (( incr(1) <= 10 )); do
  print "$counter"
done

Another alternative in ksh93 or recent versions of mksh is to use forms of command substitution that don't introduce subshells:

counter=0
function incr { print "$(( ++counter ))"; }
while [ "${ incr; }" -le 10 ]; do
  print "$counter"
done

Or in mksh:

counter=0
incr() (( REPLY = ++counter ))
while [ "${| incr; }" -le 10 ]; do
  print "$counter"
done

In any POSIX shell including bash, you can always return the value in a predefined variable ($REPLY is commonly used for that):

counter=0
incr() { REPLY=$(( counter += 1 )); }
while incr; [ "$REPLY" -le 10 ]; do
  echo "$counter"
done

You'll find more details at this answer to another Q&A about mksh's valsub feature


¹ stripped of its trailing newline characters, and in the case of bash of its NUL characters, and here because you forgot the quotes, subject to split+glob

1

Command substitution captures the output of a command or function. That function has no output.

The $? variable holds the return code of the function.

Either do this:

some_func
(( $? < 10 )) && echo yes

or change the function to:

some_func() {
    echo $(( 3 + 5 ))
}

[[ $(some_func) -lt 10 ]] && echo yes


Notice how I'm using [[...]] instead of [...]? The double bracket conditional is more forgiving about empty values.

glenn jackman
  • 85,964
  • Ok, that test was falsey. I tried to test why the interpreter complains about my bash script where the command substituted function actually does echo something and I get the same error message too many arguments. The line is if [ $(lookahead 1) = "-" ]; then... and the function is lookahead () { if [[ $(($current_char_index + $1)) -lt ${#current_char} ]]; then echo "${current_token:$current_char_index:$1}"; else parsing_error! "lookahead goes over end of current token"; fi; } Hope I set the semicolons correct to make it a one liner. – von spotz Jun 02 '21 at 14:01
  • 2
    @vonspotz please be sure to add any relevant information into the original question rather than answering in comments. – AdminBee Jun 02 '21 at 14:02
  • I thought my example would be representative enough to avoid posting the code from the script. Obviously it wasn't. I'm sorry for that. – von spotz Jun 02 '21 at 14:04
  • Too many unknowns in that code: what does parsing_error! do? Assuming it prints a value to stdout, then [ $(lookahead 1) = "-" ] becomes [ lookahead goes over end of current token = "-" ] and that's indeed too many arguments. It's very important to use proper quoting within [...] – glenn jackman Jun 02 '21 at 14:30
  • 1
    Note that [ is a synonym for the test command. At an interactive bash prompt, enter help test and see that "The behavior of test depends on the number of arguments." – glenn jackman Jun 02 '21 at 14:31
  • 1
    I wouldn't advertise [[ $(cmd) -lt 10 ]] here as that's a command injection vulnerability as the output of cmd is interpreted as an arithmetic expression. [ "$(cmd)" -lt 10 ] doesn't have the problem, but of course you mustn't forget the quotes! – Stéphane Chazelas Jun 02 '21 at 15:21