Bash shell scripting lets you place Linux and Unix commands in a text file and run them as a reusable program. A Bash script can automate file operations, system checks, backups, application startup, text processing, and other command-line tasks.

This tutorial explains what Bash scripting is, how it differs from general shell scripting, and how to create, run, debug, and organize Bash scripts safely.

What Is Bash Shell Scripting?

Bash is a command-line shell and scripting language commonly available on Linux and other Unix-like systems. The name Bash stands for Bourne Again Shell.

When commands are entered directly in a terminal, Bash executes them interactively. When the same commands are saved in a file, Bash can execute them as a script. Bash scripts usually use the .sh filename extension, although the extension is optional.

Bash Scripting and Shell Scripting

Shell scripting is a general term for writing scripts for a command-line shell. Bash scripting refers specifically to scripts interpreted by Bash.

TermMeaning
Shell scriptingScripting for any compatible shell, such as sh, Bash, Zsh, or KornShell.
Bash scriptingScripting that uses Bash syntax and Bash-specific features.

A script written only with portable POSIX shell syntax may run in several shells. A script that uses Bash arrays, [[ ]] tests, brace expansion, or other Bash-specific features should explicitly run with Bash.

Check Whether Bash Is Installed

Open a terminal and run the following command:

</>
Copy
bash --version

You can also locate the Bash executable with:

</>
Copy
command -v bash

A common result is /usr/bin/bash or /bin/bash. The exact path depends on the operating system.

Create and Run Your First Bash Script

Create a file named hello.sh in a text editor:

</>
Copy
nano hello.sh

Add the following Bash code:

</>
Copy
#!/usr/bin/env bash

echo "Hello from Bash"

The first line is called a shebang. It tells the operating system which interpreter should execute the file. Using /usr/bin/env bash asks the environment to locate Bash through the current PATH.

Save the file, make it executable, and run it:

</>
Copy
chmod +x hello.sh
./hello.sh

The result is:

Hello from Bash

You may also run the script by passing it directly to Bash:

</>
Copy
bash hello.sh

Running bash hello.sh does not require execute permission, but running ./hello.sh does.

Bash Comments, Commands, and Exit Status

A line beginning with # is treated as a comment, except when #! appears as the first line of a script.

</>
Copy
#!/usr/bin/env bash

# Display the current date and working directory
date
pwd

Every command returns an exit status. By convention, 0 indicates success, while a nonzero value indicates an error or another unsuccessful condition. The special variable $? contains the exit status of the most recently executed command.

</>
Copy
mkdir reports
echo "Exit status: $?"

Bash Variables and Quoting

Create a variable by assigning a value without spaces around the equals sign:

</>
Copy
name="Asha"
project_directory="project files"

echo "Hello, $name"
echo "Directory: $project_directory"

Double quotes allow variable expansion. Single quotes preserve text literally.

</>
Copy
language="Bash"

echo "Learning $language"
echo 'Learning $language'

The output is:

Learning Bash
Learning $language

Quote variable expansions unless you specifically require word splitting or wildcard expansion. For example, use "$project_directory" when a path may contain spaces.

Read User Input in a Bash Script

The read command accepts input from the terminal:

</>
Copy
#!/usr/bin/env bash

read -r -p "Enter your name: " name
echo "Hello, $name"

The -r option prevents backslashes in the entered text from being treated as escape characters.

Pass Command-Line Arguments to Bash Scripts

Bash provides positional parameters for values passed after the script name:

  • $0 contains the script name.
  • $1, $2, and later variables contain individual arguments.
  • $# contains the number of arguments.
  • "$@" represents all arguments while preserving their boundaries.
</>
Copy
#!/usr/bin/env bash

if [[ $# -lt 1 ]]; then
    echo "Usage: $0 <name>"
    exit 1
fi

echo "Hello, $1"

Run the script with an argument:

</>
Copy
./greet.sh "Ravi Kumar"

Use If Conditions in Bash

An if statement runs commands only when a condition succeeds.

</>
Copy
if condition; then
    commands
elif another_condition; then
    commands
else
    commands
fi

The following script checks whether a directory exists:

</>
Copy
#!/usr/bin/env bash

directory="reports"

if [[ -d "$directory" ]]; then
    echo "$directory exists"
else
    echo "$directory does not exist"
fi

Common file test operators include:

TestMeaning
-e pathThe path exists.
-f pathThe path is a regular file.
-d pathThe path is a directory.
-r pathThe path is readable.
-w pathThe path is writable.
-x pathThe path is executable.
-s pathThe file exists and is not empty.

Compare Strings and Numbers in Bash

Inside Bash conditional expressions, string and numeric comparisons use different operators.

</>
Copy
username="admin"
count=12

if [[ "$username" == "admin" ]]; then
    echo "Administrator account"
fi

if (( count >= 10 )); then
    echo "Count is at least 10"
fi

Use [[ ]] for Bash string and file tests. Use (( )) for arithmetic conditions.

Repeat Commands with Bash Loops

Bash for loop

</>
Copy
for file in *.txt; do
    echo "Found: $file"
done

To process command-line arguments safely, iterate over "$@":

</>
Copy
for argument in "$@"; do
    echo "Argument: $argument"
done

Bash while loop

</>
Copy
count=1

while (( count <= 5 )); do
    echo "$count"
    ((count++))
done

Create Reusable Bash Functions

A function groups related commands under a reusable name:

</>
Copy
#!/usr/bin/env bash

print_message() {
    local message="$1"
    echo "Message: $message"
}

print_message "Backup started"

The local keyword limits a variable to the current function. Function arguments are accessed through $1, $2, and other positional parameters.

Use Command Substitution and Arithmetic

Command substitution stores or embeds a command’s output:

</>
Copy
current_date=$(date +%F)
echo "Today is $current_date"

Arithmetic expansion evaluates integer calculations:

</>
Copy
price=40
quantity=3
total=$((price * quantity))

echo "Total: $total"

Redirect Bash Output and Build Pipelines

Redirection changes where a command reads input or writes output.

OperatorPurpose
>Write standard output to a file, replacing its current contents.
>>Append standard output to a file.
2>Write standard error to a file.
<Read standard input from a file.
|Send one command’s output to another command.
</>
Copy
date > report.txt
echo "Completed" >> report.txt
find /tmp -type f 2> errors.log
printf '%s\n' apple banana avocado | grep '^a'

To redirect both standard output and standard error to the same file in Bash, use:

</>
Copy
some_command > command.log 2>&1

Bash Arrays

Bash supports indexed arrays:

</>
Copy
servers=("web-01" "web-02" "database-01")

echo "First server: ${servers[0]}"

for server in "${servers[@]}"; do
    echo "Checking $server"
done

Use "${array[@]}" to expand all elements while preserving each element as a separate value.

A Practical Bash Backup Script

The following example validates a source directory and creates a compressed archive with a date-based filename:

</>
Copy
#!/usr/bin/env bash

set -euo pipefail

source_directory="${1:-}"
backup_directory="${2:-./backups}"

if [[ -z "$source_directory" ]]; then
    echo "Usage: $0 <source-directory> [backup-directory]" >&2
    exit 1
fi

if [[ ! -d "$source_directory" ]]; then
    echo "Source directory does not exist: $source_directory" >&2
    exit 1
fi

mkdir -p "$backup_directory"

timestamp=$(date +%Y%m%d-%H%M%S)
source_name=$(basename "$source_directory")
archive_path="$backup_directory/${source_name}-${timestamp}.tar.gz"

tar -czf "$archive_path" -C "$(dirname "$source_directory")" "$source_name"

echo "Backup created: $archive_path"

Run it as follows:

</>
Copy
chmod +x backup.sh
./backup.sh "/home/user/project files" "/home/user/backups"

The paths are quoted so that directory names containing spaces are handled correctly.

Handle Errors More Safely in Bash

Many Bash scripts begin with the following options:

</>
Copy
set -euo pipefail
  • -e exits when an unhandled command fails.
  • -u treats references to unset variables as errors.
  • -o pipefail makes a pipeline fail when any command in it fails.

These settings can expose errors early, but they do not replace explicit validation. Some commands legitimately return nonzero statuses, so error behavior should be tested for each script.

You can also define a cleanup function and run it when the script exits:

</>
Copy
temporary_file=$(mktemp)

cleanup() {
    rm -f "$temporary_file"
}

trap cleanup EXIT

Check Bash Script Syntax and Trace Execution

Check a script for syntax errors without executing it:

</>
Copy
bash -n script.sh

Run a script with execution tracing:

</>
Copy
bash -x script.sh

Bash prints expanded commands before executing them. To trace only part of a script, enable and disable tracing within the file:

</>
Copy
set -x
some_command
another_command
set +x

Avoid tracing sections that process passwords, access tokens, or other sensitive values because expanded commands may expose them in terminal output or logs.

Common Bash Scripting Mistakes

  • Adding spaces around variable assignment: write name="Sam", not name = "Sam".
  • Leaving path variables unquoted: use cd "$directory" so spaces and wildcard characters are handled correctly.
  • Using $* instead of "$@": quoted "$@" preserves separate command-line arguments.
  • Assuming a command succeeded: inspect its exit status or place it in an if condition.
  • Parsing ls output: use shell globbing, find, or another tool designed for structured file selection.
  • Using an incorrect interpreter: a script containing Bash-specific syntax should use a Bash shebang and should not be run with sh script.sh.
  • Running scripts as root unnecessarily: use the minimum permissions required for the task.

Useful Bash Commands for Scripts

CommandTypical scripting use
printfProduce formatted and predictable output.
readRead user input or file data.
test or [[ ]]Evaluate file, string, and numeric conditions.
grepSearch text for matching lines.
sedTransform text streams.
awkProcess field-oriented text and reports.
findLocate files by path, type, name, time, or size.
sortSort lines of text.
cutExtract selected fields or character ranges.
teeWrite output to a file and display it at the same time.
getoptsProcess short command-line options.
mktempCreate temporary files or directories safely.

When Bash Scripting Is a Good Choice

Bash is well suited to tasks that primarily coordinate command-line programs, files, processes, environment variables, and operating-system utilities. Typical uses include:

  • Automating repeated terminal commands
  • Creating deployment and build wrappers
  • Checking files, services, disks, and processes
  • Preparing backups and archives
  • Filtering logs and text files
  • Configuring development or server environments
  • Running scheduled maintenance tasks

For large applications, complex data structures, extensive testing requirements, or cross-platform business logic, a general-purpose language such as Python may be easier to maintain. Bash remains useful for connecting commands and managing system-level workflows.

Bash Shell Scripting Best Practices

  • Start Bash-specific scripts with #!/usr/bin/env bash or a verified absolute Bash path.
  • Quote variable expansions, especially variables containing paths or user input.
  • Use "$@" when forwarding all script arguments.
  • Validate required files, directories, commands, and arguments before using them.
  • Write errors to standard error with >&2.
  • Return meaningful exit statuses with exit.
  • Use functions to separate repeated or logically distinct tasks.
  • Use local for variables that belong only to a function.
  • Create temporary files with mktemp and remove them with trap.
  • Run bash -n before executing a changed script.
  • Test scripts with filenames and directory names containing spaces.
  • Use a Bash-aware static analysis tool such as ShellCheck during development when available.

Bash Shell Scripting FAQs

What is Bash shell scripting?

Bash shell scripting is the practice of saving Bash commands, conditions, loops, functions, and variables in a file so they can be executed as a reusable program.

Is Bash scripting the same as shell scripting?

Bash scripting is one type of shell scripting. Shell scripting may target Bash or another shell, while Bash scripting specifically uses the Bash interpreter and may include Bash-only features.

How do I create and run a Bash script?

Create a text file with a Bash shebang, add commands, run chmod +x script.sh, and execute it with ./script.sh. You can also run it directly with bash script.sh.

Why does a Bash script show permission denied?

The file may not have execute permission. Run chmod +x script.sh and then use ./script.sh. Also verify that the current directory or mounted filesystem allows execution.

Should Bash variables always be quoted?

Variable expansions should normally be enclosed in double quotes, such as "$file". Quoting prevents unintended word splitting and wildcard expansion. Arithmetic contexts and a few deliberate expansion cases follow different rules.

Bash Shell Scripting Editorial QA Checklist

  • Confirm that every Bash-specific example uses a Bash shebang.
  • Verify that variable assignments contain no spaces around =.
  • Check that path variables and command-line arguments are quoted correctly.
  • Confirm that output-only examples use the output code class.
  • Test the first script with both bash hello.sh and ./hello.sh.
  • Run bash -n against each complete Bash script.
  • Test examples with a directory name containing spaces, such as project files.
  • Confirm that error examples return a nonzero exit status.
  • Review destructive commands and ensure they do not operate on unvalidated paths.
  • Check that Bash-specific syntax is not described as portable to every shell.