| |

LFCS 120 ๐Ÿง Shell Scripting โ€” Your First Script

โฑ๏ธ Estimated read: 10 min | |

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

FormPortability
#!/bin/bashHardcoded path
#!/usr/bin/env bashPortable (uses PATH)
#!/bin/shPOSIX shell

Variable Syntax

SyntaxPurpose
var=valueAssignment (no spaces)
$varReference
${var}Reference with braces
$0Script name
$1, $2Positional parameters
$#Number of parameters
$@All parameters (separate words)
$?Exit code of last command

set Options

OptionEffect
set -eExit on error
set -uExit on undefined variable
set -o pipefailPipe fails if any command fails
set -xPrint commands before executing

Exit Codes

CodeMeaning
0Success
1General error
2Misuse of shell built-in
126Command not executable
127Command not found
128+NKilled 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

PitfallWhy It HappensFix
Permission deniedExecute bit not setchmod +x script.sh
bad interpreterWrong shebang pathUse #!/usr/bin/env bash
Variable emptyAssignment has spacesvar=value (no spaces)
Script continues after failureNo set -eAdd set -e
Unset variable not caughtNo set -uAdd 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

ItemValue
Shebang#!/bin/bash or #!/usr/bin/env bash
Execute permissionchmod +x script.sh
Variable assignmentvar=value (no spaces)
Variable reference$var or ${var}
Positional parameters$1, $2, $@, $#
Exit code of last command$?
Strict modeset -euo pipefail
Error output>&2
Exit commandexit 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 bash form is more portable than #!/bin/bash .
  • The execute bit is required for direct execution. The chmod +x command sets the bit. Without it, the script can still be run with bash script.sh.
  • Variables are assigned without spaces. var=value is correct. var = value is a command, not an assignment.
  • set -euo pipefail is 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 0 is success. Any non-zero value is failure. The exit N command 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.

Was this helpful?


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!