Right, let’s get this script party started. Before you can write the next great American novel in bash, you need to do two incredibly boring but absolutely critical things: tell the system what interpreter to use and make the darn file executable. Skip this, and you’ll be staring at a Permission denied error, wondering if the universe is personally rejecting your genius. It’s not. You just skipped the paperwork.

The Shebang: Your Script’s First Line of Defense

The very first line of any script you write must be the shebang (#!). This isn’t a suggestion; it’s a contract. When you run an executable file, the kernel looks at the first few bytes. If it sees #!, it knows, “Aha! This is a text file meant for an interpreter,” and it then reads the rest of the line to find out which interpreter.

The syntax is brutally simple. A space after the #! is technically allowed but utterly pointless. Don’t do it.

#!/bin/bash

This tells the kernel, “Hey, take the rest of this file and shovel it into the program located at /bin/bash.” Why /bin/bash and not just bash? Because the kernel doesn’t give a hoot about your $PATH. It needs a full, absolute path to the binary. It’s a literal-minded creature.

Now, you might see #!/usr/bin/env bash and wonder what fresh sorcery this is. This is the portable programmer’s workaround. Instead of hard-coding the path to bash (which might be /bin/bash on one system and /usr/local/bin/bash on another), it calls env and asks it to find bash using the $PATH. It’s generally considered a more flexible and modern approach.

#!/usr/bin/env bash

Why this matters: If you don’t have a shebang, and you somehow manage to run the script (we’ll get to that), your shell will just execute it in a sub-shell. This might work, but it’s a gamble on what shell the user is running. It’s sloppy and unprofessional. Always, always use a shebang.

Making the Leap from Text File to Tool

You’ve written your masterpiece. You’ve added your shebang. You try to run it…

$ ./my_awesome_script.sh
-bash: ./my_awesome_script.sh: Permission denied

Cue the frustration. This has nothing to do with the contents of your file and everything to do with the metadata attached to it—the file’s “mode” or permissions. In the Unix world, you must explicitly tell the system, “Yes, I am aware this is a text file, and yes, I am also aware of the risks, and I want to make it executable.” It’s a good safety feature. It prevents you from accidentally executing that PDF you just downloaded.

You grant execution permission using the chmod (change mode) command.

chmod +x my_awesome_script.sh

The +x means “add the execute permission.” Now, check the permissions with ls -l:

$ ls -l my_awesome_script.sh
-rwxr-xr-x  1 you  staff  123 Sep 26 10:00 my_awesome_script.sh

See that string of characters at the front? -rwxr-xr-x? The x appears in three places: for the owner (you), the group, and everyone else. The chmod +x adds it for all three. If you’re feeling security-conscious and want to be a bit more surgical (e.g., only let yourself run it), you can use chmod u+x (user/owner + execute).

Now, the magic happens:

$ ./my_awesome_script.sh
Hello, World! It works!

The ./ is crucial. It means “look in the current directory for the file called my_awesome_script.sh.” Your shell, by sane default, does not search the current directory for executables (it’s a security thing, to stop you from accidentally running a malicious ls program planted in a directory). You either have to provide a full path (/home/you/scripts/my_script.sh) or a relative one (./my_script).

The Order of Operations (And A Classic Pitfall)

Here’s the sequence of events when you type ./script.sh:

  1. The kernel sees the file has the execute permission bit set.
  2. The kernel opens the file and sees the shebang #!....
  3. The kernel starts the interpreter specified in the shebang (/bin/bash) as a new process.
  4. The kernel passes the path to your script (./script.sh) as an argument to the interpreter.
  5. bash now opens your script file and starts reading it line by line.

Now, for the classic “oh, come on” moment. What if you change the shebang after you’ve made the file executable? The next time you run it, it will use the new interpreter. The shebang is read every single time the script is executed. The execute permission is just a key to the door; the shebang is the instruction manual waiting inside.

One last pitfall: editing your script on Windows and then moving it to a Linux/Mac system. Windows uses \r\n (carriage return + line feed) for line endings, while Unix uses just \n. This can result in an error like bash: ./script.sh: /bin/bash^M: bad interpreter: No such file or directory. That ^M is the visible ghost of a carriage return. The fix is to run dos2unix on the file or use a editor that knows how to save files with Unix line endings. It’s a small thing that has derailed more scripts than I care to admit. Always check your line endings.