Module 1 — The Shell, Scripts, and How Commands Run
Updated 3 September 2026
🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
Part A — What is actually happening when you type a command?
A1. The terminal, the shell, and the kernel
When you open a "terminal" and type a command, three separate pieces of software are involved, and people mix them up constantly.
The terminal is just the window — a program that draws text on screen and sends your keystrokes onward. It understands nothing about commands.
The shell is the program running inside that window, reading what you type. On most Linux systems it is bash (the Bourne Again Shell). The shell's job is to take a line of text like ls -l /tmp, figure out what program you want to run and with what inputs, start that program, and show you what came back.
The kernel is the core of the operating system. It is the only thing that can actually start programs, read disks, or talk to the network. The shell never does any real work itself — it asks the kernel to do it.
So bash is not "the terminal" and not "Linux". It is an ordinary program whose specialty is launching other programs. That has a huge consequence: anything you can type at the prompt can be saved in a file and replayed later. A file of shell commands is called a shell script, and a shell script is nothing more exotic than that — typed commands, saved.
One more distinction you will need in a moment: some commands are programs — separate files on disk that the shell finds and asks the kernel to run (ls is one). Others are builtins — abilities wired into bash itself, so no separate program is started (cd is one). You can ask bash which is which with the command type.
Where the analogy stops working. A real waiter passes your order along roughly as you said it. The shell does not — before anything reaches the kernel, bash rewrites your command line: it splits it into words, expands special patterns, and substitutes values. You will meet this rewriting machinery in Module 2, and it is the single most important thing about bash that beginners do not expect.
🧪 Exercise 1.1 — meet your shell
bash --version # which shell, which version?
type cd # is cd a separate program, or built into bash?
type ls # and ls?✅ Expected result — click to reveal
The three commands print something very close to this (versions and paths vary by machine):
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2022 Free Software Foundation, Inc.
...
cd is a shell builtin
ls is /usr/bin/lsWhat to read out of it: the first line names the program (GNU bash) and its version (5.2.21 here — yours may differ; anything 4+ is fine for this track). type cd answers shell builtin: cd is a capability of bash itself, not a file on disk. type ls answers with a path — /usr/bin/ls — meaning ls is a real, separate program stored at that location, and bash must first find it before it can run it. How bash finds it is the next section.
A2. How the shell finds commands — PATH
When you type ls, bash does not search your whole disk — that would take minutes. Instead it keeps a short list of directories where programs are allowed to live, and checks only those, in order, left to right, stopping at the first match. That list is called PATH.
You can print it with the command printenv PATH (printenv is a small program that prints one of the shell's stored settings — these settings are called environment variables, and Module 2 teaches them properly; for now you only need to look at this one).
The order matters. If two different directories each contain a program named python, the one in the earlier PATH directory wins, every time. Real outages have been caused by exactly this: someone installs a second copy of a tool, it lands earlier in PATH, and every script on the machine silently starts using the new one.
Where the analogy stops working. A pharmacist would notice two shelves holding different strengths of the same drug and ask questions. PATH lookup never asks questions and never warns — the first name match wins silently, even if a better or newer match sits one directory later.
🧪 Exercise 1.2 — see the search list
printenv PATH # the directories bash searches, separated by colons
which ls # which file would run if I typed ls?
type -a ls # every match bash knows about, in search order✅ Expected result — click to reveal
On a typical Ubuntu machine (your exact list will differ — that is normal and is itself the lesson):
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
/usr/bin/ls
ls is /usr/bin/ls
ls is /bin/lsWhat to read out of it: the first line is one long string of directories glued together with : — read it left to right, because that is the order bash searches. which ls gives the verdict: the file /usr/bin/ls is what a bare ls runs. type -a ls lists every match in search order, not just the winner — on some machines you will see an alias line first and the file second, which is a preview of an important fact: bash checks its own builtins and shortcuts before it ever looks at PATH. If your ls shows an alias, that is normal on Ubuntu. The doubled file line is normal there too: on Ubuntu /bin is a link to /usr/bin, and both are on PATH, so the same program matches twice.
Interview questions — Part A
🎯 "What is Shell?" — asked verbatim at InterviewBit and Edureka
The two-sentence answer: a shell is a command-line interpreter — a program that reads commands typed by a user (or saved in a file), translates them into requests, and asks the operating system's kernel to carry them out. On most Linux systems the default shell is bash.
Going deeper than the published answer: the shell is not part of the kernel and not part of the terminal — it is a replaceable, ordinary program. You can run several different shells on one machine, and you can see your current one's name by running echo on a setting called $0 (Module 2 explains that syntax). The shell also does substantial work before the kernel is involved: parsing the line, expanding patterns, deciding builtin vs external program.
The details that separate candidates: naming the three-layer split (terminal → shell → kernel) unprompted; saying that builtins like cd run inside the shell while programs like ls are found via PATH and executed by the kernel; and being able to say why cd must be a builtin — a separate program could not change the shell's own current directory (Module 12 gives you the full mechanism for this).
🎯 "What is a Shell Script?" — asked verbatim at Edureka and KnowledgeHut
The direct answer: a text file containing shell commands, executed in order, exactly as if you had typed them. Anything you can do at the prompt, a script can do.
Going deeper: a good answer immediately gives the anatomy — a first line saying which interpreter runs the file (the shebang, Part B), execute permission (chmod +x, Part B), and then plain commands with # comments. Stronger still is naming what scripts are for in DevOps: gluing tools together, deploys, backups, cron jobs, CI steps — anywhere a human would otherwise retype the same commands.
The details that separate candidates: mentioning that the script runs in its own shell process, so things like changing directory inside a script do not affect the terminal that launched it (Part C of this module) — interviewers use that one fact to separate people who have written scripts from people who have read about them.
🎯 "Name different types of shells available." — asked verbatim at InterviewBit; Edureka phrases it "What are the different types of commonly used shells on a typical Linux system?"
The expected list: sh (the original Bourne shell), bash (Bourne Again Shell — the default on most Linux), zsh (default on modern macOS), ksh (Korn shell), and the C-shell family csh/tcsh. Also worth naming: dash, a small, fast, strictly-POSIX shell that Debian and Ubuntu use as /bin/sh for running system scripts.
Going deeper: the families matter more than the list. Bourne-family shells (sh, bash, ksh, zsh, dash) share core syntax, so a plain script usually runs on all of them; the C-shell family is syntactically different and effectively never used for new scripts. The practical DevOps question hiding inside this one is "what is /bin/sh on your systems?" — on Ubuntu it is dash, on many RHEL-family systems it is bash itself — and Part C of this module shows why that difference bites.
The details that separate candidates: knowing that /bin/sh is often a link to a different shell rather than a shell of its own, and that this varies by distribution — it turns a memorized list into an answer about real systems.
Part B — Your first script
B1. Putting commands in a file
Three commands you need for moving around, all of which you may know already: pwd prints the directory you are standing in, cd somewhere moves you there, and mkdir name creates a new directory. We will keep all of this module's files in one practice directory.
A script is created with any text editor. We use nano because it is preinstalled and shows its keyboard shortcuts at the bottom of the screen: Ctrl+O then Enter saves, Ctrl+X exits.
Two writing rules before you type anything. First: one command per line — bash runs a script top to bottom, one line at a time, exactly as if you were typing each line and pressing Enter. Second: any line starting with # is a comment — bash ignores it completely; it exists for the humans who will read the script later. In production, that human is usually you, six months from now, at 3 a.m.
Where the analogy stops working. A waiter who hits a problem mid-order stops and asks you. Bash does not: if line 3 of your script fails, bash by default just carries on with line 4. That default — carry on past failures — surprises everyone, and Modules 3 and 14 are largely about controlling it.
🧪 Exercise 1.3 — create the file
mkdir -p ~/bash-course # a practice directory in your home folder (-p: no error if it exists)
cd ~/bash-course
nano hello.sh # opens the editor; type the 5 lines below, save with Ctrl+O Enter, exit with Ctrl+XType these five lines into the editor (the first line's meaning is section B2 — type it now, understand it in two minutes):
#!/bin/bash
# My first script
echo "Hello from my first script"
echo "Today is:"
dateThen check what you wrote:
cat hello.sh # cat prints a file's contents to the screen✅ Expected result — click to reveal
cat prints back exactly the five lines you typed:
#!/bin/bash
# My first script
echo "Hello from my first script"
echo "Today is:"
dateWhat to read out of it: nothing has run yet — cat only displays the file. If your output has extra blank lines or a mistyped line, open nano hello.sh again and fix it now; the next exercises run this exact file. Note the mix in the file: a special first line, a comment, two echo commands (echo prints its text to the screen), and date, an ordinary program — a script freely mixes builtins and programs.
B2. The shebang: the first line of every script
The first line, #!/bin/bash, is called the shebang (from hash # + bang !). It answers one question: which program should run this file? A text file cannot run itself — something must read it and execute its lines. The shebang names that something, by its full path: here, the bash program stored at /bin/bash.
The mechanism is worth knowing precisely, because interviewers probe it. When you execute a file, it is the kernel — not the shell — that opens it and looks at the first two characters. If they are #!, the kernel reads the rest of that line, starts that interpreter, and hands it the file. So a file starting #!/bin/bash is run by bash, #!/usr/bin/python3 by Python — the same mechanism runs every scripting language on the system.
Where the analogy stops working. A clerk with an unstampable envelope asks around. The kernel refuses instantly and unhelpfully — and if the stamp names a department that does not exist (/bin/bassh), the error message you get blames the envelope, not the stamp. Part D shows that confusing error up close.
B3. Permission to execute: chmod
Every file on Linux carries three separate permissions: read, write, and execute. A freshly created file gets read and write, but not execute — Linux does not assume that a file full of text is meant to be a program. Until you grant execute permission, the kernel will refuse to run the file, no matter what its shebang says.
Granting it is one command: chmod +x hello.sh (change mode: add execute). You can see the permissions with ls -l, which prints one line per file; the first block of characters is the permissions. Read it in groups of three after the first character: rw- (owner can read, write, not execute), then r-- twice (group and everyone else: read only). After chmod +x, each group gains an x.
🧪 Exercise 1.4 — run it: fail first, then fix
This exercise fails on purpose — the error is the lesson.
cd ~/bash-course
ls -l hello.sh # look at the permissions: any x?
./hello.sh # try to run it ← this will fail
chmod +x hello.sh # grant execute permission
ls -l hello.sh # look again
./hello.sh # now it runs✅ Expected result — click to reveal (contains a deliberate failure)
-rw-r--r-- 1 zaeem zaeem 86 Sep 3 12:08 hello.sh
bash: ./hello.sh: Permission denied
-rwxr-xr-x 1 zaeem zaeem 86 Sep 3 12:08 hello.sh
Hello from my first script
Today is:
Thu Sep 3 12:08:15 +08 2026What to read out of it, line by line: the first ls -l shows -rw-r--r-- — no x anywhere, so the run attempt is refused with Permission denied. That message means the file was found but you lack the right to execute it — keep that distinction; Part D builds a whole diagnosis method on it. After chmod +x, the permissions read -rwxr-xr-x — an x in each group — and the same ./hello.sh now runs: two lines from echo, then real output from date (your date, name, file size and timestamps will differ, and the number 86 is the file's size in bytes).
B4. Three ways to run a script — and why one of them fails
You now know everything needed to understand the three ways of running hello.sh, and they connect Parts A and B:
Way 1 — ./hello.sh. "Execute the file right here." The ./ sidesteps the PATH search from A2 (this is the explicit "this one, here" from the pharmacist analogy). The kernel reads the shebang, checks the execute bit. This is the normal way, and it needs both the shebang and chmod +x.
Way 2 — bash hello.sh. "Bash, read this file and run its lines." Here bash is just reading a text file, so no execute permission is needed and the shebang is ignored (B2). Useful for testing; also the reason broken shebangs stay hidden.
Way 3 — hello.sh with no prefix. Bash searches PATH for a program named hello.sh, does not find one (your practice directory is not on the list, and "here" is never searched — A2), and reports command not found. This is the expected failure, and understanding why it fails is an interview question in its own right.
🧪 Exercise 1.5 — all three ways
One of these three fails on purpose.
cd ~/bash-course
./hello.sh # way 1: direct execution
bash hello.sh # way 2: hand the file to bash yourself
hello.sh # way 3: bare name ← this will fail✅ Expected result — click to reveal (contains a deliberate failure)
Ways 1 and 2 both print the familiar three lines of output. Way 3 prints:
bash: hello.sh: command not foundWhat to read out of it: command not found is a search failure — bash walked the PATH directories from A2, found no file named hello.sh in any of them, and gave up. It never even reached the file sitting in front of you. (On stock Ubuntu the wording may differ slightly — no bash: prefix, or a "Command 'hello.sh' not found" suggestion — that is Ubuntu's command-not-found helper; it means the same thing.) Compare the two errors you have now collected: Permission denied = found, but not allowed to execute (a permissions problem, fixed with chmod). command not found = never found (a PATH problem, fixed with ./ or by installing the script into a PATH directory — Part E shows where production systems put theirs).
Interview questions — Part B
🎯 "What is the significance of the Shebang line in Shell Scripting?" — asked verbatim at Edureka; LinuxTeck asks "What is the shebang line in a bash script?"; KnowledgeHut asks "What is the #! (shebang) used for in a shell script?"
The direct answer: the shebang is the first line of a script, #! followed by an absolute path (for example #!/bin/bash); it tells the operating system which interpreter should execute the file when the file is run directly.
Going deeper than the published answers: it is the kernel, not the shell, that reads the shebang — during direct execution of the file. To bash, the line is a comment. That is why bash script.sh ignores the shebang while ./script.sh honors it. The mechanism is generic: the same two bytes route Python, Perl, and awk scripts to their interpreters.
The details that separate candidates: stating who reads it (the kernel) and when it is skipped (explicit interpreter invocation); knowing the failure mode — a wrong shebang path produces a confusing error (bad interpreter on older systems, cannot execute: required file not found on newer bash) rather than a clear one; and mentioning #!/usr/bin/env bash portability, which Part C covers.
🎯 "How do you make a shell script executable?" — asked verbatim at Hirist; LinuxTeck asks the same; InterviewBit phrases it "Write the command that is used to execute a shell file."
The direct answer: chmod +x script.sh, then run it with ./script.sh.
Going deeper: explain why the step exists — Linux keeps "this file may be executed" as an explicit permission bit, separate from readability, so text files are not runnable by accident. Verify with ls -l and read the permission string (-rwxr-xr-x). A precise answer also notes what +x grants (execute for owner, group, and others at once) and that chmod u+x grants it to the owner only — a distinction that matters on shared production machines.
The details that separate candidates: knowing that bash script.sh works without the execute bit (bash merely reads the file) — and being able to say why the two invocations differ; plus one production habit: version-control systems like git track the execute bit, so a script committed without it arrives broken on every machine that clones it.
🎯 "What file extension should a bash script use?" — asked verbatim at LinuxTeck
The direct answer: none is required. Linux decides how to run a file from its shebang and execute permission, never from its name — .sh is purely a convention for humans and tools.
Going deeper: the convention is still worth following in repositories (editors pick syntax highlighting, linters like ShellCheck auto-detect targets, and colleagues can see at a glance what a file is). But note the counter-convention: scripts installed into a PATH directory as commands usually drop the extension — you type deploy, not deploy.sh. The extension describes the source file, not the command.
The details that separate candidates: pointing out that renaming hello.sh to hello.banana changes nothing about whether or how it runs — and that this is the opposite of Windows, where the extension is the execution rule. Candidates who volunteer the cross-platform contrast show they understand the mechanism rather than the habit.
Part C — Which shell runs your script, and where
C1. A script runs in its own shell: execute vs source
Here is a fact that quietly explains half of all beginner confusion: when you run ./hello.sh, your terminal's bash does not run the script's lines itself. It starts a second, fresh bash, that second bash runs the script, and when the script ends, the second bash exits and is gone. Your terminal's bash just waits for it to finish.
Consequence: whatever the script changes about its own shell dies with it. If a script does cd /tmp, it is the second bash that moves there; your terminal has not moved. Directory changes, new settings, anything shell-internal — none of it survives the script's end.
Sometimes you want a file of commands to affect your current shell — that is what source is for. source ./file.sh (or its ancient one-character synonym, . ./file.sh) tells your current bash: read this file and run its lines yourself, right here, as if they were typed at the prompt. No second bash, so every change sticks. This is exactly how shell configuration files like ~/.bashrc work — bash sources them at startup so their settings land in your actual shell.
Where the analogy stops working. A real contractor arrives knowing nothing about your house. The script's fresh bash actually inherits a copy of much of your shell's environment (which settings travel across and which do not is precisely Module 2's export story). And the copying is one-way: the child starts from your state, but nothing it does flows back.
🧪 Exercise 1.6 — prove the script lives elsewhere
cd ~/bash-course
nano goto-tmp.sh # create this 3-line script, save, exit#!/bin/bash
cd /tmp
pwdchmod +x goto-tmp.sh
pwd # where am I before?
./goto-tmp.sh # script says it is in /tmp...
pwd # ...but where am I?
source ./goto-tmp.sh
pwd # and now?✅ Expected result — click to reveal
/home/zaeem/bash-course
/tmp
/home/zaeem/bash-course
/tmp
/tmpWhat to read out of it, line by line: line 1 — you start in the practice directory (you will see your own username, not zaeem). Line 2 — the script's pwd prints /tmp: inside its own bash, the cd worked. Line 3 — your own pwd still says the practice directory: the script's move died with the script. Line 4 — source runs the same file in your shell, so its pwd prints /tmp again. Line 5 — and this time you are still there: the change survived, because there was no second bash to die. One file, two run methods, two different worlds.
C2. /bin/sh is not bash — and why shebangs name their shell
Bash is one shell among several (Part A's interview question listed them). The one you will actually collide with in production is sh. /bin/sh is the standard, minimal shell interface that every Unix system guarantees — and on many systems it is not bash. On Debian and Ubuntu, /bin/sh is dash: a small, fast shell that implements only the standardized features. Bash implements all of those plus its own conveniences ("bashisms"). Write a script using bash conveniences, put #!/bin/sh on top — and it works on machines where sh happens to be bash, then breaks on the machine where it is dash.
Seeing it needs a bash convenience we must first teach (dependency rule). Here is the smallest one: brace expansion. Before running a command, bash rewrites {1..3} into 1 2 3 — so echo {1..3} prints 1 2 3. This is pure bash convenience; plain sh does not do it, and per its rules, text it cannot expand is left as-is. That gives us a one-line litmus test.
🧪 Exercise 1.7 — same file, two shells, different output
cd ~/bash-course
nano braces.sh # two lines, save, exit#!/bin/bash
echo {1..3}chmod +x braces.sh
./braces.sh # kernel reads shebang → bash runs it
sh braces.sh # you force sh to run it (shebang ignored, B2)✅ Expected result — click to reveal
1 2 3
{1..3}What to read out of it: same file, different interpreters, different output. Run via ./, the shebang routes it to bash, which expands {1..3} before echo ever runs — echo receives three words. Run via sh (on Ubuntu: dash), the expansion never happens and echo receives the literal characters {1..3} — printed as-is, with no error and no warning. That silence is the dangerous part: a wrong-shell script often half-works, producing wrong values instead of honest crashes. (On systems where /bin/sh links to bash — some RHEL-family machines — both lines print 1 2 3; run ls -l /bin/sh to see what yours links to.)
Interview questions — Part C
🎯 "What is the difference between source and ./ when executing a script?" — asked verbatim at Zero To Mastery; Hirist covers the same as "running via source versus direct execution"
The direct answer: ./script.sh runs the script in a new shell process, which exits when the script ends — changes to directory or shell settings vanish with it. source script.sh runs the same lines in the current shell, so every change persists.
Going deeper: give the canonical demonstration (a script containing cd /tmp — executed, your shell has not moved; sourced, it has), and the canonical real-world use — source ~/.bashrc to reload shell configuration, and CI/deploy environment files that must be sourced to take effect. Note the synonym: . is the POSIX spelling of source.
The details that separate candidates: saying why the difference exists (a process cannot alter its parent's state — the full mechanism arrives in Module 12); knowing sourcing runs even without the execute bit, since nothing is being executed as a program; and one safety instinct — sourcing hands a file complete control of your current shell, including the ability to exit it, so you source only files you trust.
🎯 "Why do we use #!/bin/bash instead of just #!/bin/sh?" — asked verbatim at LinuxTeck; Edureka's variant: "What does it mean by #!/bin/sh or #!/bin/bash at the beginning of every script?"
The direct answer: because they can be different programs. #!/bin/bash requests bash specifically, with all its features. #!/bin/sh requests the system's standard minimal shell — which on Debian/Ubuntu is dash, not bash. A script using bash features under #!/bin/sh breaks, sometimes loudly, often silently.
Going deeper: name concrete bashisms that die under plain sh — brace expansion {1..3}, arrays, [[ ]] tests (Modules 5 and 10 teach these) — and state the diagnostic: ls -l /bin/sh shows what your system's sh really is. Then the flip side: if you want maximum portability and write only standard features, #!/bin/sh is the honest choice, and dash will run it faster than bash would.
The details that separate candidates: the phrase "sh is a specification, bash is an implementation"; knowing Debian/Ubuntu chose dash for /bin/sh largely for boot-time speed; and mentioning #!/usr/bin/env bash as the portable way to request bash on systems where its location varies — plus its trade-off (you get whichever bash PATH finds first).
Part D — When it won't run: reading the errors
D1. The three refusals, and what each one is telling you
You have already met two refusals in this module. There is a third. Together they cover nearly every "my script won't start" incident you will ever debug, and each message pins the blame on a different stage of the launch sequence from Parts A–B:
| The message | Which stage failed | What it means |
|---|---|---|
| command not found | The PATH search (A2) | Bash never found any file to run. Wrong name, missing ./, or the program is not installed / not on PATH. |
| Permission denied | The permission check (B3) | The file was found, but its execute bit is off (or you genuinely lack rights to it). Fix: chmod +x, or check ls -l. |
| cannot execute: required file not found — older systems say bad interpreter: No such file or directory | The shebang (B2) | The file was found and is executable, but the interpreter named on line 1 does not exist at that path. Typo in the shebang — or the invisible variant below. |
🧪 Exercise 1.8 — break the shebang on purpose
This exercise fails on purpose.
cd ~/bash-course
nano typo.sh # two lines — note the deliberate typo "bassh"#!/bin/bassh
echo "hi"chmod +x typo.sh
./typo.sh # ← fails with the misleading error
bash typo.sh # ← works! (why?)✅ Expected result — click to reveal (contains a deliberate failure)
bash: ./typo.sh: cannot execute: required file not found
hiWhat to read out of it: the file exists, is readable, is executable — and still refuses, because the kernel went looking for an interpreter at /bin/bassh and there is no such program (on bash older than 5.2 the same failure reads bash: ./typo.sh: /bin/bassh: bad interpreter: No such file or directory, which at least names the culprit). Then the second line: bash typo.sh prints hi without complaint — you chose the interpreter yourself, so the broken shebang was skipped as a comment (B2). That pair of symptoms — direct run fails, bash file works — is the fingerprint of a shebang problem. Memorize the fingerprint.
🧪 Exercise 1.9 — the invisible shebang breaker
The most common real-world cause of the third error is not a typo. It is a file edited on Windows. Windows editors end lines with two invisible characters: carriage-return then newline (CRLF); Linux uses newline alone. To the kernel, a CRLF shebang line names an interpreter called /bin/bash\r — bash-with-a-carriage-return-stuck-to-it — which does not exist. We will manufacture such a file with printf, a printing command that lets us write the invisible \r explicitly (\r = carriage return, \n = newline). This exercise fails on purpose.
cd ~/bash-course
printf '#!/bin/bash\r\necho "hi"\r\n' > windows.sh # simulate a Windows-edited file (> sends output into a file — Module 4 teaches this properly)
chmod +x windows.sh
./windows.sh # ← fails, identically to the typo
file windows.sh # the file command identifies file types — and sees the problem
cat -A windows.sh # cat -A makes invisible characters visible✅ Expected result — click to reveal (contains a deliberate failure)
bash: ./windows.sh: cannot execute: required file not found
windows.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
#!/bin/bash^M$
echo "hi"^M$What to read out of it: the failure is byte-for-byte identical to the typo failure — no mention of line endings anywhere. The diagnosis comes from the two inspection tools: file says it plainly — with CRLF line terminators — and cat -A shows each line ending in ^M$ (^M is how the carriage return is displayed; a clean Linux file shows $ alone). Fix it with sed -i 's/\r$//' windows.sh (sed is taught properly in Module 11 — until then, treat this as a recipe) or with the small utility dos2unix if installed. Then ./windows.sh prints hi.
D2. A diagnosis routine you can run in your head
The launch sequence you learned in this module is the debugging checklist, in order: does bash find the file? (PATH — did you use ./?) → may you execute it? (ls -l — is there an x?) → can the kernel start the interpreter? (line 1 — typo? CRLF?) → only then do errors come from your script's own lines. The decision tree below compresses Parts A–D into the form worth memorizing.
Diagram source
flowchart TD
A["./script.sh<br>refuses to run"] --> B{"Which<br>message?"}
B -->|"command<br>not found"| C["PATH problem (A2)"]
C --> C1["Ran bare name?<br>Use ./script.sh"]
B -->|"Permission<br>denied"| D["Execute bit off (B3)"]
D --> D1["ls -l script.sh<br>then chmod +x script.sh"]
B -->|"cannot execute /<br>bad interpreter"| E["Shebang problem (B2, D1)"]
E --> E1["head -1 script.sh<br>typo in path?"]
E --> E2["file script.sh<br>CRLF line endings?"]
B -->|"script starts, then<br>errors from its lines"| F["The launch worked."]
F --> F1["The bug is inside the script —<br>Modules 2+ are about those."]Interview questions — Part D
🎯 "Why does ./myscript.sh sometimes return 'permission denied'?" — asked verbatim at LinuxTeck
The direct answer: the file lacks the execute permission bit. New files are created without it; chmod +x myscript.sh grants it, and ls -l confirms (-rwxr-xr-x).
Going deeper: "sometimes" is the interesting word — the same script runs fine as bash myscript.sh (reading, not executing), works for a colleague who cloned it from a repo where the bit was committed, and fails for you because your copy arrived via a route that dropped the bit (some downloads, copy-paste into an editor, certain archive tools). Also worth knowing: the execute bit is checked by the kernel at launch, and directories need their own execute bit just to be entered — a script inside a directory you cannot traverse also reports permission errors.
The details that separate candidates: mentioning that git preserves the execute bit, so committing a script without it ships the bug to everyone; and distinguishing this error from command not found in one sentence — denied means found; not found means never found.
🎯 "Why does running myscript.sh without ./ often fail?" — asked verbatim at LinuxTeck
The direct answer: a bare name makes the shell search PATH, and the current directory is deliberately not on PATH. No PATH directory contains myscript.sh, so: command not found. The ./ prefix bypasses the search by giving an explicit location.
Going deeper: explain the security reasoning — a searched-first current directory would let anyone who can write to a shared directory plant a fake ls or ll and wait for an administrator to walk in and type it. This was a real attack on old Unix systems, which is why the default changed.
The details that separate candidates: the follow-up interviewers fish for — "so how do real tools get run by bare name?" Answer: they are installed into a PATH directory (/usr/local/bin for machine-local tools, ~/.local/bin or ~/bin for per-user ones), which is exactly what installers and package managers do. Adding . to PATH is possible and is the canonical wrong answer.
🎯 "How do you write a comment in a bash script?" — asked verbatim at LinuxTeck
The direct answer: start the line with # — bash ignores everything from # to the end of that line. Comments can also follow a command on the same line: chmod +x app.sh # grant execute.
Going deeper: the shebang looks like a comment because it is one — to bash. Its power comes from the kernel reading the file's first two bytes at execution time (B2). A thoughtful answer also touches comment discipline: comment the why, not the what — # retry because registry drops ~1% of requests earns its keep; # increment i does not.
The details that separate candidates: knowing there is no true multi-line comment syntax in bash, and that the # must begin a word — echo hi#there prints hi#there, no comment involved. Bonus knowledge: # inside quotes is ordinary text (Module 2 makes sense of that).
Part E — Production
E1. 🏭 Production practices
Every script begins with a shebang, always. Production scripts never rely on "whatever shell happens to run me." #!/bin/bash when you use bash features on managed hosts; #!/usr/bin/env bash when the script must travel across varied systems; #!/bin/sh only for deliberately POSIX-portable scripts that are tested with sh.
The execute bit is set at commit time, not at install time. Git tracks the execute bit — production teams run chmod +x once, before committing, so every clone arrives runnable. A "Permission denied" from a freshly cloned repo means someone skipped this.
Scripts that users run by bare name are installed into a PATH directory — machine-wide tools into /usr/local/bin, per-user tools into ~/.local/bin — and usually drop the .sh extension there. Nobody adds . to PATH; that is the classic wrong fix.
Line endings are enforced by tooling, not vigilance. Teams pin *.sh text eol=lf in the repo's .gitattributes (and/or an .editorconfig) so a Windows editor can never smuggle CRLF into a script that a Linux host will execute.
Environment files are sourced; work scripts are executed. Anything meant to set variables for the current shell is written to be sourced and named accordingly (env.sh, activate); anything that does work runs in its own shell so its changes die with it.
E2. Production-practice table
| Symptom | What is really happening | What to run | The fix |
|---|---|---|---|
| command not found for a script sitting in the current directory | Bash searched PATH only; the current directory is deliberately not searched | printenv PATH | Run it as ./script.sh, or install it into a PATH directory such as /usr/local/bin |
| Permission denied on ./script.sh, but bash script.sh works | Execute bit is off; bash-as-reader needs no execute bit, direct execution does | ls -l script.sh | chmod +x script.sh — and commit the bit so every clone gets it |
| cannot execute: required file not found (or bad interpreter) on a file you can cat | The interpreter named on line 1 does not exist at that path — typo, or CRLF endings appending an invisible \r | head -1 script.sh then file script.sh | Correct the shebang path; for CRLF: sed -i 's/\r$//' script.sh and add .gitattributes enforcement |
| Script "works on my machine", prints wrong or unexpanded output on another host or container | Different interpreter is running it — /bin/sh is dash or busybox there, and the script uses bash-only features | ls -l /bin/sh on both machines | Make the shebang name the shell the script needs (#!/bin/bash), and ensure bash exists on the target |
| Ran a setup script, but the settings/directory change "didn't take" | Executed instead of sourced — the changes happened in a child shell that exited | pwd before and after; compare with source ./setup.sh | Source configuration files: source ./setup.sh — reserve execution for scripts that do work |
E3. 🎓 Capstone — four tickets from the queue
🎓 Ticket 1 — "Our deploy script runs fine in CI, which calls bash deploy.sh. Last night an engineer ran ./deploy.sh on the box and got cannot execute: required file not found. The file is right there."
Diagnosis. The fingerprint from D1: works via bash file, fails via direct execution → the shebang is broken, because bash deploy.sh skips the shebang and ./deploy.sh makes the kernel honor it.
Work the steps: head -1 deploy.sh — is the path typed correctly? If it looks right, file deploy.sh — does it end "with CRLF line terminators"? One of the two will be it. Here it turned out an engineer had edited the file over a Windows share: CRLF. The kernel was asked for /bin/bash\r.
Fix and prevention: sed -i 's/\r$//' deploy.sh, verify with file deploy.sh, then add *.sh text eol=lf to .gitattributes so this class of ticket cannot recur. Also worth a CI improvement: make CI run ./deploy.sh exactly as humans do, so CI and humans can no longer see different bugs.
🎓 Ticket 2 — "Wrote a cleanup script, cleanup.sh, cd'd into its directory, ran cleanup.sh — command not found. But ls shows it right there!"
Diagnosis. Bare name → PATH search only (A2) → the script's directory is not on PATH, and "right here" is never searched. The ls proving the file exists is irrelevant to the search bash performed.
Work the steps: ./cleanup.sh runs it immediately (after chmod +x if needed). That solves the engineer's afternoon.
The real fix: decide what this script is. If it is a personal utility, install it: chmod +x cleanup.sh and move it to ~/.local/bin/cleanup (dropping .sh, per convention) — now the bare name works from anywhere, for that user. If it is a machine-wide operational tool, it belongs in /usr/local/bin. If someone on the team suggests adding . to PATH, that is the canonical wrong answer — it reintroduces the planted-binary attack that the missing-. rule exists to prevent.
🎓 Ticket 3 — "The app team has setenv.sh that prepares database settings. An engineer runs ./setenv.sh, sees no errors, then starts the app — the app says the settings are missing."
Diagnosis. C1 exactly: executed, the script ran in its own bash, made all its changes there, and that bash exited taking every change with it. No errors, because nothing failed — the work was simply done in a world that no longer exists.
Work the steps: demonstrate to the app team with the goto-tmp experiment (a script that does cd /tmp; pwd — script says /tmp, your pwd disagrees). Then: source ./setenv.sh and start the app — settings present.
Fix and prevention: document the file as "must be sourced," and adopt the production convention that makes misuse visible: environment files get names like env.sh or activate, no execute bit (so ./setenv.sh fails loudly with Permission denied instead of silently doing nothing useful), and a first-line comment: # source this file — do not execute.
🎓 Ticket 4 — "A report script works on the Ubuntu dev VM but produces garbage like {1..12} in its output when it runs inside our minimal container. Same file, checked twice."
Diagnosis. Literal {1..12} in output is unexpanded brace expansion — the fingerprint of a bash script being run by a non-bash sh (C2). Minimal containers frequently have no bash; their /bin/sh is dash or busybox. Somewhere, the container invokes the script as sh report.sh, or the script says #!/bin/sh while using bashisms.
Work the steps: head -1 report.sh — if it says #!/bin/sh, the script lied about its needs; on Ubuntu that lie is forgiven only when features happen to overlap. In the container: ls -l /bin/sh (busybox or dash) and which bash (likely nothing).
Fix: two honest options. Either declare the truth — #!/bin/bash — and install bash in the container image; or make the script actually POSIX — replace bashisms with portable equivalents and keep #!/bin/sh, testing with sh report.sh. The wrong option is what caused the ticket: a shebang that does not match the features used. (Silent wrong output, notice, is worse than the crash tickets 1–3 produced — it can flow downstream into reports before anyone notices.)
E4. Documentation reference
| Topic | Authoritative reference | What you'll find there |
|---|---|---|
| bash itself | bash(1) — man7.org | The complete manual: invocation, builtins (type, cd), command search order |
| PATH and other core variables | Bash manual — Bourne Shell Variables | PATH's exact definition and the other variables bash inherits from sh |
| source / . | Bash manual — Bourne Shell Builtins | Precise semantics of sourcing a file in the current shell |
| The shebang mechanism | execve(2) — man7.org | Kernel-level "Interpreter scripts" section: how #! is actually processed, with limits |
| Permissions | chmod(1) — man7.org · ls(1) — man7.org | Symbolic (+x, u+x) and octal permission syntax; reading ls -l output |
| The other shell | dash(1) — man7.org | What Ubuntu's /bin/sh actually is, and what it deliberately leaves out |
| Diagnosing files | file(1) — man7.org · env(1) — man7.org | file for spotting CRLF and script types; env as used in portable shebangs |
E5. Self-assessment
Answer from memory, out loud or on paper. Every answer is in this module.
- Terminal, shell, kernel — one sentence each: which does what when you type ls and press Enter?
- type cd and type ls give different kinds of answers. What are they, and what is the difference?
- In what order, and where, does bash look for a command typed by bare name? What is deliberately missing from that search?
- Why does ./script.sh need the ./ — what attack does the missing-current-directory rule prevent?
- Who reads the shebang line — bash or the kernel — and in which of the three run methods is it ignored?
- Your script fails with Permission denied. What single command diagnoses it, and what single command fixes it?
- A colleague's script works as bash deploy.sh but fails as ./deploy.sh with cannot execute: required file not found. Name the two most likely causes and the command that distinguishes them.
- What is the difference between executing and sourcing a file, and why does a cd inside a script not move your terminal?
- /bin/sh and /bin/bash — why might the same script behave differently under each, and what does ls -l /bin/sh tell you on Ubuntu?
- When would you write #!/usr/bin/env bash instead of #!/bin/bash, and what trade-off does it carry?
- Where do production systems install scripts that should be runnable by bare name — machine-wide, and per-user?
- What are CRLF line endings, which error do they cause, and which two commands make the invisible visible?
E6. Sources
InterviewBit — Top Shell Scripting Interview Questions — "What is Shell?", "Name different types of shells available.", "Write the command that is used to execute a shell file." (no publication date shown on page)
Edureka — Top 60 Shell Scripting Interview Questions and Answers — "What is Shell?", "What is a Shell Script?", "What is the significance of the Shebang line in Shell Scripting?", "What does it mean by #!/bin/sh or #!/bin/bash at the beginning of every script?" (page updated Dec 9, 2024)
KnowledgeHut — Shell Scripting Interview Questions and Answers — "What is a shell script and why is it used?", "What is the #! (shebang) used for in a shell script?", "How do you run a shell script?" (no publication date shown on page)
Zero To Mastery — Bash Interview Prep: 31 Essential Questions and Answers — "What is Bash?", "What is the difference between source and ./ when executing a script?" (published June 18, 2026)
Hirist — Top 30+ Shell Scripting Interview Questions and Answers — "How do you make a shell script executable?", "What is the purpose of shebang (#!) in shell scripts?" (published Jul 22, 2025; last modified Dec 31, 2025)
LinuxTeck — Write Your First Bash Script: 50 Essential Interview Questions — "What is the shebang line in a bash script?", "Why do we use #!/bin/bash instead of just #!/bin/sh?", "What file extension should a bash script use?", "Why does ./myscript.sh sometimes return 'permission denied'?", "How do you make a script executable?", "Why does running myscript.sh without ./ often fail?", "How do you write a comment in a bash script?" (last updated July 6, 2026)
The published corpus for this module's topics is deep — no questions were invented. All expected-result outputs in this module were produced by actually running the commands on Ubuntu 24.04 with bash 5.2; where your machine will differ (usernames, dates, PATH contents, /bin/sh target), the expected-result notes say so.