Bash Reference
Volume 1 — Core Developer Skills
Bash (Bourne Again SHell) is the command-line shell used on most Linux servers, macOS, and inside WSL/containers on Windows. If you touch Azure App Service Log Stream, a Docker container, a CI/CD pipeline script, or a .sh deploy script, you're working in Bash. This chapter is a complete, standalone reference — read it top to bottom once, then use it as a lookup afterward.
1. How a Bash Command Is Built
Every Bash command follows the same shape:
command [options] [arguments]
ls -la /home/peter
| Part | Meaning |
|---|---|
ls | the command |
-la | options (flags) that change its behavior |
/home/peter | the argument the command acts on |
Options can usually be combined (-l -a is the same as -la), and most commands support a long form (--all instead of -a). When you're not sure what a command does, man <command> (e.g. man ls) opens its manual page; q quits.
2. Navigation
| Command | Purpose |
|---|---|
pwd | Print the current working directory |
ls | List files in the current directory |
ls -la | List all files (including hidden), with permissions, owner, size, and date |
cd Documents | Move into a subdirectory |
cd .. | Move up one level |
cd ~ | Jump to your home directory |
cd - | Jump back to the previous directory |
cd / | Jump to the filesystem root |
Hidden files and directories (like .bashrc or .git) start with a dot and are only shown when you pass -a.
3. File and Directory Management
touch myfile.txt # create an empty file (or update its timestamp if it exists)
mkdir Projects # create a directory
mkdir -p Projects/App1/Docs # create nested directories in one shot, no error if they exist
cp source.txt destination.txt # copy a file
cp -r folder1 folder2 # copy a directory recursively
mv old.txt new.txt # rename a file
mv file.txt Documents/ # move a file into a directory
rm file.txt # delete a file
rm -r folder # delete a directory and its contents
rm -rf folder # force delete, no confirmation, no error if missing
rm -rf does not ask for confirmation and does not use a trash bin — the data is gone. Before running it, run the same path with ls first to confirm exactly what you're about to delete, especially if the path came from a variable or a script argument.
4. Viewing File Contents
| Command | Purpose |
|---|---|
cat file.txt | Print the whole file to the terminal |
less file.txt | Page through a file (Space = next page, b = back, /text = search, q = quit) |
head file.txt | First 10 lines |
head -20 file.txt | First 20 lines |
tail file.txt | Last 10 lines |
tail -f application.log | Follow a file live as new lines are appended — this is the command you want when watching a log during a deploy |
less is almost always the right choice over cat for anything longer than a screen — cat-ing a huge log file just floods your terminal.
5. Searching: grep and find
grep searches inside file contents. find searches for files by name or attribute.
grep "error" app.log # find lines containing "error"
grep -i "error" app.log # case-insensitive
grep -r "connectionstring" . # search recursively through every file in the current directory
grep -n "TODO" *.cs # show line numbers
grep -v "debug" app.log # show lines that do NOT match
find . -name "*.log" # find files by name pattern, starting from current directory
find . -type d # find only directories
find . -type f -mtime -1 # files modified in the last day
find . -name "*.tmp" -delete # find and delete matching files — confirm the find alone first
A common real-world combination: find every .env file in a repo tree to make sure none were committed by mistake:
find . -name ".env" -not -path "*/node_modules/*"
6. Permissions
ls -l
-rwxr-xr-x 1 peter staff 1024 Jul 20 09:12 deploy.sh
The first ten characters break down as:
- rwx r-x r-x
^ ^ ^ ^
type owner group other
r = read, w = write, x = execute. A d in the first position means it's a directory instead of a file.
chmod +x deploy.sh # make a script executable
chmod 755 script.sh # owner: rwx, group: r-x, other: r-x
chmod 644 config.json # owner: rw-, group: r--, other: r--
chmod 600 secrets.env # owner: rw-, nobody else can read or write it
| Numeric value | Meaning | Typical use |
|---|---|---|
755 | Owner full access, everyone else read + execute | Executable scripts |
644 | Owner read/write, everyone else read-only | Config and text files |
600 | Owner read/write, no one else has any access | Secrets, private keys |
The number is three digits (owner, group, other), and each digit is the sum of 4 (read) + 2 (write) + 1 (execute).
7. Environment Variables
Environment variables carry configuration into the programs you run — this is exactly the mechanism behind Azure App Service settings like ConnectionStrings__Default.
echo $HOME # print a variable's value
export API_URL="https://api.roundtrips.app" # set it for this shell session and anything it launches
env # list all environment variables
unset API_URL # remove a variable
export matters: a plain NAME="Peter" only exists inside the current shell. export NAME="Peter" makes it visible to any program the shell launches — this is why a variable set without export won't show up inside a script you call afterward.
To persist a variable across every new terminal session, add the export line to ~/.bashrc (or ~/.zshrc on modern macOS, whose default shell is zsh) and open a new terminal, or run source ~/.bashrc to reload it immediately.
8. Pipes and Redirection
Pipes and redirection are what make the shell composable — you chain small, single-purpose commands into one pipeline instead of writing a program to do it.
ps aux | grep node # pipe: feed one command's output into the next
command > file.txt # redirect stdout, overwriting file.txt
command >> file.txt # redirect stdout, appending to file.txt
command 2> errors.txt # redirect stderr only
command > out.txt 2>&1 # redirect both stdout and stderr to the same file
command < input.txt # feed a file in as stdin
Every process has three standard streams: stdin (0, input), stdout (1, normal output), and stderr (2, errors). This is why a command can appear to "work" in the terminal but produce no output in a log file — if you only redirected stdout and the failure went to stderr, you redirected the wrong stream.
9. Process Management
ps aux # list all running processes
ps aux | grep node # find a specific process
top # live, updating view of CPU/memory usage per process
kill PID # ask a process to terminate gracefully
kill -9 PID # force-kill a process that won't respond to a normal kill
kill -9 (SIGKILL) doesn't give the process a chance to clean up open files or connections — use a plain kill (SIGTERM) first and only escalate to -9 if the process is genuinely hung.
10. Disk Usage
df -h # free/used space per mounted disk, human-readable
du -sh * # size of each item in the current directory, human-readable
du -sh . # total size of the current directory
du -sh node_modules before deleting a directory you suspect is huge is a good habit — it tells you what you're about to reclaim.
11. Basic Networking from the Shell
ping google.com # test basic reachability
nslookup google.com # DNS lookup
dig google.com # DNS lookup with more detail
curl -I https://api.roundtrips.app # fetch just the response headers, useful for checking an API is up
curl -v https://api.roundtrips.app/health # verbose request/response, useful for debugging TLS or redirect issues
netstat -an # list open network connections (Linux)
lsof -i # list open network connections (macOS alternative to netstat)
A DNS or connectivity deep dive belongs in a dedicated Networking chapter — this section is deliberately scoped to the handful of commands you reach for daily while developing.
12. SSH Basics
ssh user@server # connect to a remote server
scp report.txt user@server:/home/user # copy a file to a remote server
ssh-keygen -t ed25519 # generate a new SSH key pair
Full key management, agent forwarding, and hardening are covered in the dedicated SSH & Key Management chapter — this is the minimum needed to connect to a box and move a file.
13. Bash Scripting Fundamentals
Script structure
Every script should start with a shebang line, which tells the OS which interpreter to run it with:
#!/bin/bash
echo "Hello World"
Make it executable and run it:
chmod +x hello.sh
./hello.sh
Variables
NAME="Peter"
echo $NAME
echo "${NAME}" # braces are safer when concatenating: "${NAME}_backup"
No spaces around = — NAME = "Peter" is a syntax error, not an assignment.
User input
read -p "Enter your name: " NAME
echo "Hello $NAME"
Command-line arguments
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "All arguments: $@"
echo "Argument count: $#"
./deploy.sh production
If statements
if [ "$NAME" = "Peter" ]; then
echo "Welcome"
elif [ "$NAME" = "Robin" ]; then
echo "Welcome, CMO"
else
echo "Unknown user"
fi
Common test conditions:
| Test | Meaning |
|---|---|
[ "$A" = "$B" ] | strings equal |
[ "$A" != "$B" ] | strings not equal |
[ -z "$A" ] | string is empty |
[ -n "$A" ] | string is not empty |
[ "$A" -eq "$B" ] | numbers equal |
[ "$A" -gt "$B" ] | number A greater than B |
[ -f "$FILE" ] | file exists and is a regular file |
[ -d "$DIR" ] | directory exists |
Loops
# For loop
for file in *.txt
do
echo "$file"
done
# While loop
COUNT=1
while [ $COUNT -le 5 ]
do
echo "Count: $COUNT"
COUNT=$((COUNT + 1))
done
Functions
deploy() {
local env=$1
echo "Deploying to $env..."
}
deploy "production"
Exit codes and error handling
Every command returns an exit code: 0 means success, anything else means failure. Check it with $?, and use it to stop a script early on failure:
#!/bin/bash
set -e # exit immediately if any command fails
dotnet build
if [ $? -ne 0 ]; then
echo "Build failed, aborting deploy"
exit 1
fi
echo "Build succeeded, deploying..."
set -e is worth putting at the top of nearly every deploy or automation script — without it, a failed step is silently ignored and the script keeps going as if nothing went wrong.
14. Practical Developer Workflows
Tail a live App Service log while reproducing an issue:
az webapp log tail --name roundtrip-api --resource-group traxs-prod
(This streams the same Log Stream output you'd otherwise watch in the Azure Portal — useful when a CORS error in the browser console is masking a real 500, since the actual exception only shows up server-side.)
Find every occurrence of a connection string pattern before a commit, to make sure nothing sensitive is staged:
git diff --cached | grep -i "connectionstring"
Check how much space a bloated node_modules or bin/obj tree is using before cleaning it up:
du -sh node_modules bin obj
rm -rf bin obj
Confirm a deployed API is actually reachable before debugging further up the stack:
curl -I https://api.roundtrips.app/health
15. Troubleshooting Playbook
| Symptom | Likely cause | What to check |
|---|---|---|
command not found | Program isn't installed, or isn't on PATH | which <command>; confirm install; check echo $PATH |
Permission denied running a script | Script isn't executable | chmod +x script.sh |
Permission denied on a file/dir | Wrong owner or mode | ls -l the path; chmod/chown as needed — never blanket chmod 777 as a fix |
| Script "does nothing" on failure | Missing set -e, or error went to stderr and was swallowed | Add set -e; redirect with 2>&1 to confirm where output is actually going |
| Variable is empty inside a called script | Forgot export | Re-declare with export VAR=value in the parent shell |
.bashrc changes don't take effect | New terminal wasn't opened, or you're actually on zsh | source ~/.bashrc; check echo $SHELL — macOS defaults to zsh, which reads ~/.zshrc instead |
rm -rf ran on the wrong path | Path built from an unchecked variable | Always echo or ls a dynamically-built path before passing it to rm -rf |
| Command hangs indefinitely | Waiting on stdin it never receives | Check whether the command expects piped input; Ctrl+C to cancel, then rerun with input file or -y/non-interactive flag |
curl succeeds locally but fails in CI | Different DNS, proxy, or firewall context in the pipeline | Run the same curl -v inside the pipeline agent, not just locally |
16. Quick Reference
| Category | Command | What it does |
|---|---|---|
| Navigation | pwd | Show current directory |
| Navigation | cd - | Go to previous directory |
| Files | touch file | Create empty file |
| Files | cp -r a b | Copy directory |
| Files | mv a b | Move or rename |
| Files | rm -rf dir | Force delete directory |
| Viewing | less file | Page through file |
| Viewing | tail -f file | Follow file live |
| Search | grep -rn "text" . | Recursive search with line numbers |
| Search | find . -name "*.log" | Find files by name |
| Permissions | chmod +x file | Make executable |
| Permissions | chmod 600 file | Owner-only read/write |
| Env | export VAR=value | Set variable for child processes |
| Redirection | cmd > out.txt 2>&1 | Redirect stdout and stderr together |
| Processes | ps aux | grep name | Find a running process |
| Processes | kill -9 PID | Force-kill a process |
| Disk | du -sh * | Size of each item, human-readable |
| Network | curl -I url | Fetch headers only |
| SSH | ssh-keygen -t ed25519 | Generate a modern SSH key |
| Scripting | set -e | Abort script on first error |
Part of the Traxs Engineering Handbook — Volume 1: Core Developer Skills. Companion chapters in this volume: Git Reference, Git Troubleshooting, SSH & Key Management.