I've learned that in a scripting language, you can use the shebang in your file to indicate what language and version you're intending to use. Otherwise, the command ./script_name will default to using bash as an interpreter.
Why doesn't bash try to interpret a compiled program using the same style of command, ./executable? In other words, what specifically does bash look at to tell the difference between scripts and executables?
Well the answer is complicated.
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.
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.
Executable files start with a magic number -- a short sequence of bytes that identify the file's format. For example, ELF format executables start with \x7FELF (hex: 7f 45 4c 46), etc. The shebang, #! (hex: 23 21) is actually a magic number itself, that identifies the file as a script. This is why the shebang must be exactly at the beginning of the file, not (for example) indented with a space.
When the system is asked to "execute" a file, it reads the first few bytes and decides what to do based on that. bash running unidentified (no known magic number) files as bash scrips is a special case -- if the system can't figure out how to execute the file, bash tries that instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With