Bash Basics #1: Create Your First Bash Shell Script

Welcome to the first part of our Bash basics series! Learn step by step how to create your first bash shell script, make it executable, and write robust scripts.

Reading time: 15 min

Welcome to the first part of our technical series on Linux and Bash programming!

In this multi-part series, you will learn step by step how to create, execute, and use your own shell scripts to automate administrative workflows. Whether you are new to the Linux command line or want to refresh and deepen your practical skills: This basics course teaches you the fundamental knowledge for clean, safe, and reproducible scripting.

What does Bash mean?

Bash (Bourne Again Shell) is the default shell on most Linux distributions and many Unix environments. It serves both as an interactive command-line processor for system control and as a fully-fledged, powerful scripting language. With Bash scripts, you can automate complex workflows, monitor system services, create data backups, and significantly boost your productivity as an administrator or developer.

In this first lesson, we focus on the absolute fundamentals:

We create your very first script, analyze the shebang mechanism, assign execution permissions, distinguish different execution contexts, and transform a simple script into a robust program using Bash Strict Mode.

Course Overview: Your Learning Roadmap

Our Bash basics course is modular and guides you step by step from the first command to professional automation scripts:


┌─────────────────────────────────────────────────────────────┐
│                 BASH BASICS: CURRICULUM ROADMAP              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [ MODULE 1: First Script & Basics (THIS ARTICLE) ]         │
│  └── Shebang, chmod, Subshell vs. Sourcing, Strict Mode     │
│                                                             │
│  [ MODULE 2: Variables & Data Types ]                        │
│  └── Strings, Numbers, Arrays, Quoting & Expansion           │
│                                                             │
│  [ MODULE 3: Control Structures & Conditions ]               │
│  └── if/then/else, case, Test Operators [[ ]], while, for   │
│                                                             │
│  [ MODULE 4: Functions & Modularization ]                    │
│  └── Reusability, Return Codes, Scopes (local)               │
│                                                             │
│  [ MODULE 5: Input/Output & Data Streams ]                   │
│  └── stdin, stdout, stderr, Pipes, Here-Docs, read dialogs  │
│                                                             │
│  [ MODULE 6: Error Handling & Debugging ]                    │
│  └── Trap signal handling, Exit Codes, Logging, xtrace       │
│                                                             │
│  [ MODULE 7: Advanced Techniques & Best Practices ]          │
│  └── ShellCheck, Performance, Parsing, Deployment scripts    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

💡 Prerequisites: A working Linux terminal (e.g., on Ubuntu, Debian, Arch Linux, Fedora, or openSUSE) and a simple text editor like nano or vim.

Step 1: Create the Classic "Hello World" Script

Let's start with the tried-and-true "Hello World" example. This script helps you understand the basic structure of a script file.

Open Terminal and Create Script Directory

Open a terminal window (on most desktop environments with the key combination Ctrl + Alt + T).

Create a dedicated folder for your scripts and navigate into it:


mkdir -p ~/scripts && cd ~/scripts

Create File in Editor

Open a new file named hello_world.sh in the nano text editor:


nano hello_world.sh

Enter the following content:


#!/bin/bash
echo "Hello, World!"

Let's examine these two lines more closely:

  • Line 1 (#!/bin/bash): This is called the shebang. It tells the Linux operating system which interpreter should be used to execute the file.
  • Line 2 (echo "Hello, World!"): The echo command outputs the specified string to the standard output stream (stdout) in the terminal.

Save the file in nano with Ctrl + O, confirm with Enter, and exit the editor with Ctrl + X.

The Architecture Behind the Shebang (#!)

When you invoke a binary file, the Linux kernel loads the machine code directly. A script, however, consists of unformatted text.

To know which program should process this text, the kernel checks the first two bytes of the file during the execve() system call:


┌─────────────────────────────────────────────────────────────┐
│                 KERNEL SCRIPT EXECUTION FLOW                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [ User executes script: ./hello_world.sh ]                 │
│         │                                                   │
│         ▼                                                   │
│  Kernel System-Call: execve("./hello_world.sh", ...)        │
│         │                                                   │
│         ▼                                                   │
│  Check the first 2 bytes of the file (Magic Bytes):         │
│  0x23 0x21  ──>  ASCII characters: "#!" (Shebang)           │
│         │                                                   │
│         ├── YES: Read rest of line 1 (Interpreter path)     │
│         │   └─► Launch: /bin/bash ./hello_world.sh          │
│         │                                                   │
│         └── NO:                                              │
│             └─► Fallback: Launch with default system shell  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Portable Shebang: #!/usr/bin/env bash

Alongside the classic path #!/bin/bash, there is an even more flexible variant:


#!/usr/bin/env bash

The env utility dynamically locates Bash using the user's $PATH environment variable. This is particularly useful on systems where Bash is not located at /bin/bash (such as macOS or FreeBSD).

File Permissions: Making the Script Executable

By default, newly created text files under Linux have no execute permission (execute bit). If you try to run the script directly, the system denies access:


./hello_world.sh
# Output: bash: ./hello_world.sh: Permission denied

Using the chmod command, you add execute permission (+x) for the file:


chmod +x hello_world.sh

Now you run the script by typing its name with ./ prefixed:


./hello_world.sh

Output:


Hello, World!

💡 Note on the ./ path: For security reasons, Linux only searches the system directories listed in $PATH (like /usr/bin), not the current directory (.). This prevents malicious scripts in the current folder from being executed accidentally. With ./, you explicitly tell the shell: "Execute the file right here in the current directory."

Detailed background on the Linux permission system can be found in our guide to chmod and file permissions.

Execution Methods: Subshell vs. Interpreter vs. Sourcing

There are three fundamental ways to execute a shell script under Linux:


┌─────────────────────────────────────────────────────────────┐
│                 THREE METHODS TO EXECUTE SCRIPTS             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Method 1: Direct Execution (./hello_world.sh)              │
│  ├── Requires: Read bit (r) AND Execute bit (x)             │
│  └── Launches: Isolated child process (Subshell)             │
│                                                             │
│  Method 2: Via Interpreter (bash hello_world.sh)            │
│  ├── Requires: Only Read bit (r)                            │
│  └── Launches: Isolated child process (Subshell)             │
│                                                             │
│  Method 3: Sourcing (source script.sh or . script.sh)       │
│  ├── Requires: Only Read bit (r)                            │
│  └── Launches: NO Subshell! Runs in CURRENT shell           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1. Direct Execution (./hello_world.sh)

The standard way for finished scripts. Launches a new child shell (subshell). Requires chmod +x.

2. Via Interpreter (bash hello_world.sh)

You pass the file directly to Bash:


bash hello_world.sh
  • Advantage: The file does not need the execute bit (chmod +x), only read permissions (r), since Bash reads the file as a plain text file.
  • Use case: Ideal for quick testing during development.

3. Sourcing into the Current Shell (source or .)

When sourcing, no new process is started:


source hello_world.sh
# or shorter:
. hello_world.sh
  • Effect: Variables and functions defined in the script remain available in your current terminal session after execution (e.g., source ~/.bashrc).

From Simple Shell Script to Real Bash Script

So far, we have only executed simple commands. To unlock the full potential of Bash and integrate advanced language features as well as protective mechanisms, we expand our script:

Open hello_world.sh again in the editor:


nano hello_world.sh

Replace the content with the following code:


#!/usr/bin/env bash

# Enable Bash Strict Mode
set -euo pipefail
IFS=$'\n\t'

# 1. Declare a variable
greeting="Hello, World!"

# 2. Parameter expansion: Convert string to uppercase
uppercase_greeting="${greeting^^}"

# 3. Colored output with ANSI escape sequences
echo -e "\e[1;34m$greeting\e[0m"
echo -e "\e[1;31m$uppercase_greeting\e[0m"

# 4. Bash-specific condition and pattern matching
if [[ "$greeting" == *"World"* ]]; then
    echo "The greeting contains the word 'World'."
fi

What do these additions mean in detail?

  1. set -euo pipefail (Bash Strict Mode):
  • set -e (errexit): Aborts the script immediately if a command fails.
  • set -u (nounset): Prevents execution on typos in variable names.
  • set -o pipefail: Also detects errors within pipeline commands.
  1. IFS=$'\n\t': Prevents word splitting on whitespace in filenames.
  2. ${greeting^^}: Bash-specific parameter expansion that converts the text to uppercase.
  3. echo -e "\e[1;34m...": Activates ANSI color codes (blue and red) in the terminal.
  4. [[ "$greeting" == "World" ]]: The modern Bash conditional operator [[ ]] enables flexible pattern matching with wildcards.

Save the changes and run the script again:


./hello_world.sh

You will now see a colored output and the confirmation of the conditional check in the terminal.

Exit Codes and Return Values ($?)

Every clean script returns an exit code to the calling system:

  • exit 0: Signals to the operating system: Success.
  • exit 1 to exit 255: Signals an error state.

#!/usr/bin/env bash
set -euo pipefail

echo "System check running..."

# Successful completion
exit 0

You can query the exit code of the last executed command or script with echo $?:


./hello_world.sh
echo "Status: $?"  # Outputs 0

Debugging: Finding Errors in Scripts Quickly

When a script doesn't work as expected, Bash provides built-in diagnostic options:

1. Execution Trace (bash -x)

With -x (xtrace), Bash displays every executed command along with all resolved variables:


bash -x hello_world.sh

2. Syntax Check (bash -n)

Checks the file purely for syntax errors (like forgotten quotation marks), without executing any commands:


bash -n hello_world.sh

3. Static Code Analysis with ShellCheck

The open-source tool ShellCheck detects typical errors and bad habits:


# Installation on Ubuntu/Debian
sudo apt install shellcheck

# Run analysis
shellcheck hello_world.sh

Exercises & Practice Check

Practice tasks for lesson #1:

  1. Task 1 (Interactive Dialog):

Write a script dialog.sh that asks the user for their name using read -r and outputs a personalized, colored greeting.

  1. Task 2 (System Status Report):

Create a script status.sh that displays the current user ($USER), the date, the hostname, and the current directory path (pwd) in a structured format.

  1. Task 3 (Strict Mode Test):

Create a script with set -u and try to output an undefined variable. Observe Bash's error message.

Sample Solution for Task 1:


#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Ask for the name
echo -n "What is your name? "
read -r name

# Personalized greeting
greeting="Hello, $name!"
echo -e "\e[1;32m$greeting\e[0m Welcome to the Bash basics course."

exit 0

Command Reference (Cheatsheet)

Command / Syntax Function & Description
#!/usr/bin/env bash Portable shebang: Dynamically locates Bash via $PATH
chmod +x script.sh Makes the script file executable for the user
./script.sh Executes the script in an isolated subshell
bash script.sh Executes the script via the interpreter (only requires read permissions)
source script.sh Reads the script directly into the current shell
set -euo pipefail Enables Bash Strict Mode for maximum robustness
echo "$?" Shows the exit code of the last terminated program
bash -x script.sh Starts the script in interactive trace mode for debugging
bash -n script.sh Checks the syntax of the script file without execution

Further Resources

Resource Description
GNU Bash Reference Manual The official documentation of the GNU project
ShellCheck Tool Online linter and security checker for shell scripts
Linux Command Line Processor Guide Detailed fundamentals of shell architecture
chmod and File Permissions Detailed permission management under Linux

Conclusion

With creating your first Bash script, understanding the shebang and file permissions, and securing it with Bash Strict Mode, you have laid the foundation for all further steps in Linux automation.

💡 Tip: Use the portable shebang #!/usr/bin/env bash from the start with every new script and enable set -euo pipefail. This prevents unexpected behavior and makes later debugging significantly easier.

In the next lesson, we focus on dynamic data processing and examine how variables are declared, manipulated, and used in scripts: 👉 Next up: Bash Basics #2: Using Variables in Bash

👉 Course overview: All lessons of the Bash basics course

Share & export

Export as Markdown