59

I want to set a return value once so it goes into the while loop:

#!/bin/bash
while [ $? -eq 1 ]
do
#do something until it returns 0    
done

In order to get this working I need to set $? = 1 at the beginning, but that doesn't work.

codeforester
  • 39,467
  • 16
  • 112
  • 140
JohnnyFromBF
  • 9,873
  • 10
  • 45
  • 59

12 Answers12

158

You can set an arbitrary exit code by executing exit with an argument in a subshell.

$ (exit 42); echo "$?"
42

So you could do:

(exit 1)    # or some other value > 0 or use false as others have suggested
while (($?))
do
    # do something until it returns 0    
done

Or you can emulate a do while loop:

while 
    # do some stuff
    # do some more stuff
    # do something until it returns 0    
do
    continue  # just let the body of the while be a no-op
done

Either of those guarantee that the loop is run at least one time which I believe is what your goal is.

For completeness, exit and return each accept an optional argument which is an integer (positive, negative or zero) which sets the return code as the remainder of the integer after division by 256. The current shell (or script or subshell*) is exited using exit and a function is exited using return.

Examples:

$ (exit -2); echo "$?"
254
$ foo () { return 2000; }; foo; echo $?
208

* This is true even for subshells which are created by pipes (except when both job control is disabled and lastpipe is enabled):

$ echo foo | while read -r s; do echo "$s"; exit 333; done; echo "$?"
77

Note that it's better to use break to leave loops, but its argument is for the number of levels of loops to break out of rather than a return code.

Job control is disabled using set +m, set +o monitor or shopt -u -o monitor. To enable lastpipe do shopt -s laspipe. If you do both of those, the exit in the preceding example will cause the while loop and the containing shell to both exit and the final echo there will not be performed.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Should be `$(exit 42); echo "$?"` – Elliot Chance Jul 14 '15 at 04:35
  • 3
    @ElliotChance: The dollar sign in my answer represents the prompt and works as-is. What you propose would also work, but isn't necessary. – Dennis Williamson Jul 14 '15 at 04:40
  • Note that you can use the range 0-255 but what actually happens is a modulo against 256. That means you can `exit 300` and $? will be `44`. Also, `exit -1` would return `255` (a strange habit I've seen a lot). Just something to be aware of. It's best to just return the value you desire so stick to 0-255 so you (or someone else) won't get surprised. – boweeb Mar 21 '18 at 14:00
  • @boweeb `-1 mod X == X-1` is pretty normal, you essentially continue the `0,1,...,X-1` sequence into negatives. See https://en.wikipedia.org/wiki/Modulo_operation for variations and rationale. – toolforger Jul 03 '19 at 05:30
  • @toolforger -- yup... that's what I said above. I'm not confused about the math. I'm confused _why_ a developer would want to use a convoluted way to get to 255. My best guess is the convention implies _the 255 return code isn't meant to be meaningful_ (and writing it as `-1` is marginally easier to write(?)). RC 1 is common for a generic/unspecified error but I've never seen anything actually eval `$?` for 255 (specifically). – boweeb Jan 29 '20 at 13:33
28

false always returns an exit code of 1.

#!/bin/bash
false
while [ $? -eq 1 ]
do
#do something until it returns 0    
done
chepner
  • 497,756
  • 71
  • 530
  • 681
15
#!/bin/bash

RC=1

while [ $RC -eq 1 ]
do

  #do something until it returns 0    

  RC=$?
done
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
6

Some of answers rely on rewriting the code. In some cases it might be a foreign code that you have no control over.

Although for this specific question, it is enough to set $? to 1, but if you need to set $? to any value - the only helpful answer is the one from Dennis Williamson's.

A bit more efficient approach, which does not spawn a new child (but is a also less terse), is:

function false() { echo "$$"; return ${1:-1}; }
false 42

Note: echo part is there just to verify it runs in the current process.

chukko
  • 430
  • 4
  • 10
  • 1
    While I would avoid calling this function `false` (which could be confusing for readers), using a function is *significantly* faster than a subshell. A rough microbenchmark suggests more than 100x overhead with a subshell. – dimo414 Dec 06 '18 at 07:16
  • why confusing? does the same as a traditional false command, just improved as it allows you to set your preferred error value/ – chukko Dec 07 '18 at 08:28
  • Because `false 2` will have different behavior depending on whether or not your function is in the environment. – dimo414 Dec 08 '18 at 01:01
  • all standard callers of false will still work (as false is only expected to return nonzero). the only really confusing part is calling it false 0 (which should maybe throw an error to avoid confusion). so it does not break anything - it will just provide additional value. if anybody is confused, they could easily use their own name (and break their scripts in case the function is not defined). i prefer overloading and backward compatibility to new name and breaking compatibility, but YMMV. – chukko Feb 18 '19 at 18:15
  • But it's not backwards-compatible - the intent is to return a *specific* return code, which is not `false`'s semantics. If you use `false 42` in an environment that doesn't include this function you'll get subtly unexpected results (likely a `1` return code). If you use a different name you'll get a clear error message if your environment is not configured as intended. – dimo414 Feb 18 '19 at 21:52
  • Sure but the point of backwards compatibility is the use case of the original command - i.e. return non-zero error code, which it does. This argument is an extension of that. You cannot expect exactly 1 - some unix flavors dont return 1. So the only unexpected case is false 0 - there i agree it should throw an error. Thats more of a philosophical topic of whether you like overloading or not. YMMV. If you dont, rename, if you do, dont. Pick your own poison. Neither is the golden bullet. We dont need to agree on that. Anybody reading this has enough background info to decide himself. – chukko Feb 20 '19 at 00:09
5

I think you can do this implicitly by running a command that is guaranteed to fail, before entering the while loop.

The canonical such command is, of course, false.

unwind
  • 391,730
  • 64
  • 469
  • 606
5

Didn't find anything lighter than just a simple function:

function set_return() { return ${1:-0}; }

All other solutions like (...) or [...] or false might contain an external process call.

Andry
  • 2,273
  • 29
  • 28
2

Old question, but there's a much better answer:

#!/bin/bash
until
    #do something until it returns success
do
    :;
done

If you're looping until something is successful, then just do that something in the until section. You can put exactly the same code in the until section you were thinking you had to put in the do/done section. You aren't forced to write the code in the do/done section and then transfer its results back to the while or until.

don provan
  • 71
  • 4
2

$? can contain a byte value between 0..255. Return numbers outside this range will be remapped to this range as if a bitwise and 255 was applied.

exit value - can be used to set the value, but is brutal since it will terminate a process/script.

return value - when used in a function is somewhat traditional.

[[ ... ]] - is good for evaluating boolean expressions.

Here is an example of exit:

# Create a subshell, but, exit it with an error code:
$( exit 34 ); echo $? # outputs: 34

Here are examples of return:

# Define a `$?` setter and test it:
set_return() { return $1; }
set_return 0; echo $? # outputs: 0
set_return 123; echo $? # outputs: 123
set_return 1000; echo $? # outputs: 232
set_return -1; echo $? # outputs: 255

Here are are examples of [ ... ]:

# Define and use a boolean test:
lessthan() { [[ $1 < $2 ]]; }
lessthan 3 8 && echo yes # outputs: yes
lessthan 8 3 && echo yes # outputs: nothing

Note, when using $? as a conditional, zero (0) means success, non-zero means failure.

Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
1

Would something like this be what your looking for ?

#!/bin/bash
TEMPVAR=1
while [ $TEMPVAR -eq 1 ]
do
  #do something until it returns 0
  #construct the logic which will cause TEMPVAR to be set 0 then check for it in the 
  #if statement 

  if [ yourcodehere ]; then
     $TEMPVAR=0
  fi
done
David K
  • 692
  • 10
  • 23
0

You can use until to handle cases where #do something until it returns 0 returns something other than 1 or 0:

#!/bin/bash

false
until [ $? -eq 0 ]
do
#do something until it returns 0    
done
0

This is what I'm using

allow_return_code() {
  local LAST_RETURN_CODE=$?
  if [[ $LAST_RETURN_CODE -eq $1 ]]; then
    return 0
  else
    return $LAST_RETURN_CODE
  fi
}

# it converts 2 to 0, 
my-command-returns-2 || allow_return_code 2
echo $?
# 0

# and it preserves the return code other than 2
my-command-returns-8 || allow_return_code 2
echo $?
# 8
ZigZagT
  • 589
  • 4
  • 8
0

Here is an example using both "until" and the ":"

until curl -k "sftp://$Server:$Port/$Folder" --user "$usr:$pwd" -T "$filename";
do :; 

done

James Risner
  • 5,451
  • 11
  • 25
  • 47