Write Your First Shell Script

Write your first shell script in this Linux · networking · telemetry tutorial — step-by-step guide for developers.

Focus: Write Your First Shell Script

Sponsored

You've been typing the same sequence of commands into your terminal over and over—maybe to check disk usage on a server, restart a service, or parse a log file. Each time you copy-paste from a chat window or scroll through your shell history, you risk typos, wasted time, and inconsistent results. That repetition is the pain this lesson solves: you'll learn to write your first shell script so that a single command can execute a whole series of actions reliably, faster, and with zero memory lapses. By the end, you'll have a working script, a mental model for how it runs, and the confidence to build more complex automation on your path to platform fluency.

The problem this lesson solves

Manual terminal work doesn't scale. Whether you're a backend developer juggling microservices or a DevOps engineer responsible for multiple Linux hosts, repeating the same commands invites human error and wasted effort. Here's what typically goes wrong:

  • You misremember a flag (is it -f or -F?) and get an unexpected output.
  • You forget a step in a multi-command sequence (like checking disk space before deploying).
  • You lose time hunting through history for that one command you used last week.
  • You can't reproduce a workflow reliably for a colleague or a CI pipeline.

A shell script transforms a fragile, memory-dependent process into a single, documented, executable file. It's the difference between giving someone a list of ingredients and giving them a recipe you've already tested. For anyone in the Linux · networking · telemetry track, scripts are also the glue that connects commands: you'll use them to run curl probes, parse JSON responses, and trigger telemetry collection.

Core concept / mental model

Think of a shell script as a list of commands that the shell reads line by line, just as if you'd typed them. That's the whole mental model. There's no magic—no compilation, no new language. You're writing plain text that the Bash interpreter executes sequentially (unless you add logic like loops or conditionals).

Here's a diagram of the flow:

Your script (text file)
        │
        ▼
Shell interpreter (e.g., Bash)
        │
        ▼
Executes commands in order (with inputs/outputs)
        │
        ▼
Returns exit codes & output to your terminal

Key terms to know:

  • Shebang (#!/bin/bash): the first line that tells the system which interpreter to use. Without it, your script might run with the wrong shell or not at all.
  • Executable bit: a file permission that allows the system to run the script like any other program. You set it with chmod +x.
  • Exit status: a number (0 for success, non-zero for error) that the script returns when it finishes. You can use it to detect failures and chain scripts together.

Anchor this idea: a script is just a saved sequence of commands with a bit of structure. Once you hold that, everything else (variables, conditionals, loops) is just layering on top.

How it works step by step

Writing your first shell script is a five-step ritual. Repeat it until it's muscle memory:

  1. Create a file with a .sh extension (optional but conventional), e.g., myfirst.sh.
  2. Add the shebang as the first line: #!/bin/bash. This ensures your script runs with Bash, not another shell.
  3. Write your commands on separate lines. Each line is a command exactly as you'd type it in the terminal.
  4. Make it executable: run chmod +x myfirst.sh in your terminal. This flips the execute bit.
  5. Run it: execute with ./myfirst.sh from the directory (the ./ tells the system to look in the current directory).

Cause → effect: without the shebang, the system may use your default shell (which might be sh or zsh), potentially causing subtle differences. Without the executable bit, ./myfirst.sh fails with "Permission denied." Without ./, the shell won't find your script unless it's in your PATH.

Hands-on walkthrough

Let's build a script that prints system info and a network check—the kind of thing you'll want on any Linux box. Create a file called syscheck.sh with your favorite editor:

#!/bin/bash

# My first script: print system info and ping a host
echo "System info:"
uname -a
echo ""
echo "Disk usage:"
df -h /tmp
echo ""
echo "Ping 8.8.8.8 (4 packets):"
ping -c 4 8.8.8.8

Save it, then make it executable and run it:

chmod +x syscheck.sh
./syscheck.sh

Expected output (trimmed):

System info:
Linux myserver 5.15.0-91-generic #101-Ubuntu SMP ... x86_64 ...

Disk usage:
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   20G   30G  41% /tmp

Ping 8.8.8.8 (4 packets):
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=12.3 ms
...

Now let's make it more practical with variables and an exit status. You'll often want to know if the network check failed. Here's an improved version:

#!/bin/bash

SERVER="8.8.8.8"
PING_COUNT=4

echo "Pinging $SERVER ($PING_COUNT times)..."
ping -c "$PING_COUNT" "$SERVER" > /dev/null 2>&1

if [ $? -eq 0 ]; then
    echo "Success: $SERVER is reachable."
    exit 0
else
    echo "Failure: $SERVER is unreachable." >&2
    exit 1
fi

Run it, then test the failure path by changing SERVER to an unreachable address like 192.0.2.1 (a reserved, non-routable IP) and run again:

./syscheck.sh  # works, exit 0
# Edit SERVER="192.0.2.1" and run again
./syscheck.sh  # fails, exit 1

You'll see the failure message, and echo $? in your terminal will confirm 1. This pattern—checking a command's exit status—is the foundation for robust scripts.

For a third example, here's a script that loops through a list of hosts and pings each one. This mimics a simple health check you might run before deploying:

#!/bin/bash

HOSTS="google.com github.com example.com"

for host in $HOSTS; do
    echo "--- Checking $host ---"
    ping -c 1 "$host" > /dev/null 2>&1 && echo "OK" || echo "FAIL"
done

When you run it, you'll see each host's status on its own line—a tiny telemetry dashboard in your terminal.

Compare options / when to choose what

You might wonder: why not just use bash -c "..." in the terminal, or use a one-liner? For tiny tasks, a one-liner is fine. But for anything you might run more than once, a script is better. Here's a comparison:

Option Use case Pros Cons
Inline command (&& chains) Quick checks, one-off tasks No file needed, fast Hard to read, no persistence
Shell script Repeated tasks, automation, sharing Documented, reusable, versionable Requires file management
Functions (in .bashrc) Interactive shell shortcuts Loaded automatically Less portable across machines
Scripting languages (Python, Perl) Complex logic, data processing More powerful, expressive Heavier, slower startup

For most system administration and telemetry tasks, a Bash script is the sweet spot. It's the lingua franca of Linux—present on virtually every server—and it integrates seamlessly with commands like grep, awk, and curl. Choose a scripting language when you need complex data structures or JSON parsing beyond jq's comfort zone; but for orchestrating commands, Bash wins.

Variations

  • Use #!/usr/bin/env bash instead of #!/bin/bash to allow the system to locate Bash via the PATH—more portable across distributions.
  • Make your script accept arguments with $1, $2, etc., so it's reusable for different hosts or paths.
  • Add set -e near the top to make your script exit immediately on any error, a common practice in production scripts to avoid cascading failures.

Troubleshooting & edge cases

  • Permission denied when running ./script.sh → Use chmod +x script.sh. If the file is on a mounted volume with noexec, move it or run it with bash script.sh (bypasses the executable bit, but the shebang is ignored).
  • command not found → Check the first line: if the shebang points to #!/bin/bash and that path doesn't exist (e.g., in some minimal containers), install Bash or change to #!/bin/sh. Also verify you typed ./ before the filename.
  • Script runs but output is wrong → Use bash -x script.sh to trace each command as it executes. This prints + lines showing expansions, which reveals quoting or variable issues.
  • Variables with spaces → Always quote variables in commands: ping -c "$COUNT" "$HOST". Without quotes, a variable containing spaces (like "my host name") gets split into multiple arguments.
  • Exit status not 0 even though commands succeeded → The script's exit status is that of the last command. If your final echo succeeds, the exit is 0 even if a ping earlier failed. Use exit explicitly to control the status.
  • Windows line endings → If you edit on Windows and transfer, \r\n causes $'\r': command not found. Convert with sed -i 's/\r$//' script.sh or use a Linux editor.

What you learned & what's next

You've now written your first shell script, made it executable, and understood the essentials: the shebang, the executable bit, variables, exit statuses, and basic control flow. These are the same building blocks used in production scripts that monitor servers, parse logs, and trigger telemetry collection—all part of the Linux · networking · telemetry track.

You're ready to move on to the next lesson: shell variables and expansion—where you'll learn how to capture command output into variables ($(command)) and write scripts that adapt to their environment. With that, your scripts will become truly dynamic, and you'll be one step closer to automating your entire platform.

Pro tip: Keep a dedicated ~/bin folder for your scripts and add it to your PATH. Then you can run syscheck from anywhere—just like a native command. This is a small habit that pays off every day.

Practice recap

Create a script called healthcheck.sh that checks disk usage of /tmp and pings 8.8.8.8. If the ping succeeds, print 'Network OK'; otherwise, print 'Network FAIL' and exit with status 1. Run it, then modify it to accept the host as a positional argument ($1) and test with both reachable and unreachable addresses.

Common mistakes

  • Forgetting to make the script executable with chmod +x, then running ./script.sh and getting 'Permission denied'—remember the executable bit is separate from your read/write permissions.
  • Using #!/bin/sh instead of #!/bin/bash when you rely on Bash-specific features like arrays or [[ ]]—you'll get cryptic syntax errors on some systems.
  • Not quoting variables, e.g., ping -c $COUNT $HOST—if a variable contains spaces or is empty, the command may behave unexpectedly or throw errors.
  • Assuming the script's exit code is 0 even if a command fails because the script doesn't use set -e or an explicit exit—the exit status is always from the last command run.
  • Using Windows-style line endings after editing on Windows or Mac—you'll see \r errors; run sed -i 's/\r$//' script.sh to fix.

Variations

  1. Use #!/usr/bin/env bash as shebang for better portability across Unix-like systems where Bash lives in different paths.
  2. Make your script accept positional parameters ($1, $2) so you can pass hosts or filenames without editing the file each time.
  3. Add set -e near the top to cause the script to exit immediately on any failing command—a common practice for production scripts to avoid cascading errors.

Real-world use cases

  • A script that performs a series of system health checks (disk, memory, network) and emails a report—scheduled via cron.
  • A deployment helper that stops a service, pulls new code, runs tests, and restarts it—ensuring a consistent, repeatable process.
  • A telemetry collector that pings multiple endpoints, parses response with curl and jq, and writes metrics to a log file.

Key takeaways

  • A shell script is just a sequence of commands saved in a file—the shell executes it line by line.
  • The shebang (#!/bin/bash) selects the interpreter, and the executable bit (chmod +x) makes it runnable.
  • Use variables, conditionals, and exit statuses to build robust, dynamic scripts.
  • Always quote variables and test edge cases (missing args, unreachable hosts) to avoid silent failures.
  • The exit code of the last command determines the script's exit code unless you explicitly exit.
  • Scripts are the foundation for automating Linux, networking, and telemetry tasks—master them to advance in the track.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.