intermediate21 min

Shell Scripting: Parameters, Conditions, Loops, and Functions

Turning a sequence of commands you'd type by hand into a real, reusable script — parameters, conditionals, loops, and functions, the same building blocks as any programming language, applied to shell.

What you'll learn

  • Write a script that reads positional parameters and reacts to whether they were provided
  • Write conditional logic using [[ ]] test expressions and if/elif/else
  • Write a loop and a function, and explain how a function returns a value in shell versus other languages

Prerequisites

Explanation

Every real command below runs only in your own terminal — this lesson's exercises model scripting logic as JavaScript, never executing a real shell script.

A script's positional parameters$1, $2, ... for its arguments, $0 for the script's own name, $# for the argument count, $@ for all arguments — are how a script receives input from its caller: ./deploy.sh production v2.1 gives the script $1=production, $2=v2.1, $#=2. Checking $# (or whether $1 is empty) before assuming an argument was actually provided is basic, necessary defensiveness — a script that blindly uses $1 without checking produces a confusing error (or worse, silently wrong behavior) when called with no arguments at all.

Bash's modern conditional test, [[ ]] (preferred over the older, more error-prone single-bracket [ ] for reasons this course's next lesson covers), checks conditions: [[ -f "$file" ]] (does this regular file exist), [[ -d "$dir" ]] (does this directory exist), [[ "$a" == "$b" ]] (string equality), [[ $count -gt 0 ]] (numeric comparison — note -gt/-lt/-eq for numbers, versus >/</== for strings, a real and common source of confusion if mixed up). if [[ condition ]]; then ... elif [[ other ]]; then ... else ... fi is the standard conditional structure.

Loops: for item in "${array[@]}"; do ... done iterates over a list; while [[ condition ]]; do ... done repeats while a condition holds. Functions (greet() { echo "Hello, $1"; }) group reusable logic — but a shell function's "return value," precisely, works differently from most programming languages: return in a shell function sets its exit code (0-255, the same success/failure convention from this course's earlier lesson), not an arbitrary value the way return works in JavaScript or Python. To actually get a computed value out of a function (not just success/failure), the idiomatic pattern is having the function echo the value and capturing that with command substitution at the call site: result=$(compute_something "$input"). Confusing these two mechanisms — trying to return a string, or checking a function's echoed output as if it were its exit code — is a genuine, common shell-scripting mistake worth understanding precisely rather than working around by trial and error.

Example

Modeling positional-parameter handling and the return-vs-echo distinction, as pure JS logic -- no real script runs.

function scriptBehavior(args) {
  const argCount = args.length;
  if (argCount === 0) {
    return { exitCode: 1, message: "Usage: deploy.sh <environment> [version]" };
  }
  const environment = args[0];
  const version = args[1] ?? "latest"; // models a default when $2 wasn't provided
  return { exitCode: 0, message: "Deploying " + environment + " at " + version };
}
console.log(scriptBehavior([]));                       // exit 1 -- missing required argument
console.log(scriptBehavior(["production", "v2.1"]));    // exit 0 -- both provided
console.log(scriptBehavior(["production"]));            // exit 0 -- version defaults to "latest"

// Modeling the return-(exit-code)-vs-echo-(value) distinction:
function isValidEnvironment(env) {
  return ["staging", "production"].includes(env); // models a function's boolean "return" (exit code 0/1)
}
function buildDeployTag(env, version) {
  return env + "-" + version; // models a function that ECHOES a computed value, captured via $(...)
}
console.log(isValidEnvironment("production")); // true -- the "exit code" style result
console.log(buildDeployTag("production", "v2.1")); // "production-v2.1" -- the "echoed value" style result

Try it yourself

Call scriptBehavior with three arguments and observe that the third one is simply unused by this function, exactly as an unused $3 would be in a real script.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

This models positional-argument handling only -- no real script runs. Write scriptBehavior(args): if args.length is 0, return {exitCode:1, message:'Usage: deploy.sh <environment> [version]'}; otherwise return {exitCode:0, message: 'Deploying ' + args[0] + ' at ' + (args[1] ?? 'latest')}.

Checks: no arguments produces the correct error exit code · a missing optional argument uses the correct default · both arguments provided are both used correctly

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

This models the return-(exit-code)-vs-echo-(value) distinction only -- no real function is invoked. Write isValidEnvironment(env) returning true/false (models a function's exit-code-style boolean result) for exactly 'staging' or 'production'. Write buildDeployTag(env, version) returning env + '-' + version (models a function that echoes a computed value for the caller to capture).

Checks: correctly validates a real environment name · correctly rejects an unrecognized environment name · correctly builds a combined value from two inputs

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Using a script's $1 without first checking $# (or whether $1 is empty) -- this produces a confusing error, or worse, silently wrong behavior, when the script is called with no arguments.
  • Confusing numeric comparison operators (-gt, -lt, -eq) with string comparison operators (>, <, ==) inside [[ ]] -- using the wrong category for the wrong kind of value produces incorrect or unexpected comparisons.
  • Trying to `return` a computed string VALUE from a shell function, expecting it to work like a return statement in JavaScript or Python -- a shell function's return sets its EXIT CODE (0-255) only; getting an actual value out requires echoing it and capturing the output via $(...) at the call site.

Knowledge check

Knowledge check

1. What does $# represent in a shell script?
2. Inside [[ ]], what's the difference between using -gt and using > for a comparison?
3. How does a shell function actually 'return' a computed value (like a calculated string) to its caller?

Takeaway

Always check whether a positional parameter was actually provided before using it; use numeric comparison operators (-gt, -lt, -eq) for numbers and string operators (==, <, >) for text; and remember a shell function's return sets only its exit code — getting an actual computed value out requires echoing it and capturing that output with command substitution.

Summary

$1, $2, ..., $#, and $@ give a script access to its arguments. [[ condition ]] with if/elif/else, and numeric (-gt/-lt/-eq) vs. string (==/</>) comparison operators, drive conditional logic. for/while loops iterate. Shell function 'return' sets only the exit code; echoing a value and capturing it via $(...) is how a function actually produces computed data for its caller.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.