tl;dr: invoking the script as an executable is just a shortcut for calling the scriptâs interpreter as an executable with the source as an argument, and the execute permissions are only required to get the OS to read the shebang to identify the interpreter, not to get the interpreter to interpret the source file
When you invoke a script by calling the interpreter explicitly, youâre not running the script as an executable per se.
In other words, when you invoke
/bin/bash script.bash
at the command-line, the executable youâre invoking is /bin/bash
, and script.bash
is merely an argument telling it what source file to read. In this case, not only do you not need script.bash
to be marked executable, script.bash
does not need a shebang (the #!/bin/bash
or, more portably, #/usr/bin/env bash
line), either.
When you invoke the script by typing its full path as a command, however, youâre treating the script as an executableâ . When the operating system sees a file marked executable which is not a binary executable, it checks to see whether it has a #!<...>
line. If it does, it then invokes the interpreter named in the shebang line just like we did manually above. So
/usr/local/bin/script.bash
is transformed into
/usr/bin/env bash /usr/local/bin/script.bash
or whatever, according to your shebang line. But all of this only happens if the shebang is present and the file is marked executable.
âââââ
â : Generally, âexecutableâ means a binary executable compiled for a given platform. On some Unices (e.g., macOS), shells will read shebang lines of scripts invoked at them and launch them according to the shebang line. On Linux, thereâs a kernel feature that interprets shebangs, rendering scripts with shebang lines and the appropriate scripts âexecutablesâ in a deeper sense.