3

Here is my code abstract. I use the result of the comparison between the return_val in func and "true" to decide the while termination condition. But the echoes don't work at all.

#!/bin/bash

func(){
    a=$1
    b=$2
    echo $a
    echo $b
    if ((a > b)) 
    then 
        return_val="true"
    else
        return_val="false"
    fi  
}

c=1
d=4
while [[ $(func $c $d) && ("$return_val"!="true") ]]
do
    ((c++))
done
Yulong Ao
  • 1,199
  • 1
  • 14
  • 22

3 Answers3

3

Since you want to see the output from your echo command, you need to separate the stdout output from the return (exit) code:

#!/bin/bash

func(){
    local a=$1 b=$2
    echo "$a"  # stdout output
    echo "$b"
    if (( a > b ))  # see below for how this could be simplified.
    then 
        return 0 # set exit code
    else
        return 1
    fi  
}

c=1 d=4
while ! func $c $d
do
    ((c++))
done

That way, you're free to use the function directly (prefixed with ! to negate the test's outcome), without a command substitution, and let its return (exit) code drive the outcome of the while loop condition (an exit code of 0 meaning success, any other meaning failure).

By using just ! func $c $d as the condition, func's stdout output is simply printed as is.


Addendum: Billy Wayne McCann's answer points out that there's no strict need to use explicit return statements to set the exit code (but there's no need to use test[1]).

(( a > b )) can be used by itself as the last statement in the function, to set the exit code implicitly:

If there's no explicit return statement, it is a function's last command that sets its exit code (the same applies analogously to scripts in the absence of an exit statement).

(( ... )) sets the exit code to 1, if ... evaluates to 0, and 1 otherwise (any non-negative result). A Boolean expression such as a > b results in 1, if the expression is true, and 0 otherwise. Therefore, (( a > b )) by itself does the same thing implicitly that the above if statement does explicitly:

func(){
    local a=$1 b=$2
    echo "$a"  # stdout output
    echo "$b"
    (( a > b ))  # exit code implicitly set to 0, if test *succeeds*, 1 otherwise
}

[1] Using test or [ ... ] is the POSIX-compliant way to evaluate a condition. Unless your code must be POSIX-compliant, Bash's [[ ... ]] and (( ... )) (for arithmetic Boolean tests) are the better choice - see https://stackoverflow.com/a/29320710/45375

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Sorry, echoes are used to debug. I have learnt that we should return the exit status instead of value. – Yulong Ao Apr 03 '15 at 03:43
  • @CodingTheLife: That's what exactly what this solution does: the function's _exit code_ determines when to exit the loop. _Independently_ of that, whatever output the command that forms the condition produces is passed through. If you do _not_ want that output, use `>/dev/null` to suppress stdout, `2>/dev/null` to suppress stderr, and `&>/dev/null` to suppress both. Since your question states that "echoes don't work" - what did you mean by that, given that this solution does "make them work"? – mklement0 Apr 03 '15 at 03:49
1

I would rather have function return a value.

#!/bin/bash

func(){
    a=$1
    b=$2
    echo $a
    echo $b
    if ((a > b)) 
    then 
        return 0 # 0 means success
    else
        return 1 # non-zer0 means failure
    fi  
}

c=1
d=4
until (func $c $d) #While ! success, do something..
do
    ((c++))
done

Note: The parenthesis around func $c $d are only for readability. Not required really.

anishsane
  • 20,270
  • 5
  • 40
  • 73
  • 2
    @anishane: Using redundant parentheses there has the effect of creating a redundant subshell, because that's what parentheses do in bash. Anyway, IMHO, `while func $c $d; do` is perfectly readable (at least, once you get used to bash). – rici Apr 03 '15 at 03:42
1

Forcing a return of 0 or 1 isn't the proper way to do this. You want to return the true exit status of the test, not a forced return status. There's really no need for the if ... then block.

Functions automatically return their exit status. This exit status of the function is the status of the final statement. Hence, one can place test as the final statement in func.

#!/usr/bin/env bash

func(){
    local a=$1 
    local b=$2
    test $a -lt $b  #use exit status directly
}

c=1 
d=4

while func $c $d  #while c is less than d
do
   ((c++)) 
done
  • 1
    "isn't the proper way to do this." is hyperbole - the accepted answer is _proper_, but _needlessly verbose_. `((a > b)) ` by itself would do as the last statement - no need to use the less flexible and less powerful `test` builtin instead. – mklement0 Apr 03 '15 at 21:59
  • To clarify my previous comment: your suggestion to _simplify_ the code by simply letting the outcome of a test determine a function's exit code _implicitly_ is a good one. While the `if` statement in the accepted answer is effectively _redundant_, it doesn't, however, _mask_ the `(( … ))` expression's exit code, because `(( … ))` itself only ever sets exit code 0 or 1. (Even if there _were_ masking, it would only matter if you subsequently tested for a _specific_ exit code set, which rarely happens; in the case at hand, `while` only cares about 0 vs. non-zero.) – mklement0 Apr 04 '15 at 14:01
  • `a` and `b` are also needlessly verbose; `func () { test "$1" -lt "$2"; }` would work identically since you aren't modifying the arguments. – chepner Apr 04 '15 at 14:06