Setting up the default script interpreter in Apache on Linux - linux

On Windows, the following registry setting configures the script interpreter to be used by Apache:
HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command=C:\Perl\bin\perl.exe
How is this done on Linux?

To add a bit more information to #Mohit's good answer:
Unix uses many interpreters for many languages. Some of them are called "shells", but most are just another computer language to the system. In fact, every file is written in some language, even if it's compiled assembly of Java bytecodes.
The first few bytes of a file are "magic": they tell the OS how to execute the file. If the first two bytes are '#!', the OS knows that the file needs an interpreter. The rest of the first line up to newline is then used as a command to execute. The first "word" (space-separated group of non-spaces) of the line is interpreted as absolute file name to run, and all the other words are passed to it as command line arguments. Last parameter is the file name of the file you're running.
So, for example, if you have the first line as
#!/bin/tclsh
in a file /home/user/aaa.tcl
the OS will execute /bin/tclsh with /home/user/aaa.tcl as command line argument:
/bin/tclsh /home/user/aaa.tcl
For a more advanced example, try this:
#! /bin/env perl
in /home/user/myperlscript
This executes the following command:
/bin/env perl /home/user/myperlscript
/bin/env is a utility program that looks up its first argument using PATH environment variable, and then executes the program it finds, passing the rest of its arguments on to the program. With the help of env, you can use PATH to find your interpreters.

If you are talking about CGI script handlers.
It is set on the first line of each CGI script, I frequently use TCL as my script handler in Apache and hence add:
#!/bin/tclsh
Add this line on top of your script, eg. test.cgi and it will be executed by TCL shell whenever it is requested by someone.
Similary you can set it as
for BASH -- #!/bin/sh
or
for PERL -- #!/usr/bin/perl
Note: The path for the shell binary executable can be different, from above, on your machine. Use the following command to find it:
#which perl
Also, as Max has suggested, do check if Apache is configured to allow CGI scripts
Find detailed description of the same here at this Apache Tutorial Link

ScriptInterpreterSource is an Apache configuration setting and is only supported on Windows. I'm not really experienced at configuring Apache on Linux but I reckon you should check out the Script directive.

There is no registry under Linux. Also, I doubt you will get Perl.exe running under Linux.

Related

What is the purpose of a shebang [duplicate]

In a script you must include a #! on the first line followed by the path to the program that will execute the script (e.g.: sh, perl).
As far as I know, the # character denotes the start of a comment and that line is supposed to be ignored by the program executing the script. It would seem, that this first line is at some point read by something in order for the script to be executed by the proper program.
Could somebody please shed more light on the workings of the #!?
I'm really curious about this, so the more in-depth the answer the better.
Recommended reading:
The UNIX FAQ: Why do some scripts start with #! ... ?
The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
Wikipedia: Shebang
The unix kernel's program loader is responsible for doing this. When exec() is called, it asks the kernel to load the program from the file at its argument. It will then check the first 16 bits of the file to see what executable format it has. If it finds that these bits are #! it will use the rest of the first line of the file to find which program it should launch, and it provides the name of the file it was trying to launch (the script) as the last argument to the interpreter program.
The interpreter then runs as normal, and treats the #! as a comment line.
The Linux kernel exec system call uses the initial bytes #! to identify file type
When you do on bash:
./something
on Linux, this calls the exec system call with the path ./something.
This line gets called in the kernel on the file passed to exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
It reads the very first bytes of the file, and compares them to #!.
If the comparison is true, then the rest of the line is parsed by the Linux kernel, which makes another exec call with path /usr/bin/env python and current file as the first argument:
/usr/bin/env python /path/to/script.py
and this works for any scripting language that uses # as a comment character.
And yes, you can make an infinite loop with:
printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a
Bash recognizes the error:
-bash: /a: /a: bad interpreter: Too many levels of symbolic links
#! is human readable, but that is not necessary.
If the file started with different bytes, then the exec system call would use a different handler. The other most important built-in handler is for ELF executable files: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 which checks for bytes 7f 45 4c 46 (which also happens to be human readable for .ELF). Let's confirm that by reading the 4 first bytes of /bin/ls, which is an ELF executable:
head -c 4 "$(which ls)" | hd
output:
00000000 7f 45 4c 46 |.ELF|
00000004
So when the kernel sees those bytes, it takes the ELF file, puts it into memory correctly, and starts a new process with it. See also: How does kernel get an executable binary file running under linux?
Finally, you can add your own shebang handlers with the binfmt_misc mechanism. For example, you can add a custom handler for .jar files. This mechanism even supports handlers by file extension. Another application is to transparently run executables of a different architecture with QEMU.
I don't think POSIX specifies shebangs however: https://unix.stackexchange.com/a/346214/32558 , although it does mention in on rationale sections, and in the form "if executable scripts are supported by the system something may happen". macOS and FreeBSD also seem to implement it however.
Short story: The shebang (#!) line is read by the shell (e.g. sh, bash, etc.) the operating system's program loader. While it formally looks like a comment, the fact that it's the very first two bytes of a file marks the whole file as a text file and as a script. The script will be passed to the executable mentioned on the first line after the shebang. Voilà!
Slightly longer story: Imagine you have your script, foo.sh, with the executable bit (x) set. This file contains e.g. the following:
#!/bin/sh
# some script commands follow...:
# *snip*
Now, on your shell, you type:
> ./foo.sh
Edit: Please also read the comments below after or before you read the following! As it turns out, I was mistaken. It's apparently not the shell that passes the script to the target interpreter, but the operating system (kernel) itself.
Remember that you type this inside the shell process (let's assume this is the program /bin/sh). Therefore, that input will have to be processed by that program. It interprets this line as a command, since it discovers that the very first thing entered on the line is the name of a file that actually exists and which has the executable bit(s) set.
/bin/sh then starts reading the file's contents and discovers the shebang (#!) right at the very beginning of the file. To the shell, this is a token ("magic number") by which it knows that the file contains a script.
Now, how does it know which programming language the script is written it? After all, you can execute Bash scripts, Perl scripts, Python scripts, ... All the shell knows so far is that it is looking at a script file (which is not a binary file, but a text file). Thus it reads the next input up to the first line break (which will result in /bin/sh, compare with the above). This is the interpreter to which the script will be passed for execution. (In this particular case, the target interpreter is the shell itself, so it doesn't have to invoke a new shell for the script; it simply processes the rest of the script file itself.)
If the script was destined for e.g. /bin/perl, all that the Perl interpreter would (optionally) have to do is look whether the shebang line really mentions the Perl interpreter. If not, the Perl interpreter would know that it cannot execute this script. If indeed the Perl interpreter is mentioned in the shebang line, it reads the rest of the script file and executes it.

"mode" doesn't run in python 3.5 subprocess

I have encountered a bit of a conundrum while working on an automation project.
When I try to run:
program = subprocess.run("mode")
I get:
FileNotFoundError: [WinError 2] The system cannot find the file specified
However, when I replace mode with ipconfig:
program = subprocess.run("ipconfig")
it runs perfectly fine.
Anyone got an explanation? I am currently using a batch file to run the mode command, but I would like to change the arguments without editing a batch file.
Edit 1:
I also just tried using os.system:
os.system("mode")
and that also worked.
Edit 2:
Now I would just like answer to the original problem just to understand what was going on.
In Actual meaning of 'shell=True' in subprocess it pretty much says that shell=True is something that you should shy away from.
FileNotFoundError: [WinError 2] The system cannot find the file specified
Is what tipped me off that you might want shell=True in your subprocess call. If the file can't be found that means one of two things:
It's not on your path.
It's not actually a file.
For instance, in Linux:
$ which echo
echo: shell built-in command
That makes it pretty obvious that there is no echo file. It's just a command that's built into the shell. This may be the same thing when it comes to mode on Windows. Though this site seems to suggest that it's a MODE.COM file. You may try invoking that, as in
subprocess.run('MODE.COM')
That may work - at least according to one of the answers that I linked to
Invoking via the shell does allow you to expand environment variables and file globs according to the shell's usual mechanism. On POSIX systems, the shell expands file globs to a list of files. On Windows, a file glob (e.g., ".") is not expanded by the shell, anyway (but environment variables on a command line are expanded by cmd.exe).
So in your case, perhaps mode isn't a file, but MODE.COM is, and since Windows has a spotty relationship with casing, it seems possible that by passing shell=True, the Windows shell happily takes mode and converts it to MODE.COM for you, but without it, it tries to execute the file literally named mode, which doesn't exist.

What exactly does "/usr/bin/env node" do at the beginning of node files?

I had seen this line #!/usr/bin/env node at the beginning of some examples in nodejs and I had googled without finding any topic that could answer the reason for that line.
The nature of the words makes search it not that easy.
I'd read some javascript and nodejs books recently and I didn't remember seeing it in any of them.
If you want an example, you could see the RabbitMQ official tutorial, they have it in almost all of their examples, here is one of them:
#!/usr/bin/env node
var amqp = require('amqplib/callback_api');
amqp.connect('amqp://localhost', function(err, conn) {
conn.createChannel(function(err, ch) {
var ex = 'logs';
var msg = process.argv.slice(2).join(' ') || 'Hello World!';
ch.assertExchange(ex, 'fanout', {durable: false});
ch.publish(ex, '', new Buffer(msg));
console.log(" [x] Sent %s", msg);
});
setTimeout(function() { conn.close(); process.exit(0) }, 500);
});
Could someone explain me what is the meaning of this line?
What is the difference if I put or remove this line? In what cases do I need it?
#!/usr/bin/env node is an instance of a shebang line: the very first line in an executable plain-text file on Unix-like platforms that tells the system what interpreter to pass that file to for execution, via the command line following the magic #! prefix (called shebang).
Note: Windows does not support shebang lines, so they're effectively ignored there; on Windows it is solely a given file's filename extension that determines what executable will interpret it. However, you still need them in the context of npm.[1]
The following, general discussion of shebang lines is limited to Unix-like platforms:
In the following discussion I'll assume that the file containing source code for execution by Node.js is simply named file.
You NEED this line, if you want to invoke a Node.js source file directly, as an executable in its own right - this assumes that the file has been marked as executable with a command such as chmod +x ./file, which then allows you to invoke the file with, for instance, ./file, or, if it's located in one of the directories listed in the $PATH variable, simply as file.
Specifically, you need a shebang line to create CLIs based on Node.js source files as part of an npm package, with the CLI(s) to be installed by npm based on the value of the "bin" key in a package's package.json file; also see this answer for how that works with globally installed packages. Footnote [1] shows how this is handled on Windows.
You do NOT need this line to invoke a file explicitly via the node interpreter, e.g., node ./file
Optional background information:
#!/usr/bin/env <executableName> is a way of portably specifying an interpreter: in a nutshell, it says: execute <executableName> wherever you (first) find it among the directories listed in the $PATH variable (and implicitly pass it the path to the file at hand).
This accounts for the fact that a given interpreter may be installed in different locations across platforms, which is definitely the case with node, the Node.js binary.
By contrast, the location of the env utility itself can be relied upon to be in the same location across platforms, namely /usr/bin/env - and specifying the full path to an executable is required in a shebang line.
Note that POSIX utility env is being repurposed here to locate by filename and execute an executable in the $PATH.
The true purpose of env is to manage the environment for a command - see env's POSIX spec and Keith Thompson's helpful answer.
It's also worth noting that Node.js is making a syntax exception for shebang lines, given that they're not valid JavaScript code (# is not a comment character in JavaScript, unlike in POSIX-like shells and other interpreters).
[1] In the interest of cross-platform consistency, npm creates wrapper *.cmd files (batch files) on Windows when installing executables specified in a package's package.json file (via the "bin" property). Essentially, these wrapper batch files mimic Unix shebang functionality: they invoke the target file explicitly with the executable specified in the shebang line - thus, your scripts must include a shebang line even if you only ever intend to run them on Windows - see this answer of mine for details.
Since *.cmd files can be invoked without the .cmd extension, this makes for a seamless cross-platform experience: on both Windows and Unix you can effectively invoke an npm-installed CLI by its original, extension-less name.
Scripts that are to be executed by an interpreter normally have a shebang line at the top to tell the OS how to execute them.
If you have a script named foo whose first line is #!/bin/sh, the system will read that first line and execute the equivalent of /bin/sh foo. Because of this, most interpreters are set up to accept the name of a script file as a command-line argument.
The interpreter name following the #! has to be a full path; the OS won't search your $PATH to find the interpreter.
If you have a script to be executed by node, the obvious way to write the first line is:
#!/usr/bin/node
but that doesn't work if the node command isn't installed in /usr/bin.
A common workaround is to use the env command (which wasn't really intended for this purpose):
#!/usr/bin/env node
If your script is called foo, the OS will do the equivalent of
/usr/bin/env node foo
The env command executes another command whose name is given on its command line, passing any following arguments to that command. The reason it's used here is that env will search $PATH for the command. So if node is installed in /usr/local/bin/node, and you have /usr/local/bin in your $PATH, the env command will invoke /usr/local/bin/node foo.
The main purpose of the env command is to execute another command with a modified environment, adding or removing specified environment variables before running the command. But with no additional arguments, it just executes the command with an unchanged environment, which is all you need in this case.
There are some drawbacks to this approach. Most modern Unix-like systems have /usr/bin/env, but I worked on older systems where the env command was installed in a different directory. There might be limitations on additional arguments you can pass using this mechanism. If the user doesn't have the directory containing the node command in $PATH, or has some different command called node, then it could invoke the wrong command or not work at all.
Other approaches are:
Use a #! line that specifies the full path to the node command itself, updating the script as needed for different systems; or
Invoke the node command with your script as an argument.
See also this question (and my answer) for more discussion of the #!/usr/bin/env trick.
Incidentally, on my system (Linux Mint 17.2), it's installed as /usr/bin/nodejs. According to my notes, it changed from /usr/bin/node to /usr/bin/nodejs between Ubuntu 12.04 and 12.10. The #!/usr/bin/env trick won't help with that (unless you set up a symlink or something similar).
UPDATE: A comment by mtraceur says (reformatted):
A workaround for the nodejs vs node problem is to start the file with
the following six lines:
#!/bin/sh -
':' /*-
test1=$(nodejs --version 2>&1) && exec nodejs "$0" "$#"
test2=$(node --version 2>&1) && exec node "$0" "$#"
exec printf '%s\n' "$test1" "$test2" 1>&2
*/
This will first try nodejs and then try node, and only
print the error messages if both of them are not found. An explanation
is out of scope of these comments, I'm just leaving it here in case it
helps anyone deal with the problem since this answer brought the
problem up.
I haven't used NodeJS lately. My hope is that the nodejs vs. node issue has been resolved in the years since I first posted this answer. On Ubuntu 18.04, the nodejs package installs /usr/bin/nodejs as a symlink to /usr/bin/node. On some earlier OS (Ubuntu or Linux Mint, I'm not sure which), there was a nodejs-legacy package that provided node as a symlink to nodejs. No guarantee that I have all the details right.
The exec system call of the Linux kernel understands shebangs (#!) natively
When you do on bash:
./something
on Linux, this calls the exec system call with the path ./something.
This line of the kernel gets called on the file passed to exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
It reads the very first bytes of the file, and compares them to #!.
If the comparison is true, then the rest of the line is parsed by the Linux kernel, which makes another exec call with:
executable: /usr/bin/env
first argument: node
second argument: script path
therefore equivalent to:
/usr/bin/env node /path/to/script.js
env is an executable that searches PATH to e.g. find /usr/bin/node, and then finally calls:
/usr/bin/node /path/to/script.js
The Node.js interpreter does see the #! line in the file, but it must be programmed to ignore that line even though # is not in general a valid comment character in Node (unlike many other languages such as Python where it is), see also: Pound Sign (#) As Comment Start In JavaScript?
And yes, you can make an infinite loop with:
printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a
Bash recognizes the error:
-bash: /a: /a: bad interpreter: Too many levels of symbolic links
#! just happens to be human readable, but that is not required.
If the file started with different bytes, then the exec system call would use a different handler. The other most important built-in handler is for ELF executable files: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 which checks for bytes 7f 45 4c 46 (which also happens to be human readable for .ELF). Let's confirm that by reading the 4 first bytes of /bin/ls, which is an ELF executable:
head -c 4 "$(which ls)" | hd
output:
00000000 7f 45 4c 46 |.ELF|
00000004
So when the kernel sees those bytes, it takes the ELF file, puts it into memory correctly, and starts a new process with it. See also: How does kernel get an executable binary file running under linux?
Finally, you can add your own shebang handlers with the binfmt_misc mechanism. For example, you can add a custom handler for .jar files. This mechanism even supports handlers by file extension. Another application is to transparently run executables of a different architecture with QEMU.
I don't think POSIX specifies shebangs however: https://unix.stackexchange.com/a/346214/32558 , although it does mention in on rationale sections, and in the form "if executable scripts are supported by the system something may happen". macOS and FreeBSD also seem to implement it however.
PATH search motivation
Likely, one big motivation for the existence of shebangs is the fact that in Linux, we often want to run commands from PATH just as:
basename-of-command
instead of:
/full/path/to/basename-of-command
But then, without the shebang mechanism, how would Linux know how to launch each type of file?
Hardcoding the extension in commands:
basename-of-command.js
or implementing PATH search on every interpreter:
node basename-of-command
would be a possibility, but this has the major problem that everything breaks if we ever decide to refactor the command into another language.
Shebangs solve this problem beautifully.
Short answer:
It is the path to the interpreter.
EDIT (Long Answer):
The reason there is no slash before "node" is because you can not always guarantee the reliability of #!/bin/ . The "/env" bit makes the program more cross-platform by running the script in a modified environment and more reliably being able to find the interpreter program.
You do not necessarily need it, but it is good to use to ensure portability (and professionalism)

How to know what script header to use and why it matters?

I infrequently have to write bash scripts for various unrelated purposes and while I usually have a good idea what commands I want in the script, I often have no idea what header to use or why I'm using one when I do find it. For example(s):
Standard shell script:
#!/bin/bash
Python:
#!/usr/bin/env python
Scripts seem to work fine without headers but if headers are the standard, there's a reason for them and they shouldn't be ignored. If it has an effect, then it's a valuable tool that could be used to accomplish more.
Minimally, I'd like to know what headers to use with MySQL scripts and what the headers do on Standard, Python, and MySQL scripts. Ideally, I'd like a generic list of headers or an understanding of how to create a header based on what program is being used.
How the Kernel Executes Things
Simplified (a bit), there are two ways the kernel in a POSIX system knows how to execute a program. One, if the program is in a binary format the kernel understands (such as ELF), the kernel can execute it "directly" (more detail out of scope). If the program is a text file starting with a shebang, such as
#!/usr/bin/somebinary -arg
or what-have-you, the kernel actually executes the command as if it had been directed to execute:
/usr/bin/somebinary -arg "$0"
where $0 here is the name of the script file you just tried to execute. (So you can immediately tell why so many scripting languages use # as a comment-starter – it means they don't have to treat the shebang as special.)
PATH and the env command
The kernel does not look at the PATH environment variable to determine which executable you're talking about, so if you are distributing a python script to systems that may have multiple versions of python installed, you can't guarantee that there will be a
#!/usr/bin/python
env, however, is POSIX, so you can count on it existing, and it will look up python in PATH. Thus,
#!/usr/bin/env python
will execute the script with the first python found in your PATH.
BASH, SH and Special Meanings for Invocation
Some programs have special semantics for how they're invoked. In particular, on many systems /bin/sh is a symlink to another shell, such as /bin/bash. While bash does not contain a perfectly POSIXLY_STRICT implementation of sh, when it is invoked as /bin/sh it is stricter than it would be if invoked as plain-old-bash.
MySQL and arg limitations
The shebang line can be length limited and technically, it can only support one argument, so mysql is a bit tricky – you can't expect to pass a username and database name to a mysql script.
#!/usr/bin/env mysql
use mydb;
select * from mytbl;
Will fail because the kernel will try mysql "$0". Even if you have your credentials in a .my.cnf file, mysql itself will try to treat "$0" as a database name. Likewise:
#!/usr/bin/mysql -e
use mydb;
select * from mytbl;
will fail because again, "$0" is not a table name (you hope).
There does not seem to be an appropriate syntax for directly executing a mysql script this way. Your best bet is to pipe the sql commands to mysql directly:
mysql < my_sql_commands
http://mywiki.wooledge.org/BashGuide/Practices#Choose_Your_Shell
When the first line of a script starts with #!, that's what's called a "shebang". When that script is run as an executable, the operating system uses that line to determine how to run the script -- that is to say, to find the program with which the script should be executed.
It's incorrect that "scripts work fine without headers" -- if you don't have a shebang line, you can't be invoked using the execve() call, which means that many (most?) programs won't be able to execute your script. Sometimes invocation from a shell will try to use that shell itself in the absence of a shebang, but you can't trust that to be the case.
(There's an exception to that -- if someone starts your script by running sh yourscript or bash yourscript, the shebang line isn't read at all, and the script they chose is used; however, running scripts this way is a bad practice, as the author typically knows better than the user what the correct interpreter is).
In short:
If you want to use modern features, and you want the user to be able to override the shell version in use by putting a different release of bash earlier in their path, use #!/usr/bin/env bash
If you want to use modern features and ensure that you always run with the system shell, use #!/bin/bash
If you're going to write your script to strictly conform with POSIX sh, use #!/bin/sh
There's not a limited list of shebang lines we can give you, since any native executable (non-script program) can be used as a script interpreter, and thus be placed in a shebang. If you created a file called myscript with #!/usr/bin/env yourprogram, gave it executable permissions, and ran ./myscript foo bar, this would result in /usr/bin/env yourprogram myscript foo bar being invoked; yourprogram would be run by /usr/bin/env (after a PATH lookup), and would be responsible for knowing what to do with myscript and its arguments.
For an extremely detailed history of shebang lines and how they work across systems both modern and ancient, see http://www.in-ulm.de/~mascheck/various/shebang/

Why do you need to put #!/bin/bash at the beginning of a script file?

I have made Bash scripts before and they all ran fine without #!/bin/bash at the beginning.
What's the point of putting it in? Would things be any different?
Also, how do you pronounce #? I know that ! is pronounced as "bang."
How is #! pronounced?
It's a convention so the *nix shell knows what kind of interpreter to run.
For example, older flavors of ATT defaulted to sh (the Bourne shell), while older versions of BSD defaulted to csh (the C shell).
Even today (where most systems run bash, the "Bourne Again Shell"), scripts can be in bash, python, perl, ruby, PHP, etc, etc. For example, you might see #!/bin/perl or #!/bin/perl5.
PS:
The exclamation mark (!) is affectionately called "bang". The shell comment symbol (#) is sometimes called "hash".
PPS:
Remember - under *nix, associating a suffix with a file type is merely a convention, not a "rule". An executable can be a binary program, any one of a million script types and other things as well. Hence the need for #!/bin/bash.
To be more precise the shebang #!, when it is the first two bytes of an executable (x mode) file, is interpreted by the execve(2) system call (which execute programs). But POSIX specification for execve don't mention the shebang.
It must be followed by a file path of an interpreter executable (which BTW could even be relative, but most often is absolute).
A nice trick (or perhaps not so nice one) to find an interpreter (e.g. python) in the user's $PATH is to use the env program (always at /usr/bin/env on all Linux) like e.g.
#!/usr/bin/env python
Any ELF executable can be an interpreter. You could even use #!/bin/cat or #!/bin/true if you wanted to! (but that would be often useless)
It's called a shebang. In unix-speak, # is called sharp (like in music) or hash (like hashtags on twitter), and ! is called bang. (You can actually reference your previous shell command with !!, called bang-bang). So when put together, you get haSH-BANG, or shebang.
The part after the #! tells Unix what program to use to run it. If it isn't specified, it will try with bash (or sh, or zsh, or whatever your $SHELL variable is) but if it's there it will use that program. Plus, # is a comment in most languages, so the line gets ignored in the subsequent execution.
Every distribution has a default shell. Bash is the default on the majority of the systems. If you happen to work on a system that has a different default shell, then the scripts might not work as intended if they are written specific for Bash.
Bash has evolved over the years taking code from ksh and sh.
Adding #!/bin/bash as the first line of your script, tells the OS to invoke the specified shell to execute the commands that follow in the script.
#! is often referred to as a "hash-bang", "she-bang" or "sha-bang".
The shebang is a directive to the loader to use the program which is specified after the #! as the interpreter for the file in question when you try to execute it. So, if you try to run a file called foo.sh which has #!/bin/bash at the top, the actual command that runs is /bin/bash foo.sh. This is a flexible way of using different interpreters for different programs. This is something implemented at the system level and the user level API is the shebang convention.
It's also worth knowing that the shebang is a magic number - a human readable one that identifies the file as a script for the given interpreter.
Your point about it "working" even without the shebang is only because the program in question is a shell script written for the same shell as the one you are using. For example, you could very well write a javascript file and then put a #! /usr/bin/js (or something similar) to have a javascript "Shell script".
The operating system takes default shell to run your shell script. so mentioning shell path at the beginning of script, you are asking the OS to use that particular shell. It is also useful for portability.
It is called a shebang. It consists of a number sign and an exclamation point character (#!), followed by the full path to the interpreter such as /bin/bash. All scripts under UNIX and Linux execute using the interpreter specified on a first line.
Bash standards for “Bourne-Again shell” is just one type of many available
shells in Linux.
A shell is a command line interpreter that accepts and runs commands.
Bash is often the default shell in most Linux distributions. This is why bash is
synonymous to shell.
The shell scripts often have almost the same syntaxes, but they also differ sometimes. For example, array index starts at 1 in Zsh instead of 0 in bash. A script
written for Zsh shell won’t work the same in bash if it has arrays.
To avoid unpleasant surprises, you should tell the interpreter that your shell script
is written for bash shell. How do you do that?
simply begin your bash script into #!/bin/bash
Also you will see some other parameters after #!/bin/bash,
for example
#!/bin/bash -v -x
read this to get more idea.
https://unix.stackexchange.com/questions/124272/what-do-the-arguments-v-and-x-mean-to-bash .
It can be useful to someone that uses a different system that does not have that library readily available. If that is not declared and you have some functions in your script that are not supported by that system, you should declare #/bin/bash. I've ran into this problem before at work and now I just include it as a practice.

Resources