Bash Scripting 01 ๐ง Shell Basics โ Terminal environments and script execution
The previous chapter introduced the terminal environment. This chapter covers the mechanics of script execution: how a script is interpreted, what the shebang does, and why the difference between ./script.sh and bash script.sh matters.
Key point: The shebang (#!/bin/bash) is the script’s contract with the kernel, declaring which interpreter must execute it. The ./script.sh invocation requires the execute bit and uses the shebang; the bash script.sh invocation ignores the shebang and uses the explicitly named shell.
Why script execution requires a specific mental model
The shebang problem. A file ending in .sh is not inherently executable. The kernel does not parse shell syntax. When you run ./script.sh, the kernel checks the first two bytes. If they are #!, it treats the rest of the line as the interpreter path and executes that interpreter with the script as an argument. If the shebang is missing, the kernel falls back to the current shell, which may not be the intended one. A script written for bash features ([[ ]], arrays) may fail if executed by dash because the shebang was omitted or pointed to /bin/sh.
The permission problem. A file is not executable by default. Running ./script.sh before chmod +x produces “Permission denied.” The execute bit tells the kernel the file may be executed. The read bit must also be set, because the interpreter needs to read the script’s contents. Setting mode 111 (execute only) fails because the interpreter cannot open the file. The standard mode is 755 or 700.
The explicit interpreter problem. bash script.sh executes the bash binary, which then opens and reads script.sh. The script does not need the execute bit, because the file being executed is bash, not the script. The shebang is ignored. If the script’s shebang says #!/usr/bin/env python3 but you run bash script.sh, Bash attempts to parse Python code and fails.
The PATH problem. Typing script.sh without ./ fails unless the current directory is in $PATH, which is not the default for security reasons. The shell searches $PATH for the command and does not search the current directory. The ./ prefix tells the shell the script is a path, not a command name.
The trade-off. ./script.sh respects the script’s intent (the shebang) and is the correct way to run a script in production. bash script.sh is useful for debugging when you want to force a specific interpreter without editing the shebang, but it bypasses the script’s own declaration of its runtime requirements.
a. The shebang and the execute bit
The shebang is the first line of the script. It begins with #! followed by the absolute path to the interpreter.
#!/bin/bash
# This script runs under bash
echo "Shell: $BASH_VERSION"
Running the script without the execute bit fails.
$ ./script.sh
bash: ./script.sh: Permission denied
The chmod +x command adds the execute bit for all users. The chmod u+x adds it only for the owner. The mode 755 gives read and execute to everyone, write to the owner.
chmod +x script.sh
./script.sh
The shebang can point to env for portability, but env must be present on the system.
#!/usr/bin/env bash
This form searches $PATH for bash rather than hardcoding /bin/bash. It is useful on systems where bash is installed in a nonstandard location, but it adds a small overhead.
b. The bash script.sh invocation
The explicit invocation runs the named shell with the script as an argument. The shebang is not consulted.
$ bash script.sh
Shell: 5.2.21(1)-release
The script does not need the execute bit. The shell that runs is the one you named, regardless of the shebang. If the script contains a shebang for a different interpreter, it is treated as a comment.
#!/usr/bin/env python3
print("Hello")
Running bash script.sh on this file produces a syntax error, because Bash tries to parse the Python print statement. Running ./script.sh (after chmod +x) runs Python, because the kernel reads the shebang.
c. The subshell and the environment
A script invoked as ./script.sh runs in a subshellโa new process with a copy of the parent’s environment. The script’s $$ variable (PID) differs from the interactive shell’s PID. Changes to variables inside the script do not affect the parent shell.
$ echo $$
1375
$ ./script.sh
Script PID: 4337
The $$ in the script expands to the script’s PID, not the interactive shell’s. The parent shell’s variables are exported to the script only if they are marked for export. A variable defined without export is local to the shell and invisible to the script.
$ FOO="local"
$ ./script.sh
# FOO is not set in the script
$ export FOO="exported"
$ ./script.sh
# FOO is visible
The export mark is what passes the variable to child processes. The script inherits the exported environment, plus its own variables, but cannot modify the parent’s environment.
Complete Example Session
# ============================================
# PART 1: CREATE THE SCRIPT
# ============================================
cat > greet.sh <<'EOF'
#!/bin/bash
echo "Greeting from PID $$"
echo "Shell: $BASH_VERSION"
EOF
# ============================================
# PART 2: ATTEMPT DIRECT EXECUTION
# ============================================
./greet.sh
# bash: ./greet.sh: Permission denied
# ============================================
# PART 3: ADD THE EXECUTE BIT
# ============================================
chmod +x greet.sh
ls -l greet.sh
# -rwxr-xr-x 1 user user 68 ... greet.sh
# ============================================
# PART 4: RUN DIRECTLY
# ============================================
./greet.sh
# Greeting from PID 4337
# Shell: 5.2.21(1)-release
# ============================================
# PART 5: COMPARE WITH THE INTERACTIVE SHELL
# ============================================
echo $$
# 1375
# ============================================
# PART 6: RUN WITH EXPLICIT BASH
# ============================================
bash greet.sh
# Greeting from PID 4380
# Shell: 5.2.21(1)-release
# ============================================
# PART 7: CHANGE THE SHEBANG
# ============================================
sed -i '1s|.*|#!/bin/dash|' greet.sh
./greet.sh
# Shell: (empty, dash has no BASH_VERSION)
# ============================================
# PART 8: RUN WITH EXPLICIT BASH AGAIN
# ============================================
bash greet.sh
# Shell: 5.2.21(1)-release
# (ignores the dash shebang)
# ============================================
# PART 9: EXPORT A VARIABLE
# ============================================
export GREETING="Hello"
./greet.sh
# (the script does not reference $GREETING)
The nine parts trace the execution: the script creation, the permission failure, the execute bit, the direct run, the PID comparison, the explicit invocation, the shebang change, the explicit override, and the environment export.
Quick Reference
Shebang Forms
| Form | Path |
|---|---|
#!/bin/bash | Hardcoded |
#!/usr/bin/env bash | Via $PATH |
#!/bin/dash | Dash interpreter |
Permission Commands
| Command | Effect |
|---|---|
chmod +x script.sh | Add execute for all |
chmod u+x script.sh | Add execute for owner |
chmod 755 script.sh | Read/execute for all |
Invocation Comparison
| Method | Needs +x | Uses Shebang |
|---|---|---|
./script.sh | Yes | Yes |
bash script.sh | No | No |
Environment
| Command | Effect |
|---|---|
export VAR=value | Pass to child processes |
VAR=value | Local to the shell |
Best Practices
โ Do This:
#!/bin/bash
<!-- Include the shebang in every script --> # โ
chmod +x script.sh
./script.sh
<!-- Use the execute bit and the shebang --> # โ
export PATH="$PATH:/usr/local/bin"
<!-- Extend PATH rather than adding . --> # โ
โ Don’t Do This:
# Omit the shebang
<!-- The script may run under the wrong shell --> # โ
chmod 111 script.sh
<!-- Execute without read fails --> # โ
# Add . to $PATH
<!-- Security risk: the script shadowing --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Permission denied | No execute bit | chmod +x |
| Script runs under wrong shell | No shebang | Add #!/bin/bash |
bash script.sh fails on Python | Wrong interpreter | Use ./script.sh or python |
| Variables not visible in script | Not exported | export VAR=value |
| Script not found | No ./ prefix | ./script.sh |
Real-World Examples
1. Shebang
#!/bin/bash
2. Execute Bit
chmod +x script.sh
3. Direct Run
./script.sh
4. Explicit Run
bash script.sh
5. PID Check
echo $$
6. Export
export FOO=bar
7. Portability Shebang
#!/usr/bin/env bash
8. Dash Shebang
#!/bin/dash
9. PATH Extension
export PATH="$PATH:/usr/local/bin"
10. Verify Mode
ls -l script.sh
Visual
Shebang Resolution
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ./script.sh โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Kernel reads the first two bytes: #! โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ YES โ NO
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ Read interpreter โ โ Use current shell โ
โ from the line โ โ (may fail) โ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Execute the interpreter with the script โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Environment Inheritance
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Parent shell โ
โ โโโ FOO (exported) โ
โ โโโ BAR (local, not exported) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ ./script.sh
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Child shell (script) โ
โ โโโ FOO (inherited) โ
โ โโโ BAR (not visible) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Shebang | #!/bin/bash |
| Execute bit | chmod +x |
| Direct run | ./script.sh |
| Explicit run | bash script.sh |
| Subshell PID | $$ |
| Export | export VAR=value |
| PATH search | No . by default |
| Portability shebang | #!/usr/bin/env bash |
Key takeaways:
- The shebang is the interpreter contract. The kernel reads
#!and executes the named interpreter with the script as an argument. Without it, the current shell is used, which may lack the required features. - The execute bit is required for
./script.sh. The kernel must be permitted to execute the file, and the interpreter must be permitted to read it. bash script.shbypasses the shebang. The named shell runs the script as an argument. This is useful for debugging but ignores the script’s declared runtime.- Scripts run in a subshell. The environment is inherited only if the variables are exported. Changes inside the script do not affect the parent shell.
- The current directory is not in PATH. Typing
script.shwithout./fails. The./prefix is the standard way to run a local script.
Remember: The shebang is the intent. The execute bit is the permission. The ./ is the path. The export is the environment. The subshell is the isolation. The exam tests the execution mechanics, and the script that respects the shebang runs under the intended shell.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!