Unit 10: Shell programming

CSE105 — Creative Engineering Workshop 5 min read

The shell is the command interpreter that sits between the user and the Unix/Linux kernel, reading commands and dispatching them for execution. Shell programming (or scripting) collects those commands into a text file so that repetitive or complex tasks run automatically. This unit uses the Bourne-Again Shell (bash, released 1989), the default on most Linux systems and a superset of the original Bourne shell (sh, 1979).

Defining properties the rest of the unit relies on:

  • Interpreted, not compiled: Each line is read and executed in sequence; there is no build step. Errors surface at runtime.
  • Everything is a string: Variables are untyped text by default (x=5 stores the characters 5); arithmetic needs explicit constructs like $(( )).
  • Whitespace is significant: x=5 assigns, but x = 5 tries to run a command x. No spaces around =.
  • Exit status: Every command returns an integer status ($?); 0 means success, non-zero means failure. Conditionals test this, not boolean values.
  • Shebang convention: The first line #!/bin/bash names the interpreter.

II. Structure and Execution of Scripts

How a script is laid out, made runnable, and fed input.

A. Structure of Shell Scripts

A script is an ordered text file with a defined skeleton.

  • Shebang line: #!/bin/bash on line 1 tells the kernel which interpreter to use.
  • Comments: Any text after # (except the shebang) is ignored, used for documentation.
  • Body: Variable assignments, commands, and control structures execute top to bottom.
    BASH
    #!/bin/bash
    # greet.sh - prints a greeting
    name="World"          # variable assignment
    echo "Hello, $name"   # command using the variable

B. Creating and Executing Scripts

A script must be created as a file and given permission before it runs.

  • Create: Write the file in any editor (nano greet.sh).
  • Make executable: chmod +x greet.sh sets the execute bit.
  • Execute — three ways:
    1. ./greet.sh — runs in a new subshell; needs the execute bit and shebang.
    2. bash greet.sh — passes the file to bash explicitly; no execute bit needed.
    3. source greet.sh (or . greet.sh) — runs in the current shell, so variable changes persist.

C. Interactive Shell Scripts

Interactive scripts pause to collect input from the user at runtime.

  • read command: Reads a line from standard input into a variable: read name.
  • Prompting: read -p "Enter name: " name prints a prompt on the same line.
  • Silent input: read -s pass hides typing, used for passwords.
    BASH
    read -p "Age: " age
    echo "Next year you will be $((age + 1))"

D. Command-Line Arguments

Arguments let a script receive data at invocation without prompting.

  • Positional parameters: $1, $2, … hold the first, second, … arguments; $0 is the script name.
  • $#: Count of arguments passed.
  • $@ and $*: All arguments; "$@" preserves each as a separate quoted word.
    BASH
    # run as: ./add.sh 3 4
    echo "$# args"          # 2 args
    echo $(( $1 + $2 ))     # 7

III. Operators and Testing

The expressions that produce the true/false and numeric values conditionals depend on.

A. Operators in Shell Scripting

Operators fall into distinct families because the shell treats text and numbers differently.

  • Arithmetic: + - * / % evaluated inside $(( )) or expr: $(( 7 % 3 )) gives 1.
  • Relational (numeric): -eq -ne -lt -le -gt -ge compare integers inside test brackets.
  • String: = (equal), != (not equal), -z (empty), -n (non-empty).
  • Logical: && (and), || (or), ! (not) combine command results by exit status.
  • Assignment: = binds a value to a variable name.

B. test command and []

test and its synonym [ evaluate a conditional expression and return an exit status.

  • Equivalence: test $a -gt $b is identical to [ $a -gt $b ]; the [ form requires a closing ] and spaces around every token.
  • File tests: -f (regular file exists), -d (directory), -e (exists), -r/-w/-x (readable/writable/executable).
  • Return value, not output: [ 5 -gt 3 ] prints nothing but sets $? to 0.
    BASH
    [ -f /etc/passwd ] && echo "file present"
  • [[ ]] extension: bash's own keyword adds pattern matching and safer handling of unquoted variables.

IV. Decision-Making Constructs

Branching statements that act on the exit status produced above.

A. if

if runs a block only when its test command succeeds (exit status 0).

  • Syntax: the then block runs on success; fi closes the statement.
    BASH
    if [ $marks -ge 40 ]; then
      echo "Pass"
    fi
  • Note the semicolon: ; then (or then on a new line) is required.

B. if-else

if-else provides an alternative block when the test fails.

  • Two-way branch: exactly one of the two blocks runs.
  • elif: chains additional conditions between if and else.
    BASH
    if [ $n -gt 0 ]; then echo "positive"
    elif [ $n -eq 0 ]; then echo "zero"
    else echo "negative"
    fi

C. nested if

A nested if places one if statement inside another to test dependent conditions.

  • Purpose: the inner test is reached only if the outer condition already holds.
  • Each level needs its own fi.
    BASH
    if [ $age -ge 18 ]; then
      if [ $citizen = "yes" ]; then
        echo "Eligible to vote"
      fi
    fi

D. case Statement

case matches a single value against several patterns, replacing a long if-elif chain.

  • Structure: each pattern ends with ), its block ends with ;;, and esac closes the statement.
  • Wildcards: patterns use glob syntax — * (any), [yY] (character set), | (alternatives).
    BASH
    case $choice in
      start) echo "starting" ;;
      stop|halt) echo "stopping" ;;
      *) echo "unknown" ;;   # default
    esac

V. Loops

Repetition constructs; contrast condition-driven and list-driven iteration.

A. while loop

while repeats its body as long as the test command keeps succeeding.

  • Condition-driven: the test is re-evaluated before each pass; the loop ends when it fails.
  • Requires a state change inside the body, or it loops forever.
    BASH
    i=1
    while [ $i -le 5 ]; do
      echo $i
      i=$((i + 1))   # essential update
    done
  • until: the mirror image — loops until the condition becomes true.

B. for loop

for iterates over an explicit list of items, one per pass.

  • List-driven: the count is fixed by the list length, not by a test.
  • List sources: literal words, {1..5} brace expansion, $(command) output, or "$@".
  • C-style form: bash also supports for ((i=0; i<5; i++)).
    BASH
    for file in *.txt; do
      echo "Processing $file"
    done

    while vs for: use while when the number of iterations is unknown (waiting on a condition); use for when iterating a known collection.

VI. Functions and Arrays

Reusable code blocks and indexed data storage.

A. functions

A function is a named, reusable block of commands defined once and called by name.

  • Definition: name() { commands; } or function name { … }.
  • Arguments: passed positionally like scripts — inside the function $1, $2 refer to the call's arguments, not the script's.
  • Return: return N sets the exit status (0–255 only); to return data, echo it and capture with $(...).
  • Scope: variables are global unless declared local.
    BASH
    add() {
      local sum=$(( $1 + $2 ))
      echo $sum
    }
    result=$(add 3 4)   # result = 7

B. arrays

An array stores multiple values under one name, accessed by numeric index.

  • Declaration: arr=(red green blue) — indices start at 0.
  • Access: ${arr[1]} gives green; a single index only.
  • All elements: ${arr[@]}; count: ${#arr[@]} gives 3.
  • Assignment: arr[3]=yellow adds or replaces an element.
    BASH
    colors=(red green blue)
    for c in "${colors[@]}"; do
      echo $c
    done
    echo "Total: ${#colors[@]}"   # Total: 3
  • Associative arrays: declare -A map allows string keys (map[name]=Sam) in bash 4+.