Learn BASH Scripting - Shebang, Execution Modes & First Script
Episode 2 of 27

Learn BASH Scripting - Shebang, Execution Modes & First Script

Writing your first BASH script correctly: understanding the shebang line and the difference between #!/bin/bash and /usr/bin/env bash, setting execution permission with chmod +x, and dissecting the three ways to run a script — as a program, as an interpreter argument, and as a source.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

In episode 1 we dissected the history — that BASH was born from the Bourne Shell, became the de facto standard, and sits among sh, zsh, and high-level languages — so in this episode we finally write our first real script. Episode 2 is the point where theory turns into practice: you'll write a file, make it executable, and run it through three different ways whose behavior is not the same.

The topics of this episode look simple — a first line called the shebang, one chmod +x command, and three ways to run a script. But don't be fooled: misunderstandings here are the source of many of the mysteries that frustrate beginners. Why does a script work with bash script.sh but fail with ./script.sh? Why do variables set inside a script "disappear" the moment the script ends? Why do files created on Windows error weirdly on Linux? All the answers are in this episode.

The Shebang Line: The Heart of a Script's Identity

What Is a Shebang?

Every good BASH script opens with the two characters #! on the first line. This combination is called a shebang (or hashbang). It's not a comment — it's an instruction to the kernel: "run this file with the interpreter I point to."

Basic shebang
#!/bin/bash
 
echo "Hello, BASH Scripting!"

When you type ./script.sh in the terminal, the kernel reads the first two bytes #!, finds the path /bin/bash, and hands the entire file to BASH for execution. Without a shebang, the system doesn't know which interpreter to use — and you'll get a Permission denied error or have to invoke the interpreter manually.

An analogy: the shebang is like the "Attach to relevant department" label on a letter. The kernel is the front desk that reads the label and forwards the letter (the script) to the right desk (the BASH interpreter). Without the label, the letter doesn't know where to go.

#!/bin/bash vs #!/usr/bin/env bash

Two most common variants:

ShebangHow it worksWhen to use
#!/bin/bashInvokes BASH directly from the absolute path /bin/bashMost common on servers; deterministic
#!/usr/bin/env bashLooks up bash from $PATHMore portable on non-Linux systems (BSD, macOS with Homebrew)

Why two options? On Linux, /bin/bash almost always exists. But on other systems, BASH can be located somewhere else — for example /opt/homebrew/bin/bash on macOS with Homebrew. #!/usr/bin/env bash solves that by finding bash wherever $PATH points.

The trade-off: env uses $PATH, so if $PATH is unusual (for example, the very minimal environment in cron), env bash can point to the wrong location. For this series — and for the majority of Linux server scripts — #!/bin/bash is the right, safe, and deterministic choice.

Note

Whether you use #!/bin/bash or #!/usr/bin/env bash, what matters is: always on the first line, with no leading space, and no spaces inside the path. Small mistakes here produce confusing errors — we cover them in the pitfalls section.

First Script: hello.sh

Let's write our first script. Create a file named hello.sh in your practice folder:

hello.sh — your first script
#!/bin/bash
 
echo "Hello, BASH Scripting!"
echo "Tanggal hari ini: $(date +%A)"
echo "Sistem ini bernama: $(hostname)"
Run with the interpreter (chmod not needed yet)
bash hello.sh
Expected output
Hello, BASH Scripting!
Tanggal hari ini: Minggu
Sistem ini bernama: arman-vm

Notice: $(date +%A) and $(hostname) are command substitutions — the command's output is inserted into the string. We won't dissect them now (there's a dedicated episode), but look at how easy it is to weave command output into text — this is BASH's power that you'll keep using.

chmod +x: Granting the Permission to Become a Program

Running with bash hello.sh doesn't need execution permission — you're invoking the interpreter manually. But to run ./hello.sh, the file must have execution permission. This is where chmod +x comes in:

Make it executable then run
chmod +x hello.sh
./hello.sh
Output
Hello, BASH Scripting!

Now you can check the difference in the file's metadata:

Compare permissions before & after chmod
ls -l hello.sh
Note the execution flag (x)
-rwxr-xr-x 1 arman arman 89 Agu  2 10:00 hello.sh

The -rwxr-xr-x notation means: owner can read-write-execute (rwx), group and others can read-execute (r-x). It's the execution permission (x) that allows the kernel to invoke the file as a program. Without chmod +x, ./hello.sh will refuse with Permission denied.

Tip

chmod +x adds execution permission for all categories (user, group, others). For personal scripts, chmod u+x hello.sh (owner only) is enough. Permission details — including octal modes like 755 and 644 — are covered in depth in the Learn Linux series' permission episode, but the principle is: a script needs x to be executed as a program.

Execution Modes: Three Ways to Run a Script

This is the most important part of this episode — and the most common source of confusion. There are three ways to run a script, and they are not the same:

MethodSyntaxNew process?Environment?
Program./hello.shYes, new subshellInherits environment, changes don't come back
Interpreter argumentbash hello.shYes, new subshellSame as above
Sourcesource hello.sh or . hello.shNo — runs in the current shellVariable changes persist in the shell

Let's dissect each one with an analogy: imagine the script is a sheet of instructions.

  1. ./hello.sh — you hand the instruction sheet to a new employee (a subshell process). The employee completes the task at their own desk, then goes home. Anything they wrote on their whiteboard (variables) is gone when they leave.

  2. bash hello.sh — same as above, but you choose the interpreter manually instead of letting the shebang decide. Because of that, it runs even if the file doesn't have the x permission.

  3. source hello.sh (or its shorthand . hello.sh) — you read out the instructions yourself at your desk, line by line, in the same shell. Anything you write on your own whiteboard (variables) stays there after you finish.

This difference is decisive. Let's prove it with an experiment.

Proof: The Environment Difference with export

Create two small scripts:

setenv.sh — trying to save a variable
#!/bin/bash
 
export MESSAGE="Dari dalam skrip"
echo "MESSAGE di dalam skrip: $MESSAGE"
Check with three execution methods
bash setenv.sh
echo "setelah bash setenv.sh   : [$MESSAGE]"
 
. setenv.sh
echo "setelah source setenv.sh : [$MESSAGE]"
Result — note the last lines
MESSAGE di dalam skrip: Dari dalam skrip
MESSAGE di dalam skrip: Dari dalam skrip
setelah bash setenv.sh   : []
setelah source setenv.sh : [Dari dalam skrip]

Notice the results:

  • With bash setenv.sh, the script runs in a subshell — the MESSAGE variable is created in that subshell and is gone the moment the script finishes. $MESSAGE in the main shell stays empty.
  • With source setenv.sh, the script runs in your own shell — the MESSAGE variable persists and is visible afterward.

This is why configuration files (like .bashrc) are sourced, not run as programs: you want their changes to stick to the current shell, not vanish in a subshell.

Important

The execution mode must match your goal. Want a script to modify your shell's environment (aliases, variables, functions)? Use source. Want the script to run independently and isolated (backup, deployment, cron)? Use ./script.sh or bash script.sh. Using the wrong method is the root of "mysteriously disappearing variables" — not a bug, but a mistake in choosing the execution mode.

When to Use Each

NeedRight modeReal-world example
Running an automation task./script.sh./deploy.sh, ./backup.sh
Running without execution permissionbash script.shbash setup.sh on someone else's system
Loading configuration into the shellsource / .. ~/.bashrc, source env.local
Running a script that sets variables for a sessionsourcesource venv/bin/activate

The rule of thumb sysadmins hold onto: default to ./script.sh (respecting the shebang and execution permission); use source only when you truly want to affect the current shell.

Subshell vs Current Shell

Now that you know the core difference, let's formalize it. When you run ./script.sh or bash script.sh, the main shell creates a fork — a child process (subshell) that inherits the environment (exported variables) but executes its instructions in its own space. Any change inside it — variables, cd, export — doesn't affect the parent.

Conversely, source doesn't create a new process. The script executes line by line inside the current shell, exactly as if you'd typed it yourself. A cd inside the script will change your shell's directory; an export will apply to all subsequent commands.

Another proof: source changes directories, normal execution doesn't
cat > whereami.sh <<'EOF'
#!/bin/bash
cd /tmp
echo "Sekarang di: $PWD"
EOF
 
bash whereami.sh
echo "Shell utama masih di : $PWD"
The subshell moves, the main shell doesn't
Sekarang di: /tmp
Shell utama masih di : /home/arman/lab

If you replace bash whereami.sh with source whereami.sh, the main shell moves to /tmp too. This experiment makes the subshell vs source difference tangible.

Common Pitfalls

  1. Forgetting chmod +x and typing ./script.sh. The Permission denied error doesn't mean the script is wrong — the file just isn't executable yet. The fix is chmod +x script.sh. This is why bash script.sh "works" while ./script.sh doesn't.

  2. A shebang with a space. #! /bin/bash (with a space after #!) isn't supported by all systems. Write it without a space: #!/bin/bash.

  3. A shebang that isn't on the first line. The shebang must be the first line. If there's an empty line or a comment above it, the kernel treats the file as a plain text script — and it may get run with the wrong shell.

  4. CRLF from Windows (the trickiest one). Files created in Notepad/Windows editors end with \r\n, while Linux uses \n. As a result, the first line becomes #!/bin/bash\r — the interpreter is "not found" and every line errors with $'\r': command not found. Fix it with:

Clean CRLF from a Windows script
dos2unix skrip.sh
# atau tanpa dos2unix:
sed -i 's/\r$//' skrip.sh
  1. Running a script that sets the environment with ./ when you wanted source. As we proved: an export inside a script run as a program is gone the moment it finishes. If you want the variables to persist, use source.

  2. Typing bash script.sh when the shebang is #!/usr/bin/env bash and the content uses BASH features. That's fine as long as BASH is what's invoked — but this habit can mask interpreter problems on other systems. Get used to invoking via ./script.sh so the shebang does the work.

Quick diagnosis: check hidden characters with cat -A
cat -A skrip.sh
Note the ^M at line ends = CRLF
#!/bin/bash^M$
echo "Halo"^M$

The ^M at the end of lines is the CRLF trace from Windows — the clearest visual clue why a script errors "weirdly".

Warning

When a script errors with $'\r': command not found or the interpreter is "not found" even though the shebang looks correct, CRLF should be your first suspicion. Run cat -A script.sh and look for ^M. This is one of the most common bugs that hits every beginner — and now you know how to beat it.

Conclusion

In episode 2 you wrote your first BASH script and understood three foundations that will stay with you forever: the shebang as the interpreter's identity (with the deterministic #!/bin/bash vs the portable #!/usr/bin/env bash), chmod +x as the key to running a script as a program, and three execution modes./script.sh and bash script.sh, which run in an isolated subshell, and source/., which runs in the current shell and makes changes persist.

The core takeaways:

  • The shebang is the interpreter label on the first line: #!/bin/bash for pure BASH.
  • chmod +x is needed for ./script.sh to run; bash script.sh doesn't need execution permission.
  • ./script.sh and bash script.sh run in a subshell — variable changes don't persist.
  • source script.sh runs in the current shell — variables, cd, and export persist.
  • $'\r' / interpreter not found errors = the file has CRLF line endings from Windows.

Now you know how to write and run scripts. In episode 3 we'll enter the BASH language itself: an introduction to and explanation of variables & environment variables — naming rules, the difference between $VAR and ${VAR}, export to pass variables to child processes, built-in variables like $PATH, $HOME, $USER, and building scripts that read the environment correctly. That's where you start "talking" to BASH, not just commanding it. See you there!