CommandsIntermediate7-9 min reference
Linux & CLI for Testers
The 95% of shell commands you actually use as a tester — slicing log files, hitting APIs, finding what's running on what port, and stitching it all together with pipes.
File & Directory Operations
Listing
ls -la # all files (incl. dotfiles), long format
ls -lh # human-readable sizes (1.2K, 4.5M)
ls -lt # sort by modified time, newest first
ls -lS # sort by size, largest first
ls -R # recursiveNavigation
cd ~ # home directory
cd - # previous directory
pwd # print working directory
mkdir -p path/to/dir # create nested directoriesCopy, move, remove
cp file dest
cp -r dir dest # recursive (directories)
cp -p file dest # preserve mode/timestamp
mv source dest # move or rename
rm file
rm -i file # prompt before removing
rm -rf dir # CAREFUL — recursive force-remove
rm -rfis irreversible. Always run with the relative path you mean —rm -rf $VAR/with an unsetVARdeletes from/.
Find files
find . -name "*.log" -type f # by name
find . -iname "*.LOG" # case-insensitive
find . -type d -name "node_modules" # directories only
find . -mtime -7 # modified in last 7 days
find . -mtime +30 # modified more than 30 days ago
find . -size +100M # files over 100 MB
find . -size -1k # files under 1 KB
find . -name "*.tmp" -delete # match + delete (test with -print first!)
find . -name "*.png" -size +1M -exec ls -lh {} \;Disk usage
du -sh dir # total size, human-readable
du -sh */ | sort -h # sizes of subdirectories, sorted
df -h # disk free, all mounts
df -h . # disk for current pathTree
tree -L 2 # depth 2 (install via Homebrew/apt)
tree -I 'node_modules|.git|.next' # ignore patternsSymbolic links
ln -s /actual/path link-name
readlink link-nameText Processing & Search
View files
cat file.txt
head -n 20 file.txt # first 20 lines
tail -n 50 file.txt # last 50 lines
tail -f file.log # follow (live tail)
tail -F file.log # follow + reopen on rotation
less file.log # paged viewer (q to quit, /text to search)grep
grep "ERROR" app.log
grep -r "TODO" src/ # recursive search in directory
grep -i "error" app.log # case-insensitive
grep -n "ERROR" app.log # show line numbers
grep -c "ERROR" app.log # count matches
grep -l "ERROR" *.log # filenames only
grep -v "DEBUG" app.log # invert — exclude
grep -A 3 "ERROR" app.log # 3 lines after each match
grep -B 3 "ERROR" app.log # 3 lines before
grep -C 3 "ERROR" app.log # 3 lines around (context)
grep -E "ERROR|WARN" app.log # extended regex (alternation)
grep -P "\d{3}-\d{4}" file.txt # Perl-compatible regex
grep -F "literal.string" file.txt # fixed-string (no regex)
grep --include="*.log" -r "ERROR" . # only files matching patternsed — find/replace
sed 's/old/new/g' file # print to stdout, all matches
sed -i 's/old/new/g' file # in-place (Linux)
sed -i '' 's/old/new/g' file # in-place (macOS — note empty arg)
sed -n '10,20p' file # print lines 10–20
sed '/^#/d' config.ini # delete comment lines
sed '5d' file # delete line 5awk — column processing
awk '{print $1}' file # first column (whitespace-separated)
awk '{print $1, $3}' file # 1st and 3rd columns
awk -F',' '{print $2}' data.csv # comma-separated
awk -F'\t' '{print $NF}' file # last column (NF = number of fields)
awk '$3 > 100 {print $1}' file # row filter on column 3
awk 'NR==1 || /pattern/' file # print header + matching rows
awk '{sum += $2} END {print sum}' file # sum a columnsort, uniq, wc
sort file # alphabetical
sort -n file # numeric
sort -r file # reverse
sort -u file # unique-only
sort -t',' -k2 file.csv # by 2nd CSV column
sort -k1,1 -k2n,2 file # primary alpha, secondary numeric
uniq file # remove ADJACENT duplicates (sort first!)
sort file | uniq # full dedupe
sort file | uniq -c # count occurrences
sort file | uniq -c | sort -rn # top occurrences first
sort file | uniq -d # only duplicates
wc -l file # line count
wc -w file # word count
wc -c file # byte countcut, paste, diff
cut -d',' -f2 file.csv # 2nd field of CSV
cut -d':' -f1 /etc/passwd # usernames
cut -c1-10 file # first 10 characters per line
paste file1 file2 # join columns side-by-side
diff file1 file2
diff -u file1 file2 # unified format (like git diff)
diff -y file1 file2 # side-by-side
diff -r dir1 dir2 # recursivePiping & Redirection
command > file # write stdout (overwrite)
command >> file # append stdout
command 2> err.log # redirect stderr only
command > all.log 2>&1 # both stdout and stderr to one file
command &> all.log # bash shorthand for the same thing
command < input.txt # send input.txt as stdin
cmd1 | cmd2 # pipe stdout of cmd1 to stdin of cmd2
cmd1 | tee file | cmd2 # pipe AND save to fileUseful chains
# Top 10 IPs hitting an endpoint
awk '$7 == "/login"' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
# Count error types in a log
grep "ERROR" app.log | awk '{print $4}' | sort | uniq -c | sort -rn
# Find the 5 largest files in a project
find . -type f -not -path '*/.git/*' -exec du -h {} + | sort -rh | head -5
# All unique URLs in an access log
grep -oE 'GET [^ ]+' access.log | sort -u
# Group log entries by hour
awk '{print substr($1, 1, 13)}' app.log | sort | uniq -cNetworking & HTTP
curl
# Basic
curl https://api.example.com/users
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada"}'
# Auth
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me
curl -u user:pass https://api.example.com/secure
# Save response
curl -o users.json https://api.example.com/users
curl -O https://example.com/file.zip # save with original filename
# Verbose / silent
curl -v https://api.example.com # show full request + response
curl -s https://api.example.com # silent (no progress bar)
curl -sf https://api.example.com # silent + fail on HTTP error
# Show only what you need
curl -o /dev/null -w "%{http_code}\n" url
curl -o /dev/null -w "%{http_code} %{time_total}s\n" url
curl -I https://example.com # HEAD request, headers only
# Form data / file upload
curl -F "file=@./test.csv" https://api.example.com/import
curl -F "name=Ada" -F "email=ada@example.com" https://api.example.com/users
# Follow redirects
curl -L https://example.com
# Cookies
curl -c cookies.txt -b cookies.txt https://example.comwget
wget https://example.com/file.zip
wget -O renamed.zip https://example.com/file.zip
wget -r -np -nH --cut-dirs=2 https://example.com/path/ # mirrorDNS & connectivity
ping host.example.com
ping -c 5 host.example.com # stop after 5 pings
nslookup api.example.com # basic DNS lookup
dig api.example.com # detailed (preferred)
dig +short api.example.com # just the IPs
dig MX example.com # mail records
traceroute api.example.com # path to host
mtr api.example.com # interactive traceroute + pingPorts
# What's listening?
ss -tlnp # modern (Linux)
netstat -tlnp # older
lsof -i :3000 # what's using port 3000
lsof -i -P | grep LISTEN
# Test if a port is reachable
nc -zv host.example.com 443 # one-shot port check
telnet host.example.com 443
# Open a quick test server
python3 -m http.server 8000 # serve current dir on :8000Process Management
ps aux # all processes, full info
ps aux | grep node # processes whose command contains 'node'
pgrep -a node # cleaner — match by name
top # interactive
htop # nicer interactive (install separately)
kill 12345 # send SIGTERM
kill -9 12345 # SIGKILL — only when SIGTERM doesn't work
kill -HUP 12345 # ask process to reload config
pkill -f "node server.js" # kill by command pattern
killall chrome
# What's holding a port hostage?
lsof -i :4200
fuser -v 4200/tcp # Linux
# Background jobs
sleep 60 & # run in background
nohup long-script.sh & # survives terminal close
jobs # list jobs in this shell
fg %1 # foreground job 1
bg %1 # resume in background
disown # detach from shellKeyboard interrupts
| Key | Effect |
|---|---|
Ctrl-C | SIGINT — stop the foreground process |
Ctrl-Z | SIGTSTP — suspend the foreground process |
Ctrl-D | EOF — close stdin / exit shell |
Ctrl-L | Clear screen (same as clear) |
Ctrl-R | Reverse history search |
Ctrl-A / Ctrl-E | Move to start/end of line |
Environment & Configuration
echo $PATH
echo $HOME
echo "$USER on $(hostname)"
env # all environment variables
printenv API_TOKEN
unset API_TOKEN
# Set for current shell only
export API_TOKEN="abc123"
# Run a single command with extra env
NODE_ENV=test API_TOKEN=abc npm test
# Source a file into the current shell (without running as a child process)
source .env
. .env # POSIX-portable spelling
# Find a command's executable
which node
type ls # tells you if it's an alias/builtin/fileAliases
alias ll='ls -lah'
alias gs='git status'
alias serve='python3 -m http.server'
# Make permanent — add to ~/.bashrc or ~/.zshrcPermissions
chmod +x script.sh # make executable
chmod 755 file # rwxr-xr-x
chmod 644 file # rw-r--r--
chmod -R 755 dir # recursive
chown user:group file
chown -R me:staff project/
# Read what permissions look like
ls -l file
# -rwxr-xr-x 1 me staff 1234 May 03 10:00 fileArchives & Compression
# tar — Linux/macOS standard
tar -czf archive.tar.gz dir/ # create gzip archive
tar -cjf archive.tar.bz2 dir/ # bzip2 (smaller, slower)
tar -xzf archive.tar.gz # extract gzip
tar -xjf archive.tar.bz2 # extract bzip2
tar -tzf archive.tar.gz # list contents
tar -xzf archive.tar.gz -C /target/dir/ # extract to specific dir
# zip
zip -r archive.zip dir/
unzip archive.zip
unzip -l archive.zip # list contents
unzip archive.zip -d target-dir/
# gzip / gunzip — single files
gzip file # → file.gz, original removed
gunzip file.gz
zcat file.log.gz # cat a gzipped log
zgrep "ERROR" file.log.gz # grep a gzipped logSSH & Remote
ssh user@host
ssh -i ~/.ssh/key.pem user@host
ssh -p 2222 user@host
ssh -L 5432:localhost:5432 user@host # local port forward
ssh user@host "tail -100 /var/log/app.log" # run a single command
# File transfer
scp file user@host:/remote/path/
scp -r dir user@host:/remote/path/ # recursive
scp user@host:/remote/path/file ./ # download
rsync -avz --progress dir/ user@host:/path/ # rsync — incremental, resume-able
# SSH keys
ssh-keygen -t ed25519 -C "you@example.com"
ssh-copy-id user@host # install your key on the remote~/.ssh/config lets you skip flags:
Host staging
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_ed25519
Port 22
# Now: ssh staging
QA-Specific CLI Tasks
Tail a log for errors only
tail -f app.log | grep --line-buffered -E "ERROR|FATAL"--line-buffered is critical when piping grep to a file — without it grep buffers and you see nothing for a while.
Count test failures in a report
grep -c "FAILED" test-report.txt
grep -E "FAILED|ERROR" test-report.txt | sort | uniq -c | sort -rnExtract all URLs from a log
grep -oE 'https?://[^ "]+' access.log | sort -uHealth-check loop
while true; do
if curl -sf https://api.example.com/health > /dev/null; then
echo "$(date +%H:%M:%S) UP"
else
echo "$(date +%H:%M:%S) DOWN"
fi
sleep 5
doneMeasure response time
# One-shot — print just total time
curl -s -o /dev/null -w "%{time_total}\n" https://api.example.com/users
# Watch over time
while true; do
t=$(curl -s -o /dev/null -w "%{time_total}" https://api.example.com/users)
echo "$(date +%H:%M:%S) ${t}s"
sleep 2
doneFind large test artifacts
find . -type f \( -name "*.png" -o -name "*.mp4" \) -size +1M -exec du -h {} \; | sort -rh | headClean old test outputs
find . -name "*.log" -mtime +30 -delete
find . -name "*.png" -mtime +14 -delete
find . -type d -name "test-results-*" -mtime +7 -exec rm -rf {} +Check a port is free before starting tests
lsof -i :4200 > /dev/null && echo "port busy" || echo "port free"
# Or: bail out if busy
lsof -i :4200 > /dev/null && { echo "Port 4200 in use"; exit 1; }Pretty-print JSON
cat response.json | python3 -m json.tool
cat response.json | jq # if you have jq installed
curl -s https://api.example.com/users | jq '.[] | {id, email}'Quick CSV peek
head -5 data.csv | column -t -s',' # aligned columns
csvkit-csvlook data.csv # if csvkit installed
mlr --c2p cat data.csv | head # Miller — modern data CLIWatch a value change
watch -n 2 'curl -s https://api.example.com/queue/depth | jq .pending'