Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does bash tell scripts and executables apart?

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?

like image 930
asterac Avatar asked Oct 20 '25 23:10

asterac


2 Answers

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.

like image 56
Ahmed Masud Avatar answered Oct 23 '25 15:10

Ahmed Masud


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.

like image 26
Gordon Davisson Avatar answered Oct 23 '25 13:10

Gordon Davisson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!