6

I made a bash function that looks something like this:

keystroke()
{
    read -s -n1 -t0.1 key     #Read a single keystroke for 0.1 seconds
    [ "$key" = $'\e' ] &&     #If the pressed key is escape
    {
        echo Aborted by user  #Display message
        break                 #Break parent loop
    }
}

And whenever I needed to gracefully end a loop in other bash functions, i just called keystroke. I am no longer able to do this since bash v4.4.0 says:

-bash: break: only meaningful in a `for', `while', or `until' loop

How can I solve this without copying the same code over and over again more than 10x?

2
  • 1
    Return a status code that is checked by the caller of the function? Commented Jan 8, 2017 at 12:16
  • I don't see any loop in your code. Mb that's why? Commented Jan 8, 2017 at 12:25

2 Answers 2

8

Indeed it seems that since Bash 4.4, the break keyword is not allowed anymore outside of a for, while or until loop.

I verified this with shenv and the following snippet. With Bash 4.3.30:

$ shenv shell bash-4.3.30
$ bash -c 'b() { break; }; for i in 1; do echo $i; b; done'
1

And with Bash 4.4:

$ shenv shell bash-4.4
$ bash -c 'b() { break; }; for i in 1; do echo $i; b; done'
1
environment: line 0: break: only meaningful in a `for', `while', or `until' loop

And the line in the changelog: https://github.com/samuelcolvin/bash/blob/a0c0a00fc419b7bc08202a79134fcd5bc0427071/CHANGES#L677.

xx. Fixed a bug that could allow break' orcontinue' executed from shell functions to affect loops running outside of the function.

So now you cannot use the break keyword in a function anymore to break the parent loop. The solution is to return a status code instead, and check that code in the parent loop:

keystroke()
{
    read -s -n1 -t0.1 key
    [ "$key" = $'\e' ] &&
    {
        echo Aborted by user
        return 1
    }
}

while true; do
    ...
    keystroke || break
    ...
done

However, we can see in the changelog another interesting information: https://github.com/samuelcolvin/bash/blob/a0c0a00fc419b7bc08202a79134fcd5bc0427071/CHANGES#L5954.

i. In POSIX mode, break' andcontinue' do not complain and return success if called when the shell is not executing a loop.

So it seems you can retain the old behavior if you enable POSIX-mode.

$ shenv shell bash-4.4
$ bash --posix -c 'b() { break; }; for i in 1; do echo $i; b; done'
1
Sign up to request clarification or add additional context in comments.

Comments

2

For functions you should use return:

keystroke() {
    ...
    return
}

Optionally add an integer (between 0 and 127) as the return value e.g.:

keystroke() {
    ...
    return 1
}

Note that, otherwise the exit status of the last command will be used as the return value.

8 Comments

I guess I could go that way, although in my simplified example I forgot to mention keystroke function forwards the exit status of whatever executes before it.
It does not really answer the question. The question is "How to write a function that will break its upper/parent loop?". Apparently in previous Bash versions, break was working in functions, and in Bash >=4.4 it's not. The first comment was a good answer: "Return a status code that is checked by the caller of the function". I guess there is no other way now: while true; do keystroke && break; ...; done, keystroke returning 0 when escape was hit, 1 (or more) otherwise.
@pawamoy Please read OP's question, example and come again to re-read my answer. When you're inside a function, return is perfectly fine and preferable way to break out early; does not matter whether the function has regular commands or loop within. return also sets the exit status of the function FYI (like I mentioned).
I've done that, and I still don't think you're answering OP's question. Your answer only tells about the return keyword and what it does. OP wants to break the parent loop from within a function, not simply return early from a function.
@pawamoy How do I return a status code from a function that the caller of the function can check?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.