thecodingidiot.com

The TerminalWriting Your First Script

Writing Your First Script

Everything you have typed at the prompt so far — commands, pipes, redirects, variable assignments — can be written into a file and run as a program. That is a shell script. It is not a different language. It is the same shell, reading from a file instead of your keyboard.

The shebang

#!/bin/bash

The first line of every shell script is the shebang[1] (also called hashbang). It tells the kernel which interpreter to use when the file is executed. /bin/bash is the path to Bash.

Without it, the shell falls back to running the file with /bin/sh — the system's default shell, which might be dash or another minimal POSIX shell, not Bash. If your script uses any Bash-specific syntax, it will behave differently or fail outright. The shebang is how you guarantee which interpreter runs the file.

Create a script:

cat > greet.sh << 'EOF'
#!/bin/bash
echo "hello from a script"
EOF

Make it executable and run it:

chmod +x greet.sh
./greet.sh
hello from a script

Variables

#!/bin/bash
NAME="terminal"
echo "welcome to the $NAME"

Variables have no spaces around =. $NAME expands to the value. Double quotes allow expansion; single quotes suppress it:

echo "$NAME"    # prints: terminal
echo '$NAME'    # prints: $NAME

Arguments

When you call a script with arguments, Bash populates special variables:

#!/bin/bash
echo "script name: $0"
echo "first arg:   $1"
echo "second arg:  $2"
echo "all args:    $@"
echo "arg count:   $#"
./greet.sh foo bar
script name: ./greet.sh
first arg:   foo
second arg:  bar
all args:    foo bar
arg count:   2

Conditionals

#!/bin/bash
if [[ -f "$1" ]]; then
    echo "$1 is a regular file"
elif [[ -d "$1" ]]; then
    echo "$1 is a directory"
else
    echo "$1 does not exist"
fi

[[ ... ]] is the Bash compound test. Common tests:

[[ -f file ]]       # file exists and is a regular file
[[ -d dir ]]        # dir exists and is a directory
[[ -z "$var" ]]     # var is empty
[[ -n "$var" ]]     # var is non-empty
[[ "$a" = "$b" ]]   # strings are equal
[[ "$a" -eq "$b" ]] # integers are equal
[[ "$a" -gt "$b" ]] # integer a > b

Always quote variables inside [[ ]] to avoid errors when they are empty — including integer tests.

In Bash scripts, use [[ ]] for all tests. It avoids word-splitting and supports pattern matching and regex. [ ] is the POSIX test command — it works in any sh script, but Bash scripts should use [[ ]] consistently.

Loops

for iterates over a list. Each item is assigned to the loop variable in turn, and the body runs once per item:

#!/bin/bash
for file in *.c; do
    echo "found: $file"
done

The glob *.c expands to all .c files in the current directory before the loop starts. The loop runs once per file. If there are no .c files, it does not run at all.

To iterate over a range of numbers, use brace expansion:

for i in {1..5}; do
    echo "step $i"
done

{1..5} expands to 1 2 3 4 5 before the loop runs. The shell handles the expansion — for just sees a list of five items.

while runs as long as a condition is true. Use it when you do not know the number of iterations in advance, or when you are counting yourself:

count=0
while [[ "$count" -lt 10 ]]; do
    echo "$count"
    count=$((count + 1))
done

count=0 initialises the variable before the loop. [ "$count" -lt 10 ] is the condition — less than 10. $(( ... )) performs integer arithmetic: $((count + 1)) adds one to count and assigns it back. Without that line, the condition never becomes false and the loop runs forever.

Exit codes

#!/bin/bash
set -o pipefail
if [[ "$#" -eq 0 ]]; then
    echo "usage: $0 <directory>" >&2
    exit 1
fi
 
count=$(find "$1" -name '*.c' -type f | wc -l)
echo "$count C files in $1"
exit 0

exit 0 means success. exit 1 (or any non-zero) means failure. The >&2 redirects the error message to stderr — the convention for usage errors and diagnostics.

Save this as count_c_files.sh, chmod +x it, and run it against the directories you will create in the C chapters:

./count_c_files.sh ~/Development

This is the shape of every tester in the companion repo. A script that takes arguments, checks preconditions, runs tests, and exits with a code that reflects the result.

Footnotes

  1. Shebang (Unix) - Wikipedia