Has AudioScope found a bug in bash 4.4.5?

Using AudioScope.sh on Ubuntu 17.04 from a live DVD disc I came across an error.
Consider the code below it is a MUCH shortened version of the KEYBOARD input in AudioScope.

#!/bin/bash
bash --version
uname -a
status=0
KEYBOARD()
{
	read -r -p "Enter QUIT or EXIT to quit:- " kbinput
	if [ "$kbinput" == "QUIT" ] || [ "$kbinput" == "EXIT" ]
	then
		status=255
		break
	fi
	if [ "$kbinput" == "TEST" ]
	then
		echo "Hello World!"
	fi
}
while true
do
	echo "This will loop and hold until keyboard input is quitted."
	KEYBOARD
done
echo "you are here..."

When run in OSX 10.12.5 this is the expected result:-

Last login: Wed May 24 16:37:36 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./kb_loop.sh
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
Darwin Barrys-MBP 16.6.0 Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64 x86_64
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- TEST
Hello World!
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- QUIT
you are here...
AMIGA:amiga~/Desktop/Code/Shell> _

When run in Linux Mint 18 I get the same expected result from a LIVE DVD:-

mint@mint ~ $ chmod 755 kb_loop.sh
mint@mint ~ $ ./kb_loop.sh
GNU bash, version 4.3.42(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Linux mint 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- TEST
Hello World!
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- QUIT
you are here...
mint@mint ~ $ _

HOWEVER running from a Ubuntu 17.04 current LIVE DVD This is what ensues:-

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ubuntu:~$ chmod 755 kb_loop.sh
ubuntu@ubuntu:~$ ./kb_loop.sh
GNU bash, version 4.4.5(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Linux ubuntu 4.10.0-19-generic #21-Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- TEST
Hello World!
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- QUIT
./kb_loop.sh: line 11: break: only meaningful in a `for', `while', or `until' loop
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- _

NOTE the error:-
./kb_loop.sh: line 11: break: only meaningful in a `for', `while', or `until' loop
And it keeps looping with the same error.

Is this a bug in bash 4.4.5, as bash 4.3.42 and presumably earlier is perfectly fine?!

At what point between 4.3.42 and 4.4.5 would this have broken I wonder?

Can anyone else confirm this error please...

TIA.

Barry.

What it says is perfectly true: 'break' is only meaningful inside a 'for', 'while', or 'until' loop. You can't use it to break a loop outside of your function.

It shouldn't have accepted that before, and probably wasn't doing quite exactly what you planned. You should use return codes instead, to inform whatever's calling keyboard whether it succeeded or failed. That will let you ditch the 'status' variables as well.

Your loop should instead look something like

while KEYBOARD
do
...
done

And the function would be

KEYBOARD()
{
	read -r -p "Enter QUIT or EXIT to quit:- " kbinput || return 1

	if [ "$kbinput" == "QUIT" ] || [ "$kbinput" == "EXIT" ]
	then
                return 1 # Nonzero return will break while loop
	elif [ "$kbinput" == "TEST" ]
	then
		echo "Hello World!"
	fi

        return 0 # Zero return will not break while loop
}
3 Likes

You can simplify that further with a case:

KEYBOARD()
{
	read -r -p "Enter QUIT or EXIT to quit:- " kbinput || return 1

        case "$kbinput" in
        QUIT|EXIT)        return 1 ;;
        TEST)        echo "Hello World" ;;
        esac

        return 0
}

Hi.

$ shellcheck z7

In z7 line 4:
status=0
^-- SC2034: status appears unused. Verify it or export it.


In z7 line 10:
                status=255
                ^-- SC2034: status appears unused. Verify it or export it.


In z7 line 11:
                break
                ^-- SC2104: In functions, use return instead of break.

On a system like:

OS, ker|rel, machine: Linux, 3.16.0-4-amd64, x86_64
Distribution        : Debian 8.7 (jessie) 
bash GNU bash 4.3.30

Some details on shellcheck :

shellcheck      analyse shell scripts (man)
Path    : /usr/bin/shellcheck
Version : ShellCheck - shell script analysis tool
Type    : ELF 64-bit LSB executable, x86-64, version 1 (SYSV ...)
Help    : probably available with -h
Repo    : Debian 8.7 (jessie) 
Home    : http://hackage.haskell.org/package/ShellCheck (pm)

Best wishes ... cheers, drl

(On holiday at the moment so a limited reply.)
Well what should have happened until bash version 4.4.5 and what actully happens is not the same.

#!/bin/bash
bash --version
uname -a
status=0
KEYBOARD()
{
	read -r -p "Enter QUIT or EXIT to quit:- " kbinput
	if [ "$kbinput" == "QUIT" ] || [ "$kbinput" == "EXIT" ]
	then
		status=255
		break
	fi
	if [ "$kbinput" == "TEST" ]
	then
		echo "Hello World!"
	fi
}
while true
do
	echo "This will loop and hold until keyboard input is quitted."
	KEYBOARD
done
echo "You are here..."
echo "Status = $status..."

Resukts:-

Last login: Fri May 26 09:45:32 on ttys000
AMIGA:barrywalker~> chmos 755 test.sh
-bash: chmos: command not found
AMIGA:barrywalker~> chmod 755 test.sh
AMIGA:barrywalker~> ./test.sh
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
Darwin Patricias-iMac.local 16.6.0 Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64 x86_64
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- TEST
Hello World!
This will loop and hold until keyboard input is quitted.
Enter QUIT or EXIT to quit:- EXIT
You are here...
Status = 255...
AMIGA:barrywalker~> _

This is the same on bash version 4.3.42.
The status is/was exactly as I want it except I have changed the code now whilst away on a break!
Will be home again soon and get back to you fully...

That it ever worked is, without exaggeration, the worst BASH bug I have seen to date. That behavior is explicitly barred in every language I know, SH, C, AWK, PERL -- even tinkertoys like Forth where I often wish it wasn't. break should not leap outside its local context into brave new worlds. Control statements control local blocks only.

I'm sorry, but there's nothing for it but to change your ways. You were relying on accidental, broken behavior. Your code will benefit greatly from learning how to use exit codes.

1 Like

Hi Corona688.

Still on a break with limited internet access but my 'ways are now changed'... ;o)
Well using the return method suits me fine.

I had no idea that this bug was near decades old and I was _exploiting_ it.
Thanks for pointing me in the right direction.

Here is a slightly simplified version of yours using dash as the prime mover.
OSX 10.12.5, default bash terminal calling 'dash' from the script...
I still get my variables where I want them so I am a happy bunny...

Last login: Sat May 27 10:20:46 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> cat kb_loop2.sh
#!/usr/local/bin/dash
# This passes the ShellCheck test as /bin/sh.
TEXT="Thank you Corona688."
status=0
KEYBOARD()
{
	printf "Enter QUIT to quit:- "
	read -r kbinput
	if [ "$kbinput" = "QUIT" ]
	then
		TEXT="YOU ARE HERE!"
		status=255
		echo "Exiting the KEYBOARD() function..."
                return 1
	elif [ "$kbinput" = "TEST" ]
	then
		echo "Hello World!"
	fi
        return 0
}
# Main loop...
while true
do
	echo "This will loop and hold until keyboard input is quitted."
	KEYBOARD
	if [ $? -eq 1 ]
	then
		break
	fi
done
echo "Now outside the loop..."
echo "$TEXT"
echo "Status = $status..."
AMIGA:amiga~/Desktop/Code/Shell> 
AMIGA:amiga~/Desktop/Code/Shell> 
AMIGA:amiga~/Desktop/Code/Shell> ./kb_loop2.sh
This will loop and hold until keyboard input is quitted.
Enter QUIT to quit:- NOTHING
This will loop and hold until keyboard input is quitted.
Enter QUIT to quit:- TEST
Hello World!
This will loop and hold until keyboard input is quitted.
Enter QUIT to quit:- QUIT
Exiting the KEYBOARD() function...
Now outside the loop...
YOU ARE HERE!
Status = 255...
AMIGA:amiga~/Desktop/Code/Shell> _

Thank you.

1 Like

If you can limit your status values 0-127, with 0 for success, then you can do return "$status" and remove all your special return cases.

Also, you shouldn't do if [ $? -eq 1 ] since any code besides zero can mean error. You should do if [ $? -ne 0 ] to check if it's not zero, to catch every possible error value.

You could also do the precisely equivalent statement of

if ! KEYBOARD
then
        break
fi

for the exact same effect in something short and straightforward. Or even KEYBOARD || break

1 Like

Hi Corona688...

(Will be back off holidays tomorrow for quicker replies.)

I like your 'return' method and it is now already coded for inside the next upload.
I researched the range of user available RCs and ended up using return 10 .

I used the number '1' in the example as proof of concept, no other reason.

I also checked any other of my break situations and all are valid.

Trust me to use a potential _exploit_ that I thought was valid. I knew Python was very strict, even from Version 1.4.0, (I still currently use this in AMIGA OS), but after using my getout method in bash I thought it was a_feature_ -- my bad. It has taught me to research deeper before commiting...

Thanks for that.

I am surprised however in all the years 'bash' has been around no-one has picked this up until some weeks ago...

'ksh' is fine as is 'dash' so I will test any ideas I get with those first before modifying to bash...

EDIT:
Just noticed your return $status and I like, very much. It will take a while to re-arrange but this will be added as I use statuses from 0 up to 255...

There's only two "predefined", as it were:

  • 0: Success
  • 127: Killed via interrupt. Highest possible return code.

Anything else can mean whatever error you want it to mean. Some specific programs might have a traditional meaning for certain codes, but since audioscope is not any of those specific traditional programs, it doesn't matter.

By convention, there are five classes of exit codes:

0		success
1-125		unspecified failure of some type
126		utility to be invoked found, but is not executable
127		utility to be invoked not found
128+signo	process terminated or stopped by signal number sig

The standards require the 0 exit status to mean successful termination for most standard utilities. And they require 126 and 127 as described above for the command , env , nice , nohup , time , and xargs utilities. A process killed by a signal will exit with the above mentioned exit status, but there is nothing that keeps a process from exiting with an exit code greater than 128 (up to 255) even if it was not terminated by a signal.

On UNIX systems, a process killed by a SIGTERM signal would exit with exit code 143 and a process killed by a SIGKILL signal would exit with exit code 137. On other systems, the standards do not specify the signal numbers assigned to the various signals defined by the standards.

As always, there are exceptions to these conventions. (For example, the false utility's successful exit code is an unspecified non-zero value; not 0.)

Hi Don...

What I found from the mighty WWW was what you put, plus, an exit code of '1' was a general case reserved number too. I only needed one exit code so '10' was easy to remember.

'QUIT' and 'EXIT' are not the same in the AudioScope.sh code anymore.
'QUIT' requires Corona688's return 10 whereas 'EXIT' uses exit 0 .
They both do different things now...
(AudioScope.sh with my major __bash_feature__ error corrected, is now ready for upload.)

Trust me to find a possible exploit that I thought was a feature.
It still surprises me it has taken this long to become apparent however.