LFCS 120 ๐ง Shell Scripting โ Your First Script
A shell script is a text file containing a sequence of commands that the shell executes as if they had been typed at the prompt. It is not a compiled program, it does not require a build step, and it runs on any system with the shell that interprets it. The LFCS exam tests the basic structure: the shebang line, the executable permission, the variables, and the exit codes. The critical trap is the shebang: a script without one is executed by the current shell, which may be bash on one system and sh on another, and the two are not identical. The shebang makes the interpreter explicit.
Key point: The first line of a script is the shebang: #!/bin/bash or #!/usr/bin/env bash. It tells the kernel which interpreter to use. The file must have the executable bit set (chmod +x script.sh) or it cannot be executed directly. A script without the shebang can still be run with bash script.sh, but the behavior is determined by the invoking shell, not by the script.
Why shell scripts require a specific structure
The interpreter problem. The shebang is the first two characters of the file: #!. The kernel reads this line when the script is executed directly. If the path to the interpreter is correct, the kernel passes the rest of the file to that interpreter. If the path is wrong, the kernel returns “bad interpreter: No such file or directory.” The portable form is #!/usr/bin/env bash, which finds bash in the PATH instead of hardcoding /bin/bash. The non-portable form is faster and is the default on most Linux systems .
The permission problem. A script is a file. To execute it directly, the file must have the execute bit set. Without it, the shell returns “Permission denied.” The chmod +x script.sh command sets the bit for all users. The ./script.sh command executes the file in the current directory. The script.sh command without ./ searches the PATH and fails if the current directory is not in PATH .
The exit code problem. Every command returns an exit code. 0 means success. Any non-zero value means failure. The script’s exit code is the exit code of the last command executed. A script that does not explicitly call exit returns the status of its last command. The $? variable holds the exit code of the most recent command. The set -e option makes the script exit immediately if any command fails, which is the correct behavior for most scripts .
The variable problem. A shell variable is a name that holds a string. It is assigned with var=value (no spaces around the =). It is referenced with $var or ${var}. The ${var} form is required when the variable is followed by a character that could be part of the name (e.g., ${var}_suffix). Variables are not exported to child processes unless export is used. The $0 variable is the script name. The $1, $2, etc. are positional parameters. The $# variable is the number of positional parameters. The $@ variable is all positional parameters as separate words .
The trade-off. A shell script is fast to write and requires no toolchain. It is not the right tool for complex logic, data structures, or performance-critical work. The exam tests the basics because they are the foundation for the automation tasks that a Linux administrator performs daily.
a. The shebang and the executable permission
The first line of the script is the shebang. The rest of the file is the commands.
#!/bin/bash
# This is a comment.
echo "Hello, World"
The #!/bin/bash line tells the kernel to use /bin/bash to interpret the script. The #!/usr/bin/env bash line tells the kernel to find bash in the PATH. The second form is more portable; the first is slightly faster.
The file must be made executable before it can be run directly.
# Make the script executable
chmod +x hello.sh
# Run it
./hello.sh
# Output: Hello, World
Without the execute bit, the shell returns Permission denied. The bash hello.sh command still works because it passes the file to bash explicitly, which does not require the execute bit.
b. Variables and positional parameters
A variable is assigned without spaces around the =. The value is referenced with $name or ${name}.
#!/bin/bash
name="World"
echo "Hello, $name"
echo "Hello, ${name}!"
The "Hello, $name" and "Hello, ${name}!" forms produce the same output. The braces are needed when the variable name is followed by a character that could be part of the name. In "Hello, $name!", the ! is not part of the variable name, so the braces are not needed. In "Hello, ${name}_suffix", the braces are needed to distinguish name from name_suffix.
Positional parameters are the arguments passed to the script. The $1 is the first argument, $2 is the second, and so on. The $# is the number of arguments. The $@ is all arguments as separate words. The $0 is the script name.
#!/bin/bash
echo "Script: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Argument count: $#"
echo "All arguments: $@"
The "$@" form in double quotes preserves the argument boundaries. The $* form in double quotes joins all arguments into a single word. The difference matters when the arguments contain spaces.
c. Exit codes and the set options
The exit command terminates the script with a specific exit code. The $? variable holds the exit code of the most recent command. The set -e option makes the script exit immediately if any command returns a non-zero exit code. The set -u option makes the script exit if an unset variable is referenced. The set -o pipefail option makes a pipeline fail if any command in the pipeline fails, not just the last one.
#!/bin/bash
set -e
set -u
echo "Starting"
ls /nonexistent
echo "This line is not reached"
The ls command fails because the directory does not exist. The set -e option causes the script to exit before the next echo. Without set -e, the script continues and the final exit code is 0 (from the echo), which hides the failure.
The exit command is the explicit way to set the exit code.
#!/bin/bash
if [ ! -f /etc/passwd ]; then
echo "Error: /etc/passwd not found" >&2
exit 1
fi
echo "File found"
exit 0
The >&2 redirects the error message to standard error. The exit 1 terminates the script with a failure code. The exit 0 is the success code. The convention is that 0 is success and any non-zero value is failure.
Complete Example Session
# ============================================
# PART 1: CREATE THE SCRIPT
# ============================================
cat > hello.sh <<'EOF'
#!/bin/bash
echo "Hello, World"
EOF
# ============================================
# PART 2: MAKE IT EXECUTABLE
# ============================================
chmod +x hello.sh
ls -l hello.sh
# -rwxr-xr-x 1 user user 32 hello.sh
# ============================================
# PART 3: RUN IT
# ============================================
./hello.sh
# Output: Hello, World
# ============================================
# PART 4: ADD VARIABLES
# ============================================
cat > greet.sh <<'EOF'
#!/bin/bash
name="World"
echo "Hello, $name"
EOF
chmod +x greet.sh
./greet.sh
# Output: Hello, World
# ============================================
# PART 5: ADD POSITIONAL PARAMETERS
# ============================================
cat > args.sh <<'EOF'
#!/bin/bash
echo "Script: $0"
echo "First: $1"
echo "Count: $#"
echo "All: $@"
EOF
chmod +x args.sh
./args.sh alpha beta
# Script: ./args.sh
# First: alpha
# Count: 2
# All: alpha beta
# ============================================
# PART 6: ADD set -e
# ============================================
cat > fail.sh <<'EOF'
#!/bin/bash
set -e
echo "Before"
ls /nonexistent
echo "After"
EOF
chmod +x fail.sh
./fail.sh
# Before
# ls: cannot access '/nonexistent': No such file or directory
# The script exits after the ls failure.
# ============================================
# PART 7: ADD EXIT CODES
# ============================================
cat > check.sh <<'EOF'
#!/bin/bash
if [ ! -f /etc/passwd ]; then
echo "Error: file not found" >&2
exit 1
fi
echo "File found"
exit 0
EOF
chmod +x check.sh
./check.sh
# File found
echo $?
# 0
# ============================================
# PART 8: VERIFY EXIT CODE
# ============================================
./check.sh
echo $?
# 0
# Test failure
cat > fail-check.sh <<'EOF'
#!/bin/bash
exit 42
EOF
chmod +x fail-check.sh
./fail-check.sh
echo $?
# 42
# ============================================
# PART 9: USE set -u
# ============================================
cat > strict.sh <<'EOF'
#!/bin/bash
set -u
echo "Value: $undefined_var"
EOF
chmod +x strict.sh
./strict.sh
# ./strict.sh: line 3: undefined_var: unbound variable
The nine parts trace the first script: creation, permission, execution, variables, positional parameters, set -e, exit codes, exit code verification, and set -u.
Quick Reference
Shebang Forms
| Form | Portability |
|---|---|
#!/bin/bash | Hardcoded path |
#!/usr/bin/env bash | Portable (uses PATH) |
#!/bin/sh | POSIX shell |
Variable Syntax
| Syntax | Purpose |
|---|---|
var=value | Assignment (no spaces) |
$var | Reference |
${var} | Reference with braces |
$0 | Script name |
$1, $2 | Positional parameters |
$# | Number of parameters |
$@ | All parameters (separate words) |
$? | Exit code of last command |
set Options
| Option | Effect |
|---|---|
set -e | Exit on error |
set -u | Exit on undefined variable |
set -o pipefail | Pipe fails if any command fails |
set -x | Print commands before executing |
Exit Codes
| Code | Meaning |
|---|---|
0 | Success |
1 | General error |
2 | Misuse of shell built-in |
126 | Command not executable |
127 | Command not found |
128+N | Killed by signal N |
Best Practices
โ Do This:
#!/bin/bash
set -euo pipefail
<!-- Use strict mode for robustness --> # โ
chmod +x script.sh
<!-- Make the script executable --> # โ
echo "Error: file not found" >&2
<!-- Send errors to stderr --> # โ
โ Don’t Do This:
# Omit the shebang
<!-- The interpreter is undefined --> # โ
# Use spaces around the assignment
<!-- var = value is a command, not an assignment --> # โ
# Ignore exit codes
<!-- Failures go unnoticed --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
Permission denied | Execute bit not set | chmod +x script.sh |
bad interpreter | Wrong shebang path | Use #!/usr/bin/env bash |
| Variable empty | Assignment has spaces | var=value (no spaces) |
| Script continues after failure | No set -e | Add set -e |
| Unset variable not caught | No set -u | Add set -u |
Real-World Examples
1. Shebang
#!/bin/bash
2. Portable Shebang
#!/usr/bin/env bash
3. Basic Script
#!/bin/bash
echo "Hello"
4. Variable
name="World"
echo "Hello, $name"
5. Positional Parameter
echo "First argument: $1"
6. Strict Mode
set -euo pipefail
7. Error to Stderr
echo "Error" >&2
8. Exit Code
exit 1
9. Check Exit Code
echo $?
10. Debug Mode
set -x
Visual
Script Execution Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ./script.sh โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Kernel reads shebang: #!/bin/bash โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ /bin/bash script.sh is executed โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Commands run in sequence โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Exit code returned โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Strict Mode
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ set -e โ
โ โ Exit on any command failure โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ set -u โ
โ โ Exit on undefined variable โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ set -o pipefail โ
โ โ Pipe fails if any command fails โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Shebang | #!/bin/bash or #!/usr/bin/env bash |
| Execute permission | chmod +x script.sh |
| Variable assignment | var=value (no spaces) |
| Variable reference | $var or ${var} |
| Positional parameters | $1, $2, $@, $# |
| Exit code of last command | $? |
| Strict mode | set -euo pipefail |
| Error output | >&2 |
| Exit command | exit N |
Key takeaways:
- The shebang defines the interpreter. Without it, the script is executed by the current shell, which may not be the intended one. The
#!/usr/bin/env bashform is more portable than#!/bin/bash. - The execute bit is required for direct execution. The
chmod +xcommand sets the bit. Without it, the script can still be run withbash script.sh. - Variables are assigned without spaces.
var=valueis correct.var = valueis a command, not an assignment. set -euo pipefailis the strict mode. It makes the script exit on errors, undefined variables, and pipeline failures. This is the correct default for any script that must be reliable.- Exit codes matter. The
0is success. Any non-zero value is failure. Theexit Ncommand sets the code. The$?variable reads the code of the previous command.
Remember: The first script is a foundation. The shebang makes it executable. The variables make it configurable. The positional parameters make it reusable. The exit codes make it automatable. The set options make it reliable. The exam tests the syntax and the structure. The practice is the only way to internalize the quoting rules and the exit code semantics.
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!