Shell Execution Model (Permissions, Processes, Streams)

Context: FIT2109_MOC · the answer to “why did this command run or fail?” · command inventory in Shell Toolkit (Cheatsheet), navigation and text pipelines in Unix Shell (Bash) · the OS layer beneath it is Operating Systems and Multi-Processing

Quick Revision

  • 🎯 Objective: typing a command fires lookup ➔ permission check ➔ process ➔ streams ➔ exit status ➔ name the stage that failed from the error text alone.
  • 📦 Core Components: $PATH lookup ➔ command not found | permission bits ➔ permission denied | path resolution ➔ no such file or directory.
  • ⚡ Key Constraint: each stage has ONE signature error message — the message is the diagnosis, so read it before touching anything.

🗺️ Layer & Dataflow

Every command passes these five stages in order; the first one to fail produces the error you see.

#StageWhat the shell doesSignature failureFix
1Lookupresolve the name: builtin first, else scan $PATH dirs in ordercommand not foundgive an explicit path, or add the dir to $PATH
2Path resolutionresolve the pathname to an existing inodeno such file or directorycheck pwd, check spelling, check relative vs absolute
3Permission checkis the execute bit set for you?permission deniedchmod u+x file
4Processfork/exec ➔ a running instance with a PIDprogram’s own runtime errorsread the program’s message, not the shell’s
5Reportwire up stdin/stdout/stderr, return an exit statussilent wrong outputinspect $?, not just the screen
  • Program vs process ➔ a program is stored code on disk; a process is a running instance of it, identified by a PID. One program ➔ many concurrent processes.
  • Whose error is it? ➔ stages 1–3 fail in the shell; stage 4 onward the message comes from the program. Misattributing this sends you debugging the wrong thing.

📝 How It Works

1. Command lookup — $PATH and the builtin/external split

  • Builtin vs externalcd is built into the shell (it must be — it changes the shell’s own working directory); ls is a separate executable program on disk.
  • $PATH is a search list, not the whole disk ➔ a colon-separated list of directories, scanned left to right; the first match wins, the rest are never seen.
  • A bare name is a lookup requestrun.sh asks the shell “find me a command called run.sh, and . (the current directory) is not on $PATH on modern systems ➔ command not found.
  • A path is an instruction, not a request ➔ any name containing / (./scripts/run.sh) skips lookup entirely and uses that path directly. This is the whole reason for the leading ./.
  • Shebang beats your interactive shell ➔ a script starting #!/usr/bin/env bash runs in Bash whatever shell you typed in. echo "$SHELL" reports the interactive one.

2. Permissions — existence is not access

  • Read the string in four blocks-rwxr-xr-x = - filetype · rwx owner · r-x group · r-x others.
  • Filetype character- regular file, d directory.
  • r w x ➔ read · write · execute; a - in the slot means that permission is absent.
  • x on a directory means traverse ➔ not “run it” — without it you cannot cd into it or resolve a path through it.
  • chmod u+x file ➔ adds execute for the owner only; it changes the metadata, never a single byte of the file’s contents.
  • The classic composite failure ➔ the file exists, the path is right, the code is correct, and it still won’t run — because stage 3 rejected it.

3. Streams — three separate channels

  • stdin ➔ where the program reads input from by default (the keyboard).
  • stdout ➔ where normal results go (the screen).
  • stderr ➔ where error messages go — a different channel that happens to land on the same screen.
  • Redirection retargets one channel> f overwrite stdout · >> f append · < f feed stdin · 2> f capture stderr.
  • Why the split existscmd > out.txt saves results while errors still appear on screen; nothing useful gets buried in the data file.
  • Pipes chain stdout into stdinA | B | C makes each tool small and focused; stderr does not travel down the pipe.

4. Exit status — the machine-readable verdict

  • Convention ➔ every command returns a small integer: 0 = success, nonzero = failure. Read it with echo $?.
  • Status ≠ output ➔ a command can print nothing and succeed (grep finding no match is a legitimate nonzero), or print plenty and fail. Never infer success from the screen.
  • A pipeline reports only its LAST stagebad_cmd | wc -l exits 0 because wc succeeded; set -o pipefail in a script makes any failing stage propagate.
  • $$ is the current shell’s own PID ➔ the handle for asking ps about the process you are typing into.

⚙️ Core Implementation

🔹 Diagnosing the three signature failures

🔹 Streams: separating results from errors

📊 Exam Execution Trace & Applied Exercises

Manual Execution Trace

cat app.log | grep "ERROR" | sort | uniq -c — each stage transforms the stream:

StepStagestdinstdoutRows out
1cat app.logfileall lines4000
2grep "ERROR"4000 linesmatching lines only37
3sort37 linessame lines, ordered37
4uniq -c37 sorted linescount + distinct message6
echo $?0uniq’s status, not grep’s
  • Order matters ➔ the cheap filter (grep) sits early so only 37 rows reach sort; uniq -c needs sort first because it collapses adjacent duplicates only.

Applied Exercise

Problem: ./deploy.sh prints Permission denied. ls -l shows -rw-r--r--. State the failing stage, the fix, and what the permission string looks like afterwards. Answer: stage 3 (permission check) — the file was found, so lookup and path resolution both succeeded. chmod u+x deploy.sh-rwxr--r--. The contents are untouched; only the owner’s execute bit flipped. Final Extracted Output: -rwxr--r--, and echo $? after a successful run returns 0.

⚠️ Common Mistakes

  • 💡 Reading permission denied as “the file is missing” ➔ it is the opposite — the file was found and rejected. no such file or directory is the missing-file message.
  • 💡 Treating an empty screen as success ➔ output and exit status are independent. Check $?.
  • 💡 Trusting a pipeline’s exit status ➔ only the last stage is reported; a failure mid-pipeline is invisible without set -o pipefail.
  • 💡 Ctrl-C is not copy ➔ it sends an interrupt that kills the running process. Losing work this way in a terminal is a Week-1 rite of passage.

🧠 Active Recall