That's actually one of the things that I really dislike with bash, that it doesn't read the whole script before executing it. I've been bitten by it before, when I write some long-running script, then e.g. write a comment at the top of it as it's running, and then when bash looks for the next command, it's shifted a bit and I get (at best) a syntax error and have to re-run :-(
There are several ways to get Bash to read the whole thing before executing.
My preferred method is to write a main() function, and call main "$@" at the very end of the script.
Another trick, useful for shorter scripts, is to just wrap the body of the script in {}, which causes the script to be a giant compound command that is parsed before any of it is executed; instead of a list of commands that is executed as read.
Fun fact: That's how goto worked in the earliest versions of Unix, before the Bourne shell was invented: goto was an external command that would seek() in the shell-script until it found the label it was looking for, and when control returned to the shell it would just continue executing from the new location.
To this day, when the shell launches your program, you can find the shell-script it's executing as file-descriptor 255, just in case you want to play any flow-control shenanigans.
Something wonderful I found out the other day: Bash executes scripts as it parses them, so you can do all kinds of awful things. For starters,
will have bash execute an infinite script that looks like without trying to load the whole thing first.After that, you can move onto having a script append to itself and whatever other dreadful things you can think of.