DEV Community

Cover image for Terminal Mastery-Automate Your Workflow with Shell Scripting
Hamed Farazi
Hamed Farazi

Posted on

Terminal Mastery-Automate Your Workflow with Shell Scripting

09/10 -Automate Your Workflow with Shell Scripting
You've learned how to navigate directories, search files, manage processes, troubleshoot networks, and use powerful command-line tools.

But there's another level of terminal mastery:

What if you could turn a sequence of commands into a reusable program?

Instead of typing the same commands every morning, you could run one script. Instead of manually creating project folders, you could generate them automatically. Instead of copying files one by one, you could build a backup workflow that does the job for you.

That's what shell scripting is all about.

In this article, we'll learn how to write practical scripts for:

  • Linux and macOS using Bash
  • Windows using PowerShell
  • Variables, arguments, conditions, loops, and functions
  • Error handling and exit codes
  • Automating real developer workflows
  • Debugging and maintaining scripts

By the end, you'll have the knowledge to automate repetitive tasks across all three operating systems.


1. What Is a Shell Script?

A shell script is a file containing commands that a shell executes in sequence.

Think about a task you perform regularly:

  1. Create a project directory.
  2. Create src, tests, and docs folders.
  3. Add a README file.
  4. Create a .gitignore file.
  5. Print a success message.

You could execute every command manually.

Or you could save those commands in a file and run that file whenever you need the same setup.

That's a script.

Commands vs. scripts

A command is usually a single instruction:

mkdir my-project
Enter fullscreen mode Exit fullscreen mode

A script can combine multiple instructions, variables, conditions, loops, and functions:

#!/usr/bin/env bash

mkdir -p my-project/src
mkdir -p my-project/tests
mkdir -p my-project/docs

echo "Project structure created!"
Enter fullscreen mode Exit fullscreen mode

The important difference is reusability.

You write the workflow once and execute it whenever you need it.

2. Bash vs. PowerShell

Before writing scripts, let's understand the two environments we'll use.

Feature Bash PowerShell
Common platforms Linux, macOS Windows, Linux, macOS
Typical extension .sh .ps1
Common interactive shell Bash / Zsh PowerShell
Variables $name $name
Comments # #
Main pipeline model Text streams Objects
Common script command bash script.sh .\script.ps1

Bash is widely used on Linux servers and macOS development machines.

PowerShell is the primary scripting environment in modern Windows administration, but it also runs on Linux and macOS.

You don't need to learn both at once. Start with the environment you use most often, then learn the other.

3. Creating Your First Script

Let's create a simple script that prints a message.

Bash

Create a file named hello.sh:

#!/usr/bin/env bash

echo "Hello from Bash!"
echo "Welcome to shell scripting."
Enter fullscreen mode Exit fullscreen mode

The first line is called a shebang:

#!/usr/bin/env bash
Enter fullscreen mode Exit fullscreen mode

It tells the operating system which interpreter should execute the script when you run it directly.

Run it using:

bash hello.sh
Enter fullscreen mode Exit fullscreen mode

On Linux and macOS, you can also make it executable:

chmod +x hello.sh
Enter fullscreen mode Exit fullscreen mode

Then run:

./hello.sh
Enter fullscreen mode Exit fullscreen mode

PowerShell

Create a file named hello.ps1:

Write-Host "Hello from PowerShell!"
Write-Host "Welcome to shell scripting."
Enter fullscreen mode Exit fullscreen mode

Run it from PowerShell:

.\hello.ps1
Enter fullscreen mode Exit fullscreen mode

Notice the .\ prefix.

It tells PowerShell to execute a script from the current directory.

A note about PowerShell execution policies

You might encounter an error saying that script execution is disabled.

Check your current policies with:

Get-ExecutionPolicy -List
Enter fullscreen mode Exit fullscreen mode

PowerShell execution policies control the conditions under which scripts can run. They are not a complete security boundary.

On managed computers, follow your organization's policy rather than changing it yourself. Avoid setting the policy to Unrestricted as a general workaround.

If you are using a personal machine, understand the scope and implications of any policy change before making one.

4. Variables: Store and Reuse Values

Variables let you store information and use it throughout a script.

Bash

#!/usr/bin/env bash

name="Hamed"
language="Bash"

echo "Hello, $name!"
echo "You are learning $language."
Enter fullscreen mode Exit fullscreen mode

Important: Bash does not allow spaces around the assignment operator.

Correct:

name="Hamed"
Enter fullscreen mode Exit fullscreen mode

Incorrect:

name = "Hamed"
Enter fullscreen mode Exit fullscreen mode

PowerShell

$name = "Hamed"
$language = "PowerShell"

Write-Host "Hello, $name!"
Write-Host "You are learning $language."
Enter fullscreen mode Exit fullscreen mode

PowerShell variable names begin with $.

Why variables matter

Imagine writing a backup script that copies a project directory.

Without variables, you might hardcode the same directory in several places.

With variables, you define it once:

project_dir="/home/user/my-project"
Enter fullscreen mode Exit fullscreen mode

Then reuse it:

echo "Backing up $project_dir"
Enter fullscreen mode Exit fullscreen mode

This makes scripts easier to update and maintain.

5. Getting Input from the User

Scripts become more flexible when users can provide information.

Bash: read

#!/usr/bin/env bash

read -r -p "What is your name? " name

echo "Hello, $name!"
Enter fullscreen mode Exit fullscreen mode

The read command waits for user input and stores it in a variable.

The -r option prevents backslashes from being interpreted as escape characters.

PowerShell: Read-Host

$name = Read-Host "What is your name?"

Write-Host "Hello, $name!"
Enter fullscreen mode Exit fullscreen mode

Both scripts ask a question and use the answer.

This is useful when building setup scripts, interactive tools, and small automation utilities.

6. Passing Arguments to Scripts

Sometimes you don't want a script to ask questions.

You want to provide information when executing it.

For example:

bash greet.sh Hamed
Enter fullscreen mode Exit fullscreen mode

Bash arguments

Inside a Bash script:

Variable Meaning
$0 Script name
$1 First argument
$2 Second argument
$@ All arguments
$# Number of arguments

Example:

#!/usr/bin/env bash

name="${1:-Developer}"

echo "Hello, $name!"
Enter fullscreen mode Exit fullscreen mode

Run it:

bash greet.sh Hamed
Enter fullscreen mode Exit fullscreen mode

Output:

Hello, Hamed!
Enter fullscreen mode Exit fullscreen mode

If no argument is provided, the script uses Developer.

One important rule: quote arguments when passing them to other commands.

some_command "$@"
Enter fullscreen mode Exit fullscreen mode

This preserves arguments containing spaces and prevents unwanted word splitting.

PowerShell arguments

PowerShell supports a param block:

param(
    [string]$Name = "Developer",
    [int]$Age = 25
)

Write-Host "Name: $Name"
Write-Host "Age: $Age"
Enter fullscreen mode Exit fullscreen mode

Run it:

.\greet.ps1 -Name "Hamed" -Age 30
Enter fullscreen mode Exit fullscreen mode

Named parameters make PowerShell scripts easier to understand and maintain.

For reusable scripts, prefer param() over manually reading values from $args.

7. Conditions: Make Decisions in Your Scripts

A script should sometimes behave differently depending on the situation.

For example:

  • Does a directory exist?
  • Was a required argument provided?
  • Is a file empty?
  • Did a command succeed?

That's where conditions come in.

Bash: if

#!/usr/bin/env bash

name="${1:-}"

if [[ -z "$name" ]]; then
    echo "Please provide a name."
else
    echo "Hello, $name!"
fi
Enter fullscreen mode Exit fullscreen mode

Here, -z checks whether a string is empty.

Some useful Bash comparisons:

Operator Meaning
-z String is empty
-n String is not empty
== Strings are equal
-eq Numbers are equal
-gt Greater than
-lt Less than
-f Regular file exists
-d Directory exists

Example:

age=20

if [[ "$age" -ge 18 ]]; then
    echo "You are an adult."
else
    echo "You are under 18."
fi
Enter fullscreen mode Exit fullscreen mode

PowerShell: if, elseif, and else

$age = 20

if ($age -ge 18) {
    Write-Host "You are an adult."
}
elseif ($age -ge 13) {
    Write-Host "You are a teenager."
}
else {
    Write-Host "You are a child."
}
Enter fullscreen mode Exit fullscreen mode

Some useful PowerShell comparison operators:

Operator Meaning
-eq Equal
-ne Not equal
-gt Greater than
-lt Less than
-ge Greater than or equal
-le Less than or equal
-like Wildcard matching
-match Regular expression matching

Unlike many programming languages, PowerShell uses comparison operators such as -eq and -gt.

8. Loops: Repeat Tasks Automatically

Loops are especially useful when processing multiple files or directories.

Imagine you have 100 text files and want to perform the same operation on each one.

You don't need to write the command 100 times.

Bash: for

#!/usr/bin/env bash

for name in Alice Bob Charlie; do
    echo "Hello, $name!"
done
Enter fullscreen mode Exit fullscreen mode

You can also loop through files:

for file in *.txt; do
    echo "Processing: $file"
done
Enter fullscreen mode Exit fullscreen mode

This example uses filename expansion.

One detail: if no files match *.txt, Bash normally leaves the pattern unchanged. For scripts where that matters, consider enabling nullglob or checking whether the file exists before processing it.

Bash: while

counter=1

while [[ "$counter" -le 5 ]]; do
    echo "Count: $counter"
    ((counter++))
done
Enter fullscreen mode Exit fullscreen mode

PowerShell: foreach

$names = @("Alice", "Bob", "Charlie")

foreach ($name in $names) {
    Write-Host "Hello, $name!"
}
Enter fullscreen mode Exit fullscreen mode

PowerShell: for

for ($i = 1; $i -le 5; $i++) {
    Write-Host "Count: $i"
}
Enter fullscreen mode Exit fullscreen mode

PowerShell: while

$counter = 1

while ($counter -le 5) {
    Write-Host "Count: $counter"
    $counter++
}
Enter fullscreen mode Exit fullscreen mode

Loops are the foundation of file processing, batch operations, and repetitive automation.

9. Functions: Organize Your Scripts

As scripts become larger, repeating the same code makes them harder to maintain.

Functions solve this problem.

Bash functions

#!/usr/bin/env bash

greet() {
    local name="$1"

    echo "Hello, $name!"
}

greet "Hamed"
greet "Developer"
Enter fullscreen mode Exit fullscreen mode

The local keyword keeps the variable inside the function's scope.

A useful Bash detail: return is primarily used to return an exit status, not a string value. To produce text from a function, use standard output.

For example:

get_greeting() {
    printf 'Hello, %s!\n' "$1"
}

message="$(get_greeting "Hamed")"

echo "$message"
Enter fullscreen mode Exit fullscreen mode

PowerShell functions

function Get-Greeting {
    param(
        [string]$Name
    )

    "Hello, $Name!"
}

$message = Get-Greeting -Name "Hamed"

Write-Host $message
Enter fullscreen mode Exit fullscreen mode

PowerShell functions can emit objects through the pipeline. Their output is not limited to strings.

This is one of the major differences between Bash and PowerShell: PowerShell is designed around structured objects rather than treating everything as plain text.

10. Exit Codes: Know Whether a Command Succeeded

Automation isn't just about running commands.

It's also about knowing whether they worked.

Most command-line programs communicate success or failure through an exit code.

A common convention is:

  • 0: Success
  • Non-zero: Some kind of failure

Bash

The special variable $? contains the exit status of the most recently executed command.

mkdir test-folder

if [[ "$?" -eq 0 ]]; then
    echo "Directory created successfully."
else
    echo "Something went wrong."
fi
Enter fullscreen mode Exit fullscreen mode

A more reliable approach is to use the command directly in a condition:

if mkdir test-folder; then
    echo "Directory created successfully."
else
    echo "Could not create directory."
fi
Enter fullscreen mode Exit fullscreen mode

You can also explicitly exit a script:

exit 0
Enter fullscreen mode Exit fullscreen mode

Or indicate failure:

exit 1
Enter fullscreen mode Exit fullscreen mode

PowerShell

PowerShell has two important variables:

  • $?: Indicates whether the most recent PowerShell operation succeeded.
  • $LASTEXITCODE: Contains the exit code returned by the last native executable.

For example:

node --version

if ($LASTEXITCODE -eq 0) {
    Write-Host "Node.js is available."
}
else {
    Write-Host "Node.js command failed."
}
Enter fullscreen mode Exit fullscreen mode

Don't confuse $? with $LASTEXITCODE. When working with external programs such as node, git, or npm, check $LASTEXITCODE when you need the native program's actual exit code.

11. Error Handling: Make Scripts More Reliable

A script that works only when everything goes perfectly isn't very useful.

Real automation needs to handle unexpected situations.

Bash: safer execution

You'll often see this near the beginning of Bash scripts:

set -Eeuo pipefail
Enter fullscreen mode Exit fullscreen mode

Here's what the options generally mean:

  • -e: Exit when an eligible command fails.
  • -E: Allow ERR traps to be inherited by functions and certain command contexts.
  • -u: Treat unset variables as errors.
  • -o pipefail: Make a pipeline fail if a command within it fails, rather than only considering the last command.

These options improve reliability, but they aren't a complete error-handling system. In particular, -e has exceptions and context-dependent behavior. Explicitly check important operations.

For example:

#!/usr/bin/env bash

set -Eeuo pipefail

if ! mkdir -p "my-project"; then
    echo "Could not create project directory." >&2
    exit 1
fi

echo "Project directory is ready."
Enter fullscreen mode Exit fullscreen mode

The >&2 redirects the error message to standard error.

PowerShell: try and catch

PowerShell provides structured exception handling:

$ErrorActionPreference = "Stop"

try {
    New-Item -ItemType Directory -Path "my-project"

    Write-Host "Project directory created."
}
catch {
    Write-Error "Something went wrong: $_"
}
finally {
    Write-Host "Operation finished."
}
Enter fullscreen mode Exit fullscreen mode

The three main blocks:

  • try: Code that might fail.
  • catch: Handles terminating errors.
  • finally: Runs whether an error occurs or not.

Setting $ErrorActionPreference to Stop makes many otherwise non-terminating PowerShell errors catchable.

However, native programs have their own exit-code behavior. Check $LASTEXITCODE separately when running external executables.

12. Practical Project: Automatically Create a Project Structure

Let's put our knowledge into practice.

Suppose you frequently create small development projects and want a standard directory structure.

Every project should contain:

my-project/
├── src/
├── tests/
├── docs/
├── README.md
└── .gitignore
Enter fullscreen mode Exit fullscreen mode

We'll create one script for Bash and another for PowerShell.

Bash version

Create create-project.sh:

#!/usr/bin/env bash

set -Eeuo pipefail

PROJECT_NAME="${1:-my-project}"

if [[ -e "$PROJECT_NAME" ]]; then
    echo "Error: '$PROJECT_NAME' already exists." >&2
    exit 1
fi

mkdir -p "$PROJECT_NAME"/{src,tests,docs}

cd "$PROJECT_NAME"

cat > README.md <<EOF
# $PROJECT_NAME

Project documentation.
EOF

cat > .gitignore <<'EOF'
node_modules/
dist/
.env
EOF

echo "Project '$PROJECT_NAME' created successfully!"
Enter fullscreen mode Exit fullscreen mode

Make it executable:

chmod +x create-project.sh
Enter fullscreen mode Exit fullscreen mode

Run it:

./create-project.sh my-awesome-app
Enter fullscreen mode Exit fullscreen mode

PowerShell version

Create Create-Project.ps1:

param(
    [string]$ProjectName = "my-project"
)

$ErrorActionPreference = "Stop"

if (Test-Path -LiteralPath $ProjectName) {
    throw "The project '$ProjectName' already exists."
}

New-Item -ItemType Directory -Path $ProjectName | Out-Null

$directories = @("src", "tests", "docs")

foreach ($directory in $directories) {
    New-Item -ItemType Directory `
        -Path (Join-Path $ProjectName $directory) |
        Out-Null
}

$readme = "# $ProjectName`n`nProject documentation."

$gitignore = @"
node_modules/
dist/
.env
"@

Set-Content `
    -Path (Join-Path $ProjectName "README.md") `
    -Value $readme

Set-Content `
    -Path (Join-Path $ProjectName ".gitignore") `
    -Value $gitignore

Write-Host "Project '$ProjectName' created successfully!"
Enter fullscreen mode Exit fullscreen mode

Run it:

.\Create-Project.ps1 -ProjectName "my-awesome-app"
Enter fullscreen mode Exit fullscreen mode

Both scripts create the same basic structure.

That's the beauty of automation: different shells, same outcome.

13. Practical Project: Create a Backup Script

Let's build something more useful.

Imagine you have a project directory that you want to back up regularly.

Instead of manually copying it, we'll create a script that:

  1. Checks whether the source directory exists.
  2. Creates a backup directory if necessary.
  3. Adds a timestamp to the backup name.
  4. Copies the source directory.
  5. Prints the backup location.

Bash backup script

Create backup.sh:

#!/usr/bin/env bash

set -Eeuo pipefail

SOURCE="${1:-}"
BACKUP_ROOT="${2:-$HOME/backups}"

if [[ -z "$SOURCE" ]]; then
    echo "Usage: ./backup.sh <source-directory> [backup-directory]" >&2
    exit 1
fi

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

SOURCE="$(cd -- "$SOURCE" && pwd)"

mkdir -p "$BACKUP_ROOT"

TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
PROJECT_NAME="$(basename "$SOURCE")"

DESTINATION="$BACKUP_ROOT/${PROJECT_NAME}-${TIMESTAMP}"

cp -a "$SOURCE" "$DESTINATION"

echo "Backup completed successfully!"
echo "Saved to: $DESTINATION"
Enter fullscreen mode Exit fullscreen mode

Run it:

chmod +x backup.sh

./backup.sh ~/projects/my-app
Enter fullscreen mode Exit fullscreen mode

Or provide a custom backup location:

./backup.sh ~/projects/my-app ~/external-backups
Enter fullscreen mode Exit fullscreen mode

The cp -a option preserves important file attributes where supported and copies directories recursively.

PowerShell backup script

Create Backup-Project.ps1:

param(
    [Parameter(Mandatory = $true)]
    [string]$Source,

    [string]$BackupRoot = (Join-Path $HOME "backups")
)

$ErrorActionPreference = "Stop"

if (-not (Test-Path -LiteralPath $Source -PathType Container)) {
    throw "Source directory does not exist: $Source"
}

$Source = (Resolve-Path -LiteralPath $Source).Path

if (-not (Test-Path -LiteralPath $BackupRoot)) {
    New-Item -ItemType Directory -Path $BackupRoot |
        Out-Null
}

$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"

$projectName = Split-Path -Path $Source -Leaf

$destination = Join-Path `
    $BackupRoot `
    "$projectName-$timestamp"

Copy-Item `
    -LiteralPath $Source `
    -Destination $destination `
    -Recurse

Write-Host "Backup completed successfully!"
Write-Host "Saved to: $destination"
Enter fullscreen mode Exit fullscreen mode

Run it:

.\Backup-Project.ps1 -Source "C:\Projects\My App"
Enter fullscreen mode Exit fullscreen mode

Or specify another backup directory:

.\Backup-Project.ps1 `
    -Source "C:\Projects\My App" `
    -BackupRoot "D:\Backups"
Enter fullscreen mode Exit fullscreen mode

Notice how we quote paths containing spaces.

This is an important habit in shell scripting. Paths are not always simple strings without spaces.

Important: These examples create new timestamped backups. They don't automatically delete older backups. That's intentional: deleting backups is a destructive operation and should be added only with a clear retention policy and careful testing.

14. Script Paths: Don't Depend on the Current Directory

Here's a common scripting mistake.

You write a script that reads a file:

cat config.json
Enter fullscreen mode Exit fullscreen mode

It works when you run the script from its own directory.

But when you run it from somewhere else, it fails.

Why?

Because the relative path is resolved from the current working directory, not necessarily from the script's location.

Bash: find the script directory

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

CONFIG_FILE="$SCRIPT_DIR/config.json"

cat "$CONFIG_FILE"
Enter fullscreen mode Exit fullscreen mode

Now the script can locate config.json relative to itself.

PowerShell: use $PSScriptRoot

$configFile = Join-Path $PSScriptRoot "config.json"

Get-Content $configFile
Enter fullscreen mode Exit fullscreen mode

$PSScriptRoot contains the directory where the current script is stored.

This is especially useful when distributing scripts to other developers or running them through automation tools.

15. Debugging Your Scripts

Scripts don't always work on the first attempt.

The ability to debug them is just as important as the ability to write them.

Bash debugging

Run a script with execution tracing:

bash -x script.sh
Enter fullscreen mode Exit fullscreen mode

Bash prints commands as they execute.

For example:

#!/usr/bin/env bash

name="Hamed"

echo "Hello, $name!"
Enter fullscreen mode Exit fullscreen mode

Running it with bash -x shows the expanded commands and helps you understand what happened.

PowerShell debugging

Enable tracing:

Set-PSDebug -Trace 1

.\script.ps1

Set-PSDebug -Off
Enter fullscreen mode Exit fullscreen mode

PowerShell displays additional execution information.

Remember to turn tracing off after debugging.

Common scripting mistakes

Problem Possible cause
Command not found Tool isn't installed or isn't in PATH
Permission denied Missing execute permission or insufficient access
File not found Incorrect working directory or path
Unexpected variable value Incorrect quoting or variable expansion
Script works manually but fails elsewhere Environment or working-directory differences
PowerShell script won't run Execution policy or script invocation issue
Pipeline reports success unexpectedly Exit code or pipeline error handling isn't being checked

When debugging, don't immediately assume the shell is broken.

Check the command, its arguments, the current directory, permissions, and environment first.

16. Best Practices for Writing Maintainable Scripts

A script is still software.

Treat it with the same care you would give to an application.

1. Use meaningful names

Avoid:

x="my-project"
Enter fullscreen mode Exit fullscreen mode

Prefer:

project_name="my-project"
Enter fullscreen mode Exit fullscreen mode

2. Quote paths and variables

Bash:

mkdir -p "$project_name"
Enter fullscreen mode Exit fullscreen mode

PowerShell:

New-Item -ItemType Directory -Path $projectName
Enter fullscreen mode Exit fullscreen mode

Quoting and using literal-path parameters appropriately helps avoid problems with spaces and special characters.

3. Validate input

Don't assume users will always provide the correct arguments.

Check required values and verify that files or directories exist before using them.

4. Avoid hardcoding secrets

Never store passwords, API keys, or access tokens directly in scripts that might be committed to Git.

Use environment variables or an appropriate secret-management solution.

5. Make failures visible

Print useful error messages and return meaningful exit codes.

A script should tell the user what went wrong, not silently continue.

6. Avoid destructive operations by default

Be especially careful with commands such as:

rm -rf
Enter fullscreen mode Exit fullscreen mode

And their PowerShell equivalents:

Remove-Item -Recurse -Force
Enter fullscreen mode Exit fullscreen mode

Test scripts with temporary directories and sample files before using them on important data.

7. Keep scripts focused

A script that does one thing well is usually easier to understand, test, and reuse than one enormous script that handles everything.


17. Practice: Build Your Own Automation Scripts

Now it's your turn.

Try these exercises without looking at the examples above.

Exercise 1: Personal Greeting

Create a script that:

  • Accepts a user's name.
  • Accepts their programming language.
  • Prints a personalized message.
  • Uses default values if arguments are missing.

Exercise 2: File Organizer

Create a script that:

  • Accepts a directory path.
  • Finds all .jpg files.
  • Creates an images directory.
  • Moves the matching files into that directory.

Test it on a temporary folder first.

Exercise 3: Project Generator

Improve the project generator from this article.

Add:

  • A choice between JavaScript and TypeScript.
  • A src/components directory.
  • A src/utils directory.
  • A tests/unit directory.
  • A generated README containing the project name and creation date.

Exercise 4: Automated Backup

Improve the backup script.

Add:

  • A check that the destination is writable.
  • A log file containing backup results.
  • A clear error message if copying fails.
  • A way to choose a custom backup location.

Exercise 5: Cross-Platform Challenge

Write the same small automation task twice:

  • Once in Bash.
  • Once in PowerShell.

Compare the two implementations.

Which language feels more natural? Which one handles structured data more conveniently? What differences did you notice?


18. Quick Reference: Bash vs. PowerShell

Task Bash PowerShell
Print text echo "Hello" Write-Host "Hello"
Create variable name="Hamed" $name = "Hamed"
Read input read -r name Read-Host
First argument $1 param()
Condition if [[ ... ]] if (...)
Loop for ...; do foreach (...)
Function greet() { } function Greet { }
Create directory mkdir -p app New-Item -ItemType Directory app
Check file [[ -f file ]] Test-Path file -PathType Leaf
Copy directory cp -a source destination Copy-Item source destination -Recurse
Exit code $? $LASTEXITCODE for native programs
Debug bash -x script.sh Set-PSDebug -Trace 1

Conclusion

You've now moved beyond executing individual terminal commands.

You know how to:

  • Create reusable shell scripts.
  • Work with variables and arguments.
  • Accept user input.
  • Make decisions with conditions.
  • Repeat operations with loops.
  • Organize code using functions.
  • Handle failures and exit codes.
  • Automate project creation and backups.
  • Debug scripts across different environments.

The real value of scripting isn't just saving a few keystrokes.

It's turning repetitive work into reliable, reusable workflows.

And once you start automating your everyday tasks, you'll notice that many terminal operations can become much simpler.

Next up:

In Article 10, we'll bring everything together with advanced terminal workflows and troubleshooting. We'll explore how to combine commands, investigate difficult problems, improve productivity, and build a more professional command-line workflow across Windows, macOS, and Linux.

See you in the final chapter!


Tags: terminal automation bash powershell

Series: Terminal Mastery: Windows, macOS & Linux

Reading level: Beginner to Intermediate

Top comments (0)