Well the answer is complicated.
TLDR
bash doesn't do any shebang interpretation! That's no its job. It actually simply attempts to execute the program with a normal exec system call. The only exception is when the exec call returns an errno of -ENOEXEC the it attempts to make itself the interpreter.
Longer answer:
- bash doesn't! Actually in most *nix like systems, and even in windows, shells do not tell the difference between "shell scripts" and other types of executables, and treat the shell scripts in any special way. That is left to the underlying exec engine of the OS. There are however caveats to this general statement. I will give the example of Linux, and it pretty much applies (at least in terms of user experience) to POSIX.1 compliant systems.
Treatment of #! in Linux
In this case the shebang is processed by the kernel. All the shell (e.g. bash) does is perform a system call in the exec(2) family of calls to request the running of a program.
The kernel itself is responsible for figuring out what to do with it. It understands a whole host of executable formats like AOUT, ELF, COFF and yes SHEBANG.
The relevant code is found in the linux kernel tree at fs/binfmt_script.c and of course in fs/exec.c
the static int load_script(struct linux_binprm *bprm) actually performs the loading of the script and executing the appropriate binary.
Now if you look at that code you'll notice that around line 58 it returns -ENOEXEC if no interpreter entry is found.
However, if an interpreter entry is found (and all else is okay) the execution continues as expected
So what happens when there is no interpreter entry found?
That means the script has no #! in the first line as expected by the kernel.
In this case bash (and other interactive shells) go ahead and try to either inject themselves in as the interpreters or the system default shell (e.g. /bin/sh) as the interpreter.
For more now what bash is doing I suggest that you create a simple shell script foo with the following content:
# Shebang would go here
# This is foo
echo "Hello World"
chmod a+x foo
Then run foo with strace:
strace ./foo
Followed by running it with strace but invoking bash and getting it to run foo:
strace /bin/bash -c ./foo
Now inject #!/bin/sh in there and repeat the two steps again.