Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash function that breaks loop

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?

like image 723
Ulrik Avatar asked Mar 10 '23 05:03

Ulrik


2 Answers

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
like image 85
pawamoy Avatar answered Mar 16 '23 03:03

pawamoy


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.

like image 31
heemayl Avatar answered Mar 16 '23 03:03

heemayl