Skip to main content

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
PartMeaning
lsthe command
-laoptions (flags) that change its behavior
/home/peterthe 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

CommandPurpose
pwdPrint the current working directory
lsList files in the current directory
ls -laList all files (including hidden), with permissions, owner, size, and date
cd DocumentsMove 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
warning

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

CommandPurpose
cat file.txtPrint the whole file to the terminal
less file.txtPage through a file (Space = next page, b = back, /text = search, q = quit)
head file.txtFirst 10 lines
head -20 file.txtFirst 20 lines
tail file.txtLast 10 lines
tail -f application.logFollow 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 valueMeaningTypical use
755Owner full access, everyone else read + executeExecutable scripts
644Owner read/write, everyone else read-onlyConfig and text files
600Owner read/write, no one else has any accessSecrets, 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:

TestMeaning
[ "$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

SymptomLikely causeWhat to check
command not foundProgram isn't installed, or isn't on PATHwhich <command>; confirm install; check echo $PATH
Permission denied running a scriptScript isn't executablechmod +x script.sh
Permission denied on a file/dirWrong owner or models -l the path; chmod/chown as needed — never blanket chmod 777 as a fix
Script "does nothing" on failureMissing set -e, or error went to stderr and was swallowedAdd set -e; redirect with 2>&1 to confirm where output is actually going
Variable is empty inside a called scriptForgot exportRe-declare with export VAR=value in the parent shell
.bashrc changes don't take effectNew terminal wasn't opened, or you're actually on zshsource ~/.bashrc; check echo $SHELL — macOS defaults to zsh, which reads ~/.zshrc instead
rm -rf ran on the wrong pathPath built from an unchecked variableAlways echo or ls a dynamically-built path before passing it to rm -rf
Command hangs indefinitelyWaiting on stdin it never receivesCheck 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 CIDifferent DNS, proxy, or firewall context in the pipelineRun the same curl -v inside the pipeline agent, not just locally

16. Quick Reference

CategoryCommandWhat it does
NavigationpwdShow current directory
Navigationcd -Go to previous directory
Filestouch fileCreate empty file
Filescp -r a bCopy directory
Filesmv a bMove or rename
Filesrm -rf dirForce delete directory
Viewingless filePage through file
Viewingtail -f fileFollow file live
Searchgrep -rn "text" .Recursive search with line numbers
Searchfind . -name "*.log"Find files by name
Permissionschmod +x fileMake executable
Permissionschmod 600 fileOwner-only read/write
Envexport VAR=valueSet variable for child processes
Redirectioncmd > out.txt 2>&1Redirect stdout and stderr together
Processesps aux | grep nameFind a running process
Processeskill -9 PIDForce-kill a process
Diskdu -sh *Size of each item, human-readable
Networkcurl -I urlFetch headers only
SSHssh-keygen -t ed25519Generate a modern SSH key
Scriptingset -eAbort 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.