On this page9 sections
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                  # recursive
cd ~                   # home directory
cd -                   # previous directory
pwd                    # print working directory
mkdir -p path/to/dir   # create nested directories

Copy, 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 -rf is irreversible. Always run with the relative path you mean — rm -rf $VAR/ with an unset VAR deletes 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 path

Tree

tree -L 2                                 # depth 2 (install via Homebrew/apt)
tree -I 'node_modules|.git|.next'         # ignore patterns
ln -s /actual/path link-name
readlink link-name

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 pattern

sed — 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 5

awk — 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 column

sort, 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 count

cut, 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                         # recursive

Piping & 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 file

Useful 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 -c

Networking & 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.com

wget

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/   # mirror

DNS & 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 + ping

Ports

# 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 :8000

Process 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 shell

Keyboard interrupts

KeyEffect
Ctrl-CSIGINT — stop the foreground process
Ctrl-ZSIGTSTP — suspend the foreground process
Ctrl-DEOF — close stdin / exit shell
Ctrl-LClear screen (same as clear)
Ctrl-RReverse history search
Ctrl-A / Ctrl-EMove 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/file

Aliases

alias ll='ls -lah'
alias gs='git status'
alias serve='python3 -m http.server'
 
# Make permanent — add to ~/.bashrc or ~/.zshrc

Permissions

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 file

Archives & 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 log

SSH & 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 -rn

Extract all URLs from a log

grep -oE 'https?://[^ "]+' access.log | sort -u

Health-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
done

Measure 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
done

Find large test artifacts

find . -type f \( -name "*.png" -o -name "*.mp4" \) -size +1M -exec du -h {} \; | sort -rh | head

Clean 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 CLI

Watch a value change

watch -n 2 'curl -s https://api.example.com/queue/depth | jq .pending'