From running commands to building reliable Linux tools: shebang, variables, command substitution, quoting, arguments, and wrapper patterns.
This lab turns a small real wrapper script into a full shell-scripting mental model. The main idea is that shell scripting is often not about writing a large program. It is about building a reliable interface that connects commands, files, paths, and user input.
#!/bin/sh
# Convenience wrapper for Linux command helper.
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
python3 "$SCRIPT_DIR/linux_cmd_tool_v2_1.py" quick "$@"
A shell script is a small automation layer that connects commands together.
Think:
β’ Shell = glue / interface / orchestration
β’ Python = logic / data processing / engine
β’ Filesystem = where tools and data live
| Question | Shell concept | Example |
|---|---|---|
| Who runs this script? | Shebang | #!/bin/sh |
| Where is this script located? | $0 + dirname + pwd | SCRIPT_DIR=... |
| How do I store a value? | Variable assignment | SCRIPT_DIR="..." |
| How do I run a command and capture output? | Command substitution | $(pwd) |
| How do I pass user input forward? | Arguments | "$@" |
| How do I avoid path bugs? | Absolute path from script directory | python3 "$SCRIPT_DIR/tool.py" |
User types a simple command β shell wrapper receives it β Python tool processes it β output helps your work.
This is exactly how a personal CLI system starts.
#!/bin/sh
# Convenience wrapper for Linux command helper.
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
python3 "$SCRIPT_DIR/linux_cmd_tool_v2_1.py" quick "$@"
It lets you type:
linuxcmd usb
and internally translates it into:
python3 /path/to/linux_cmd_tool_v2_1.py quick usb
#!/bin/sh
This tells Linux:
βRun this file using /bin/sh.β
Without it, the script may run under whatever shell called it, which is less predictable.
| Choice | Meaning | Use case |
|---|---|---|
| #!/bin/sh | POSIX shell, portable | Company Linux, simple wrappers |
| #!/bin/bash | Bash shell, more features | When you need bash-only syntax |
| #!/usr/bin/env python3 | Run Python from PATH | Python scripts |
For portable company scripts, start with #!/bin/sh unless you really need bash features.
# Convenience wrapper for Linux command helper.
Lines starting with # are comments. They are ignored by the shell.
Good comments explain why the script exists, not just what each command does.
SCRIPT_DIR="..."
Shell variables are simple strings.
Important:
β’ No spaces around =
β’ Use quotes around values
β’ Read the value with $SCRIPT_DIR
| Correct | Wrong | Why |
|---|---|---|
| SCRIPT_DIR="/usr/local/bin" | SCRIPT_DIR = "/usr/local/bin" | Spaces around = break assignment |
| echo "$SCRIPT_DIR" | echo $SCRIPT_DIR | Unquoted version can break on spaces |
| python3 "$SCRIPT_DIR/tool.py" | python3 $SCRIPT_DIR/tool.py | Quoted version is safer |
VALUE="$(command)"
$(...) means:
1) run the command inside
2) capture its printed output
3) store/use that output as text
CURRENT_DIR="$(pwd)"
echo "$CURRENT_DIR"
Always prefer:
VALUE="$(command)"
instead of:
VALUE=$(command)
The quoted version is safer if output contains spaces.
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
| Piece | Meaning |
|---|---|
| $0 | Path used to call the script |
| dirname -- "$0" | Extract directory part of the script path |
| cd -- "$(dirname -- "$0")" | Move into the script directory |
| pwd | Print the absolute path |
| CDPATH= | Disable CDPATH side effects so cd behaves predictably |
| && | Only run pwd if cd succeeded |
Compute the absolute directory where this wrapper script lives, then store it in SCRIPT_DIR.
Without SCRIPT_DIR, the wrapper may only work if you run it from the same directory.
With SCRIPT_DIR, the wrapper works from anywhere.
| If user runs | $0 may be | dirname -- "$0" gives |
|---|---|---|
| ./linuxcmd | ./linuxcmd | . |
| /usr/local/bin/linuxcmd | /usr/local/bin/linuxcmd | /usr/local/bin |
| ../tools/linuxcmd | ../tools/linuxcmd | ../tools |
$0 tells you how the script was called.
dirname extracts the folder from that path.
cd + pwd converts that folder into an absolute location.
CDPATH= cd -- "$(dirname -- "$0")"
CDPATH is an environment variable that can change how cd searches for directories.
Setting CDPATH= only for this command disables that behavior.
This avoids surprising output or wrong directory resolution on systems where CDPATH is configured.
It is a small detail, but it makes the wrapper more reliable.
dirname -- "$0"
cd -- "some/path"
-- means:
βStop parsing options after this point.β
This protects commands if a filename begins with a dash, such as -test.
If a variable contains a path or user input, quote it.
Use:
"$SCRIPT_DIR"
"$0"
"$@"
| Unquoted | Quoted | Why quoted is safer |
|---|---|---|
| $SCRIPT_DIR | "$SCRIPT_DIR" | Preserves spaces in paths |
| $0 | "$0" | Preserves script path as one argument |
| $@ | "$@" | Preserves each user argument separately |
| $(pwd) | "$(pwd)" | Preserves command output as one string |
"$@"
"$@" means:
Pass every argument exactly as received.
If user types:
linuxcmd previous directory
then "$@" forwards:
"previous" "directory"
| Variable | Meaning |
|---|---|
| $0 | Script name/path |
| $1 | First argument |
| $2 | Second argument |
| $# | Number of arguments |
| $@ | All arguments |
| "$@" | All arguments, safely preserved |
python3 "$SCRIPT_DIR/linux_cmd_tool_v2_1.py" quick "$@"
| Piece | Role |
|---|---|
| python3 | Interpreter for the Python tool |
| "$SCRIPT_DIR/linux_cmd_tool_v2_1.py" | Absolute path to the Python engine |
| quick | Subcommand passed to Python |
| "$@" | All user-provided search words |
User types:
linuxcmd usb mount
Wrapper runs:
python3 /path/to/linux_cmd_tool_v2_1.py quick usb mount
Shell wrapper = convenient command interface
Python script = real logic and data processing
#!/bin/sh
# Wrapper for a local Python tool.
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
python3 "$SCRIPT_DIR/YOUR_TOOL.py" "$@"
Replace YOUR_TOOL.py with your Python script name.
If your Python tool needs a default subcommand, add it before "$@".
python3 "$SCRIPT_DIR/YOUR_TOOL.py" quick "$@"
| Mistake | Example | Fix |
|---|---|---|
| Spaces around = | SCRIPT_DIR = value | SCRIPT_DIR=value |
| Not quoting variables | python3 $SCRIPT_DIR/tool.py | python3 "$SCRIPT_DIR/tool.py" |
| Using $@ unquoted | $@ | "$@" |
| Assuming current directory | python3 tool.py | python3 "$SCRIPT_DIR/tool.py" |
| Using bash syntax in sh | [[ "$x" == yes ]] | Use [ "$x" = yes ] or change shebang to bash |
| No execute permission | ./linuxcmd gives permission denied | chmod +x linuxcmd |
If a shell script fails, first print:
echo "$0"
echo "$SCRIPT_DIR"
echo "$@"
This reveals path and argument problems quickly.
Create hello.sh:
#!/bin/sh
echo "Hello from shell"
Then run:
chmod +x hello.sh
./hello.sh
Create args.sh:
#!/bin/sh
echo "script: $0"
echo "first : $1"
echo "all : $@"
echo "safe : $@"
Then run:
./args.sh previous directory
Create a script that prints its own directory:
#!/bin/sh
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
echo "$SCRIPT_DIR"
Run it from a different directory and confirm the output stays correct.
Click each card to reveal the answer. These preserve the original labβs recall practice.
The original glossary is preserved below as quick reference vocabulary.
The first line like #!/bin/sh that selects the interpreter.
Command interpreter that runs commands and scripts.
Portable shell standard supported on many Unix/Linux systems.
Named string value, e.g. SCRIPT_DIR="/path".
$(command), which runs command and substitutes its output.
Input word passed to a script or command.
Script name/path used to call the script.
All arguments passed to the script.
All arguments safely preserved as separate arguments.
Command that extracts directory portion of a path.
Environment variable affecting cd search behavior.
Small script that calls another tool with a simpler interface.
The wrapper gives users a simple command. The shell handles location and arguments. Python performs the deeper logic. This is the core pattern behind many practical engineering tools.