Getting Started
Basic File Commands
ls -la shows all files including hidden ones. cd ~ goes home, cd - toggles to previous directory. mkdir -p creates parent directories as needed. rm -r is needed for non-empty directories.
# list files
ls -la # long format, all files
ls -lh # human-readable sizes
ls -lt # sort by modification time
# current directory
pwd
# change directory
cd /home/user
cd .. # go up one level
cd ~ # go home
cd - # go to previous directory
# create/remove directories
mkdir newdir
mkdir -p path/to/dir # create parents
rmdir emptydir
rm -r nonempty # remove recursivelyGetting Help
man is the primary documentation system. Sections: 1=commands, 2=syscalls, 3=library, 4=devices, 5=file formats, 8=administration. man -k (or apropos) searches descriptions. --help gives a concise summary. tldr provides practical examples and is installed separately (npm install -g tldr).
# manual pages
man ls # full documentation
man 5 crontab # section 5 (file formats)
man -k password # search by keyword (apropos)
# info pages (GNU tools, more detailed)
info coreutils
info ls
# quick help flags
command --help
ls --help
git --help
# tldr: community cheat sheets (install separately)
tldr tar
tldr find
# whatis: one-line description
whatis ls
# where is the binary / source / man page
whereis python3Command History & Shortcuts
!! repeats the last command (sudo !! is the classic 'forgot sudo' fix). !N runs command N from history. !$ is the last argument. Ctrl+R is the interactive reverse search — press it again to find earlier matches. History is stored in ~/.bash_history; HISTSIZE controls in-memory size, HISTFILESIZE controls the file.
# history expansion
history # show command history
!! # repeat last command
sudo !! # repeat last command as root
!42 # run command #42 from history
!ls # run last command starting with ls
!$ # last argument of last command
!!:gs/old/new/ # substitute in last command
# search history (interactive)
# Ctrl+R then type to search backward
# clear history
history -c # clear current session
# edit ~/.bashrc HISTSIZE and HISTFILESIZE
# save and reload history
history -a # append to history file
history -r # reload from fileFile Types & Identification
file inspects the magic bytes of a file to determine its true type — far more reliable than the extension. ls -F appends type indicators (* executable, / directory, @ symlink, = socket). stat shows complete inode metadata including all three timestamps: Access (atime), Modify (mtime, content), Change (ctime, metadata).
# identify file type by content (not extension)
file document.pdf # PDF document
file image.png # PNG image data
file script.sh # ASCII text executable
file /bin/ls # ELF 64-bit LSB executable
# file with multiple files
file *.txt
# show MIME type
file --mime-type video.mp4 # video/mp4
# ls -F shows type indicator
ls -F
# file* (executable)
# dir/ (directory)
# link@ (symlink)
# socket= (socket)
# stat: detailed file info
stat file.txt
# Size, Blocks, IO Block, Device, Inode, Links,
# Access/Modify/Change/Birth timesTerminal Navigation Shortcuts
These Readline shortcuts work in bash and most shells. Ctrl+A/E jump to line start/end. Ctrl+W/U/K delete text — Ctrl+Y pastes it back (kill ring). Ctrl+R searches history. Ctrl+L clears the screen. Ctrl+S freezes output (common 'frozen terminal' cause — Ctrl+Q resumes). Learn these to dramatically speed up command-line editing.
# cursor movement
Ctrl+A # move to beginning of line
Ctrl+E # move to end of line
Ctrl+B # move back one char
Ctrl+F # move forward one char
# editing
Ctrl+D # delete char under cursor (or EOF/exit)
Ctrl+H # delete char before cursor (backspace)
Ctrl+W # delete word before cursor
Ctrl+U # delete from cursor to beginning
Ctrl+K # delete from cursor to end
Ctrl+Y # paste (yank) deleted text back
# job control
Ctrl+C # send SIGINT (interrupt)
Ctrl+Z # suspend (send SIGTSTP)
Ctrl+D # end of input / exit shell
# screen
Ctrl+L # clear screen (same as clear)
Ctrl+S # stop output (freeze)
Ctrl+Q # resume output
# Alt shortcuts
Alt+B / Alt+F # move back/forward by word
Alt+Backspace # delete word backwardEnvironment Variables
Environment variables are inherited by child processes when exported. $PATH determines where the shell looks for commands — directories are searched in order separated by colons. ~/.bashrc is sourced for interactive non-login shells; ~/.profile or ~/.bash_profile for login shells. Always source the file after editing to apply changes. Use printenv VAR to check a single variable without expansion.
# view all environment variables
env
printenv
# view specific variable
echo $HOME
echo $PATH
echo $USER
# set a variable (current shell only)
MYVAR="hello"
echo $MYVAR
# export to child processes
export PATH="$PATH:/opt/bin"
export EDITOR=nano
# set and export in one step
export MYVAR="hello"
# unset a variable
unset MYVAR
# common variables
# $HOME user home directory
# $PATH command search path
# $USER current username
# $SHELL current shell path
# $PWD current working directory
# $OLDPWD previous directory
# $PS1 prompt string
# persistent: add to ~/.bashrc or ~/.profile
echo 'export EDITOR=vim' >> ~/.bashrc
source ~/.bashrcFile Operations
Copying Files (cp)
cp copies files. -r (or -R) is required for directories. -i prompts before overwriting (recommended to avoid data loss). -p preserves metadata; -a is archive mode (preserves everything, including symlinks — ideal for backups). -u only updates if the source is newer. Always use -i in scripts where overwriting is risky.
# copy a file
cp source.txt dest.txt
# copy to a directory
cp file.txt /backup/
# copy multiple files to a directory
cp file1.txt file2.txt /backup/
# copy directories recursively
cp -r src_dir/ dest_dir/
# interactive (prompt before overwrite)
cp -i file.txt /backup/
# preserve attributes (timestamps, permissions, ownership)
cp -p file.txt /backup/
cp -a src_dir/ dest_dir/ # archive mode (-rdp)
# only copy if source is newer
cp -u file.txt /backup/
# verbose (show what is being copied)
cp -v file.txt /backup/
# force overwrite
cp -f file.txt /backup/Moving & Renaming (mv)
mv is both move and rename — it updates the directory entry without copying data (instant on the same filesystem). -i prompts before overwriting. mv -n prevents overwriting existing files. The rename command (Perl-based on Debian/Ubuntu) uses regex for batch renaming — much more powerful than mv loops. Note: rename syntax differs between distributions (util-linux vs perl version).
# rename a file
mv oldname.txt newname.txt
# move file to another directory
mv file.txt /target/dir/
# move multiple files
mv file1.txt file2.txt /target/dir/
# interactive (prompt before overwrite)
mv -i file.txt /target/
# force overwrite
mv -f file.txt /target/
# no overwrite (don't replace existing)
mv -n file.txt /target/
# move only if newer
mv -u file.txt /target/
# verbose
mv -v olddir/ newdir/
# rename batch with rename command
rename 's/\.txt$/.md/' *.txt
rename .JPG .jpg *.JPGRemoving Files (rm)
rm permanently deletes files (no trash/recycle bin). -r is required for directories. -rf is dangerous — it deletes recursively without prompting; always double-check the path. Never run rm -rf / or rm -rf $VAR/ when $VAR might be empty. Consider trash-cli for recoverable deletion. find -delete is safer for pattern-based deletion because you can test without -delete first.
# remove a file
rm file.txt
# remove multiple files
rm file1.txt file2.txt
# interactive (prompt for each file)
rm -i file.txt
# force (ignore nonexistent, no prompt)
rm -f file.txt
# remove directory recursively
rm -r directory/
rm -rf directory/ # force + recursive (DANGEROUS)
# verbose
rm -v *.tmp
# remove files matching pattern
find . -name "*.bak" -delete
find . -name "*.log" -mtime +30 -delete
# safer alternative: trash-cli
trash-put file.txt
trash-list
trash-restoreCreating Files (touch)
touch creates empty files or updates timestamps of existing files. Without options, it sets both atime and mtime to the current time. -t sets a specific timestamp. -r copies the timestamp from a reference file. To create a file with content, use redirection (>) or a here-document. touch is commonly used to create placeholder files or force make to rebuild targets.
# create an empty file
touch newfile.txt
# create multiple empty files
touch file1.txt file2.txt file3.txt
# update access and modification time to now
touch existing.txt
# set specific time (YYYYMMDDHHMM format)
touch -t 202401011200 file.txt # Jan 1, 2024 12:00
# set only access time
touch -a file.txt
# set only modification time
touch -m file.txt
# use time from another file
touch -r reference.txt target.txt
# create file with content
echo "content" > file.txt
cat > file.txt << 'EOF'
line 1
line 2
EOFHard & Symbolic Links (ln)
Hard links share the same inode (data) — deleting one doesn't delete the data until ALL links are removed. They can't cross filesystems or link to directories. Symlinks are path references — they break if the target moves or is deleted, but can link across filesystems and to directories. Use ln -s for symlinks (most common). readlink -f resolves the full chain to the real file.
# hard link (same inode, same filesystem only)
ln original.txt hardlink.txt
# symbolic link (symlink, path reference)
ln -s /path/to/target symlink_name
ln -s original.txt softlink.txt
# force recreate a symlink
ln -sf /new/target symlink_name
# symbolic link to a directory
ln -s /var/log /home/user/logs
# view link target
readlink symlink
readlink -f symlink # canonical absolute path
ls -l symlink # show what it points to
# find broken symlinks
find . -type l ! -exec test -e {} \; -print
# count hard links to a file
ls -li file.txt # 2nd column is link count
# remove a symlink
rm symlink_name
unlink symlink_nameFile Metadata (stat, basename, dirname)
stat shows complete inode metadata. basename and dirname decompose paths — essential in scripts that process file lists. realpath resolves relative paths and symlinks to absolute canonical paths. stat -c allows custom output formats (%n=name, %s=size, %y=mtime, %a=octal perms) — useful in scripts. These tools replace fragile parameter expansion for path manipulation.
# stat: full file information
stat file.txt
# Size: 1024 Blocks: 8 IO Block: 4096
# Device: 801h/2049d Inode: 1234567
# Links: 1
# Access: (0644/-rw-r--r--) Uid: 1000 Gid: 1000
# Access/Modify/Change: 2024-01-01 12:00:00
# custom format
stat -c '%n %s bytes' file.txt
stat -c '%y' file.txt # modification time
# basename: extract filename from path
basename /var/log/syslog # syslog
basename /var/log/syslog.log .log # syslog
# dirname: extract directory from path
dirname /var/log/syslog # /var/log
# realpath: resolve to absolute path
realpath ../file.txt
realpath -s symlink # physical path (no symlink resolution)
# file size only
stat -c %s file.txt # bytes
du -h file.txt | cut -f1 # human-readableDirectory Operations
Creating Directories (mkdir)
mkdir -p creates parent directories as needed and doesn't error if the directory already exists — essential for idempotent scripts. -m sets permissions directly (overrides umask). Brace expansion ({a,b,c}) combined with mkdir -p creates complex directory trees in one command. This is a common pattern for scaffolding project structures.
# create a single directory
mkdir newdir
# create nested directories
mkdir -p path/to/deep/dir
# create multiple directories
mkdir dir1 dir2 dir3
# create with specific permissions
mkdir -m 755 publicdir
mkdir -m 700 privatedir
# verbose output
mkdir -v newdir
# create parent dirs with permissions
mkdir -p -m 755 /opt/myapp/{bin,lib,etc}
# create a directory tree at once
mkdir -p project/{src,tests,docs}
mkdir -p project/src/{main,utils}
mkdir -p project/{src/{main,utils},tests,docs}Removing Directories (rmdir, rm)
rmdir only removes empty directories — safe but limited. rm -r removes non-empty directories recursively. rm -rf is the nuclear option: always verify the path first. The find -empty -delete pattern removes only empty directories without touching populated ones. In scripts, always check that the variable is non-empty before rm -rf to avoid accidentally deleting /.
# remove an empty directory
rmdir emptydir
# remove directory with contents
rm -r directory/
# force remove (no prompt)
rm -rf directory/
# remove empty directories recursively (find)
find . -type d -empty -delete
find . -type d -empty -exec rmdir {} \;
# remove directory with confirmation
rm -ri directory/
# safe removal pattern in scripts
DIR="/tmp/mydir"
if [ -n "$DIR" ] && [ -d "$DIR" ]; then
rm -rf "$DIR"
fi
# move to trash instead
trash-put directory/Directory Listing & Tree
ls -d */ lists only directories. tree provides a visual hierarchy — -L limits depth, -I excludes patterns (incredibly useful for excluding node_modules/.git). For large directory trees, combine tree -d -L 2 for a quick overview. ls -1 (that's the number one) lists one file per line — useful for piping to wc -l for counting.
# list directory contents
ls -la # all files, long format
ls -lh # human-readable sizes
ls -lt # sort by time (newest first)
ls -lS # sort by size (largest first)
ls -lr # reverse order
ls -R # recursive listing
ls -d */ # list only directories
# tree view (install separately: apt install tree)
tree # show directory tree
tree -L 2 # limit depth to 2
tree -d # directories only
tree -a # include hidden files
tree -h # show file sizes
tree --dirsfirst # dirs before files
tree -I 'node_modules|.git' # ignore patterns
tree -H . -o tree.html # output as HTML
# count files in directory
ls -1 | wc -l
find . -maxdepth 1 -type f | wc -lDirectory Size (du)
du measures disk usage. -s (summary) shows only the total, -h makes it human-readable. du -sh */ gives sizes of immediate subdirectories. sort -rh sorts by human-readable size in reverse (largest first). --exclude skips patterns. The difference between apparent-size and default: apparent shows logical file size, default shows actual disk blocks allocated (which may be larger due to block size).
# size of current directory (total)
du -sh .
# size of specific directories
du -sh /var/log /tmp /home
# size of all subdirectories
du -sh */
# sort directories by size (largest first)
du -sh * | sort -rh
# find the 10 largest directories
du -sh * | sort -rh | head -10
# include hidden files/dirs
du -sh .[!.]*
# maximum depth
du -h --max-depth=1
du -h -d 1 # short form
# apparent size vs disk usage
du -sh --apparent-size file # logical size
du -sh file # actual disk blocks
# exclude directories
du -sh --exclude='*.log' .
du -sh --exclude=node_modules .Directory Stack (pushd, popd)
pushd/popd manage a stack of directories — pushd saves the current directory and navigates to a new one; popd returns to the saved one. dirs shows the stack. This is more powerful than cd - (which only toggles between two directories). Useful in scripts that need to navigate between several directories and return. The +N syntax rotates or removes by position.
# push directory onto stack and cd into it
pushd /var/log
# pushes current dir, then cd to /var/log
# push another
pushd /tmp
# stack: /tmp /var/log /home/user
# pop back to previous directory
popd
# returns to /var/log
# list the directory stack
dirs
dirs -v # verbose (numbered)
dirs -c # clear the stack
# navigate by index
pushd +2 # rotate to 3rd entry
popd +1 # remove 2nd entry
# practical: save current dir, work, return
pushd /etc
cp config.conf ~/backup/
popd # back to where we were
# alternative: use cd - for simple toggle
cd /var/log
cd - # back to previous directoryFile Viewing
cat & tac
cat dumps the entire file to stdout — fine for small files but use less/head for large ones. cat -n numbers lines. cat -A reveals hidden characters (tabs, line endings) — useful for debugging formatting issues. tac reverses line order (cat backwards). cat is also used to concatenate files and redirect input. For viewing large files interactively, use less instead.
# display entire file
cat file.txt
# concatenate multiple files
cat file1.txt file2.txt > combined.txt
# display with line numbers
cat -n file.txt
# display with non-printing characters
cat -A file.txt # show tabs as ^I, line ends as $
cat -v file.txt # show non-printing chars
cat -e file.txt # show line ends as $
cat -t file.txt # show tabs as ^I
# append to a file
cat >> file.txt << 'EOF'
appended line
EOF
# tac: reverse line order
tac file.txt # last line first
# create file from stdin
cat > newfile.txt
# type content, Ctrl+D to endless Pager
less is the standard pager — far better than more because it allows backward navigation. The keybindings are vim-like (j/k/g/G). / searches forward, ? backward. F enters 'follow mode' (like tail -f) for watching logs. less automatically handles compressed files if lesspipe is configured. Press v to open the current file in your $EDITOR for quick edits.
# view a file
less file.txt
# view command output
dmesg | less
ls -la /etc | less
# navigation inside less:
# space / f forward one page
# b backward one page
# j / k down / up one line
# g go to beginning
# G go to end
# /pattern search forward
# ?pattern search backward
# n next match
# N previous match
# v open in editor ($EDITOR)
# F follow mode (like tail -f)
# q quit
# useful flags
less -N file.txt # show line numbers
less -S file.txt # chop long lines
less -i file.txt # case-insensitive search
less +/pattern file.txt # open at first match
# view compressed files
less file.gz # auto-decompresses (if lesspipe installed)head & tail
head shows the beginning, tail shows the end of a file. tail -f is essential for monitoring log files in real-time. tail -F (capital) handles file rotation (when logs are renamed/truncated) by reopening the file. head -n -N shows everything except the last N lines. For large files, head/tail are instant (they don't read the entire file).
# first 10 lines (default)
head file.txt
# last 10 lines (default)
tail file.txt
# first N lines
head -n 20 file.txt
head -20 file.txt
# last N lines
tail -n 20 file.txt
tail -20 file.txt
# all but last N lines
head -n -5 file.txt # all except last 5 lines
# first N bytes
head -c 100 file.txt
tail -c 100 file.txt
# follow a log file (live updates)
tail -f /var/log/syslog
# follow with retry (handles log rotation)
tail -F /var/log/app.log
# show last 5 lines, then follow
tail -n 5 -f app.log
# multiple files
tail -n 5 file1.txt file2.txtViewing Specific Lines
sed -n 'Np' prints specific line numbers. awk with NR (record number) is more flexible for ranges and conditions. The (head; tail) trick shows both ends of a large file. nl and cat -n number lines. hexdump/xxd display binary files in hex — essential for inspecting non-text files, debugging file formats, or recovering data.
# show line N (e.g., line 10)
sed -n '10p' file.txt
# show lines M to N
sed -n '10,20p' file.txt
awk 'NR>=10 && NR<=20' file.txt
# show first and last N lines
(head -5; echo "..."; tail -5) < file.txt
# show lines matching pattern
sed -n '/pattern/p' file.txt
awk '/pattern/' file.txt
# show every Nth line
awk 'NR % 3 == 0' file.txt # every 3rd line
sed -n '0~3p' file.txt # every 3rd line (GNU)
# number lines
nl file.txt
cat -n file.txt
grep -n "" file.txt
# show file in hex
hexdump -C file.bin
xxd file.bin
od -A x -t x1z file.binWord & Line Count (wc)
wc (word count) reports lines (-l), words (-w), bytes (-c), characters (-m, respects encoding), and the longest line length (-L). When given multiple files, it shows per-file counts and a total. The grep | wc -l pattern counts matching lines — but grep -c is more efficient (no pipe). For counting files, ls -1 | wc -l is quick, but find is more accurate for recursive counts.
# count lines, words, and bytes
wc file.txt
# 10 50 300 file.txt
# lines words bytes
# count lines only
wc -l file.txt
# count words only
wc -w file.txt
# count characters only
wc -m file.txt
# count bytes only
wc -c file.txt
# count the longest line length
wc -L file.txt
# count files in a directory
ls -1 | wc -l
find . -type f | wc -l
# count total lines in multiple files
wc -l *.py
# count matching lines (grep + wc)
grep "error" log.txt | wc -l
# count unique lines
sort file.txt | uniq | wc -lFile Search
find by Name & Type
find is the most powerful file search tool. -name matches filenames with glob patterns; -iname is case-insensitive. -type filters by file type (f=file, d=directory, l=symlink). -maxdepth limits recursion (important for performance). -not and -prune exclude paths. \( -name A -o -name B \) groups conditions with OR. Always quote patterns to prevent shell glob expansion.
# search by name (case-sensitive)
find / -name "config.txt"
find . -name "*.py"
# case-insensitive name search
find . -iname "*.JPG"
# find by type
find . -type f -name "*.txt" # regular files
find . -type d -name "node_modules" # directories
find . -type l # symlinks
find . -type b # block devices
find . -type c # character devices
# find by path pattern
find . -path "*/src/*.test.js"
# find using regex (full path)
find . -regex ".*\.\(py\|js\)$"
# multiple name patterns
find . \( -name "*.py" -o -name "*.js" \)
# limit search depth
find . -maxdepth 2 -name "*.txt"
# exclude directories
find . -name "*.py" -not -path "*/venv/*"
find . -name "*.py" -prune -path "*/venv/*"find by Time & Size
Time-based search: -mtime (days), -mmin (minutes). - (less than) and + (more than) prefix the value. -atime is access time, -ctime is metadata change time. Size suffixes: c (bytes), k (KB), M (MB), G (GB). -perm 644 matches exactly; -perm -u+x matches files with execute bit for user; /4000 matches any SUID file. Finding SUID files is a security audit technique.
# modified within last N days
find . -mtime -7 # < 7 days ago
find . -mtime +30 # > 30 days ago
find . -mtime 7 # exactly 7 days ago
# modified within N minutes
find . -mmin -60 # last 60 minutes
# accessed time (-atime / -amin)
find /var/log -atime -1 # accessed in last 24h
# changed (metadata) time
find . -ctime -1
# by size
find . -size +100M # larger than 100MB
find . -size -1k # smaller than 1KB
find . -size 10M # exactly 10MB
# empty files and directories
find . -empty -type f
find . -empty -type d
# by permissions
find . -perm 644 # exactly 644
find . -perm -u+x # has user execute
find / -perm /4000 2>/dev/null # SUID files (security audit)
# combine: large files modified recently
find /var/log -size +10M -mtime -7find -exec & -delete
-exec runs a command on each result. {} is the filename placeholder; \; runs the command once per file; + batches files (more efficient, fewer process spawns). -delete is safer than -exec rm because it's atomic. Always test with -print first before adding -delete. -ok prompts for confirmation per file. The dirs-755-files-644 pattern is the standard web server permission setup.
# execute a command on each result (one per file)
find . -name "*.log" -exec ls -lh {} \;
# execute with + (batch, more efficient)
find . -name "*.log" -exec ls -lh {} +
# delete matched files
find /tmp -name "*.tmp" -type f -delete
# safer: delete with confirmation
find . -name "*.bak" -ok rm {} \;
# find and grep
find . -name "*.py" -exec grep "TODO" {} +
# find and chmod
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
# find and compress old logs
find /var/log -name "*.log" -mtime +30 -exec gzip {} \;
# find and move
find . -name "*.jpg" -exec mv {} /photos/ \;
# print then delete (verification)
find . -name "*.tmp" -print -deletelocate & which
locate searches a pre-built database (updated by updatedb, usually via cron) — much faster than find but may be outdated. Run sudo updatedb to refresh. which finds executables in $PATH. whereis also finds source and man pages. type shows how the shell resolves a command (alias, builtin, or file) — more accurate than which for shell builtins. command -v is the POSIX-portable way.
# locate: fast file search using a pre-built database
locate config.txt
locate "*.conf"
locate -i readme # case-insensitive
locate -r "^/usr.*\.py$" # regex
locate -n 10 "*.log" # limit to 10 results
locate -c "*.py" # count matches only
# update the database (run as root)
sudo updatedb
# which: find a command's binary location
which python3 # /usr/bin/python3
which -a python3 # all matches in PATH
# whereis: binary, source, and man page
whereis python3
# python3: /usr/bin/python3 /usr/lib/python3 /usr/share/man/man1/python3.1.gz
# type: show how a command is resolved
type ls # ls is aliased to 'ls --color=auto'
type cd # cd is a shell builtin
type python3 # python3 is /usr/bin/python3
# find command in PATH
command -v python3find Practical Recipes
These recipes show find's real-world power. -printf allows custom output formatting (%s=size, %p=path). -newermt matches by modification time with date strings. -newer compares against a reference file. The broken-symlink finder uses test -e inside -exec. The file-count-by-extension recipe is a classic pipeline combining find, sed, sort, and uniq for quick codebase analysis.
# find the 10 largest files
find . -type f -exec ls -lhS {} + | head -10
find . -type f -printf '%s %p\n' | sort -rn | head -10
# find recently modified files (last 24h)
find /etc -mtime -1 -type f
# find files owned by a specific user
find /home -user alice -type f
find / -group developers -type f 2>/dev/null
# find files modified between two times
find . -newermt "2024-01-01" ! -newermt "2024-06-01"
# find files newer than a reference file
find . -newer reference.txt
# find and list with details
find . -name "*.conf" -exec ls -la {} \;
# find all broken symlinks
find . -type l ! -exec test -e {} \; -print
# find files with specific permissions
find /www -type f -perm 644 -exec chmod 664 {} \;
# count files by extension
find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rnText Search (grep)
Basic grep Patterns
grep finds lines matching a pattern. -i ignores case, -w matches whole words, -v inverts, -c counts, -n numbers lines, -l lists filenames, -h suppresses filenames, -o shows only the match. By default grep uses Basic Regex (BRE); use -E for Extended Regex (cleaner syntax) or -F for fixed strings (faster, no regex).
# basic search (literal string)
grep "error" logfile.txt
# case-insensitive
grep -i "error" logfile.txt
# whole word match (avoids matching 'errors')
grep -w "error" log.txt
# invert match (lines NOT matching)
grep -v "debug" log.txt
# count matching lines
grep -c "error" log.txt
# show line numbers
grep -n "error" log.txt
# multiple files (shows filename:line)
grep "error" *.log
# suppress filename prefix
grep -h "error" *.log
# only show filenames with matches
grep -l "error" *.log
# only show matched portion
grep -o "https?://[^ ]+" urls.txtgrep with Regular Expressions
grep -E (or egrep) uses Extended Regular Expressions where +, ?, |, () work without backslashes. ^ and $ anchor to line start/end. [] defines character classes; {} specifies repetition. -P enables Perl-Compatible Regex (PCRE) with \d, \w, \b, lookarounds — powerful but GNU-specific. -F treats the pattern as a fixed string (no regex interpretation), faster for literal searches.
# extended regex (-E or egrep)
grep -E "error|warning|fatal" log.txt
# anchors
grep -E "^ERROR:" log.txt # lines starting with ERROR:
grep -E "completed$" log.txt # lines ending with completed
# character classes
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" dates.txt
grep -E "[A-Z][a-z]+" names.txt
# quantifiers
grep -E "ab+c" file.txt # one or more b
grep -E "ab*c" file.txt # zero or more b
grep -E "ab?c" file.txt # zero or one b
# alternation and grouping
grep -E "(cat|dog) food" file.txt
# PCRE (-P, GNU grep only)
grep -P "\d{3}-\d{4}" phones.txt
grep -P "(?<=foo)bar" file.txt # lookbehind
# fixed string (no regex, faster for literals)
grep -F "literal$string" file.txtgrep Context & Output
Context flags (-B, -A, -C) show surrounding lines — essential for understanding log entries. --color highlights matches. -r searches recursively (excludes symlinks); -R follows symlinks. --include/--exclude/--exclude-dir filter which files to search — always exclude node_modules and .git. -a forces binary files to be treated as text; -I skips them. -rn combines recursive with line numbers.
# show context lines
grep -B 2 "error" log.txt # 2 lines Before
grep -A 3 "error" log.txt # 3 lines After
grep -C 2 "error" log.txt # 2 lines Context (both)
# color highlight
grep --color=auto "error" log.txt
# recursive search
grep -r "TODO" ./src/
grep -R "TODO" ./src/ # follows symlinks
# include/exclude file patterns
grep -r --include="*.py" "import" .
grep -r --exclude="*.test.js" "TODO" .
grep -r --exclude-dir=node_modules "TODO" .
# binary files
grep -a "pattern" binary_file # treat as text
grep -I "pattern" . # skip binary files
# output only the match
grep -oE "[0-9.]+" data.txt
# line numbers in recursive search
grep -rn "function" src/grep in Pipelines
The [n]ginx trick prevents grep from matching its own process: the bracket makes the pattern not match the literal 'grep' string in the process list. zgrep searches compressed files. pgrep is a specialized process finder. grep -rl lists files containing a pattern; piping to xargs grep searches within those files for another pattern — a common code archaeology technique.
# search command output
ps aux | grep nginx
ps aux | grep "[n]ginx" # trick: prevents matching grep itself
# search compressed logs
zcat log.gz | grep "error"
zgrep "error" log.gz # direct search
# chain multiple greps
cat log.txt | grep "error" | grep -v "timeout"
# extract and filter
grep -oE "[0-9.]+" response.txt | sort -n | uniq
# find processes (better than ps | grep)
pgrep -f "node server.js"
pgrep -fl "node" # full command line
# search history
history | grep "git rebase"
# find files containing pattern, then search more
grep -rl "config" . | xargs grep "database"
# count errors per hour
grep "ERROR" log.txt | grep -oE "[0-9]{2}:" | sort | uniq -c
# search and show filename + line
grep -rn "TODO" src/ | head -20grep Exit Status & Scripting
grep's exit status makes it ideal for scripting: 0 (match found), 1 (no match), 2 (error). -q (quiet) suppresses output for pure conditional checks. In set -e scripts, grep returning 1 (no match) would exit the script — use '|| true' to prevent this. grep -c returns a count. This scripting capability makes grep a building block for log monitoring, validation, and CI/CD checks.
# grep exit codes: 0=match, 1=no match, 2=error
if grep -q "error" /var/log/syslog; then
echo "Errors found!"
# send alert, trigger recovery
fi
# silent check (-q), no output
grep -q "^$" file.txt && echo "has blank lines"
# count and branch
errors=$(grep -c "ERROR" log.txt)
if [ "$errors" -gt 10 ]; then
echo "Too many errors: $errors"
fi
# use in while loop
grep "pattern" file.txt | while read -r line; do
echo "Found: $line"
done
# grep with set -e (prevents exit on no-match)
set -e
grep "missing" file.txt || true
# validate input with grep
if echo "$input" | grep -qE "^[0-9]+$"; then
echo "valid number"
firipgrep (rg) — Modern Alternative
ripgrep (rg) is a modern, fast alternative to grep for code search. It's recursive by default, respects .gitignore, and is significantly faster. -t/-T filter by file type (built-in type definitions). -g uses glob patterns. Unlike grep, rg skips hidden files and gitignored files by default — use --hidden and --no-ignore to override. Install with: cargo install ripgrep or apt install ripgrep.
# basic search (recursive by default)
rg "pattern" # searches current dir
rg "pattern" src/ # search specific dir
# case-insensitive
rg -i "error"
# whole word
rg -w "error"
# show context
rg -C 3 "pattern" # 3 lines context
rg -B 2 -A 2 "pattern" # before and after
# file type filter
rg "TODO" -t py # Python files only
rg "TODO" -t py -t js # Python and JavaScript
rg "TODO" -T md # exclude Markdown
# glob patterns
rg "pattern" -g "*.py"
rg "pattern" -g "!tests/*" # exclude tests
# fixed string (no regex)
rg -F "literal$string"
# list files with matches
rg -l "pattern"
# count matches
rg -c "pattern"
# search hidden files
rg --hidden "pattern"
# multiline search
rg -U "start.*end" # dot matches newlinesText Processing
sed — Stream Editor
sed edits text streams non-interactively. s substitutes (g for global); d deletes; p prints. -i edits in place (always test without -i first, or use -i.bak for a backup). The delimiter can be any character — use | or # for paths to avoid escaping slashes. -E enables extended regex with cleaner capture group syntax. sed processes line by line.
# substitute (replace first occurrence per line)
sed 's/old/new/' file.txt
# substitute all occurrences (global)
sed 's/old/new/g' file.txt
# case-insensitive (GNU sed)
sed 's/old/new/gi' file.txt
# in-place editing
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt # keep backup
# delete lines
sed '/^#/d' file.txt # delete comment lines
sed '/^$/d' file.txt # delete blank lines
sed '5d' file.txt # delete line 5
sed '5,10d' file.txt # delete lines 5-10
# print specific lines
sed -n '10,20p' file.txt # lines 10-20
sed -n '/pattern/p' file.txt # matching lines
# different delimiter for paths
sed 's|/usr/local|/opt|g' paths.txt
# extended regex with capture groups
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' dates.txt
# multiple commands
sed -e 's/a/A/g' -e 's/b/B/g' file.txtawk — Column Processing
awk is a mini programming language for columnar data. $1, $2... are fields; $0 is the whole line; $NF is the last field. -F sets the input separator; OFS sets output. BEGIN runs before processing, END after. NR is the record (line) number; NF is the field count. awk is ideal for CSV/TSV processing, log analysis, and reports — far more powerful than cut.
# print columns (default separator: whitespace)
awk '{print $1}' file.txt # first column
awk '{print $1, $3}' file.txt # columns 1 and 3
awk '{print $NF}' file.txt # last column
awk -F: '{print $1}' /etc/passwd # split on :
# filter and print
awk '$3 > 100' file.txt # col 3 > 100
awk '$1 == "ERROR" {print $2}' log # col 2 of ERROR lines
awk 'NR == 5' file.txt # 5th line only
awk 'NR >= 10 && NR <= 20' file.txt # lines 10-20
# BEGIN and END blocks (sum, average)
awk '{sum += $1} END {print sum}' nums.txt
awk '{sum += $2; count++} END {print sum/count}' data.txt
# field separator and output
awk -F, 'BEGIN {OFS="|"} {print $1, $2}' csv.txt
# count lines matching pattern
awk '/error/ {count++} END {print count}' log
# built-in variables: NR (row), NF (fields), FS, OFS
awk '{print NR, NF, $0}' file.txtcut & paste
cut extracts fields (-f with -d delimiter) or character positions (-c). It's fast but limited — no quoting support, so CSVs with quoted commas break it. paste merges files side by side. join combines files on a common sorted field (like SQL JOIN). expand/unexpand convert between tabs and spaces. For complex CSV processing, use awk or a dedicated tool like csvkit.
# cut: extract fields by delimiter
cut -d: -f1 /etc/passwd # field 1, delimiter :
cut -d, -f2,3 data.csv # fields 2 and 3
# cut: extract character positions
cut -c1-5 file.txt # characters 1-5
cut -c1- file.txt # from char 1 to end
# cut: extract bytes (for multibyte)
cut -b1-10 file.txt
# paste: merge files line by line
paste file1.txt file2.txt # tab-separated
paste -d',' file1.txt file2.txt # comma-separated
# paste: serial (all lines of file1, then file2)
paste -s file1.txt # lines into one line
paste -s -d',' file1.txt file2.txt
# join: combine on a common field
join -t, -1 1 -2 1 file1.csv file2.csv
# expand/unexpand tabs
expand file.txt # tabs to spaces
unexpand -a file.txt # spaces to tabssort & uniq
sort orders lines (-n numeric, -r reverse, -h human-readable, -k field, -t delimiter). uniq only removes ADJACENT duplicates — always pipe through sort first. uniq -c counts; sort -rn gives frequency ranking. comm compares two SORTED files: -12 shows common, -23 shows only in first, -13 shows only in second. The sort | uniq -c | sort -rn pattern is the classic frequency analysis pipeline.
# sort lines
sort file.txt # alphabetical
sort -n nums.txt # numeric
sort -rn nums.txt # reverse numeric
sort -h sizes.txt # human-readable (1K, 2M, 3G)
sort -R file.txt # random order
sort -u file.txt # sort + unique
# sort by field
sort -t: -k3 -n /etc/passwd # by 3rd field, numeric
sort -t, -k2 data.csv # by 2nd field
# sort by multiple keys
sort -k1,1 -k2n file.txt # key1 alpha, key2 numeric
# uniq: filter adjacent duplicates
sort file.txt | uniq # unique lines
sort file.txt | uniq -c # count occurrences
sort file.txt | uniq -d # only duplicates
sort file.txt | uniq -u # only unique lines
# top 10 most frequent lines
sort file.txt | uniq -c | sort -rn | head -10
# compare two sorted files
comm -12 <(sort a.txt) <(sort b.txt) # common lines
comm -23 <(sort a.txt) <(sort b.txt) # only in a
comm -13 <(sort a.txt) <(sort b.txt) # only in btr — Translate Characters
tr translates or deletes characters (not regex — just character sets). -d deletes, -s squeezes repeats, -c complements (inverts the set). It's perfect for case conversion, delimiter swapping, and cleaning data. tr ',' '\n' converts CSV to one-value-per-line. tr -cd '0-9' extracts only digits. Note: tr works on characters, not strings, so it can't replace multi-character patterns — use sed for that.
# translate characters
echo "Hello" | tr 'a-z' 'A-Z' # HELLO (uppercase)
echo "Hello" | tr 'A-Z' 'a-z' # hello (lowercase)
# swap characters
echo "abc" | tr 'abc' 'bca' # bca
# delete characters
echo "hello" | tr -d 'l' # heo
echo "Hello World" | tr -d ' ' # HelloWorld
# squeeze repeated characters
echo "aaabbbccc" | tr -s 'abc' # abc
tr -s ' ' < file.txt # squeeze repeated spaces
# complement (delete everything except)
echo "Hello123" | tr -cd '0-9' # 123 (keep digits only)
echo "Hello123" | tr -cd 'A-Za-z' # Hello
# convert delimiter
echo "a,b,c" | tr ',' '\n' # a\nb\nc (one per line)
echo "a,b,c" | tr ',' '\t' # tab-separated
# ROT13 cipher
echo "Hello" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
# remove non-printable characters
tr -cd '\11\12\15\40-\176' < file.txttee & column
tee splits output to both a file and stdout — essential for logging within pipelines. sudo tee writes to files requiring root without running the whole pipeline as root. column -t aligns text into neat columns (great for displaying CSVs). -s specifies the input delimiter, -o the output separator. fmt and fold reflow text — fmt joins paragraphs, fold hard-wraps at a width.
# tee: write to file AND stdout
echo "hello" | tee file.txt
echo "hello" | tee -a file.txt # append
# tee to multiple files
echo "data" | tee f1.txt f2.txt f3.txt
# tee in a pipeline (log while transforming)
ls -la | tee output.txt | grep ".py"
# tee with sudo (write to privileged file)
echo "config" | sudo tee /etc/myapp.conf
# column: format as aligned columns
column -t file.txt
echo -e "a b c\n1 2 3" | column -t
# column with custom delimiter
column -t -s, data.csv
column -t -s: /etc/passwd
# column with specific separator for output
column -t -o " | " file.txt
# fmt: reformat paragraph width
fmt -w 60 file.txt
# fold: wrap lines at width
fold -w 80 file.txt
fold -w 80 -s file.txt # break at spaces onlyPermissions & Ownership
Viewing Permissions
Permissions are three triplets: owner, group, others. Each position is r(4), w(2), x(1). So 755 = rwxr-xr-x, 644 = rw-r--r--. For directories, x means 'can access' (cd into), r means 'can list', w means 'can create/delete files'. stat -c '%a' shows octal permissions. The first character of ls -l is the file type (- file, d dir, l symlink, b block, c char).
# view file permissions
ls -l file.txt
# -rw-r--r-- 1 user group 1024 Jan 1 10:00 file.txt
# ^^^ ^^^ ^^^
# owner group others
# rwx r-x r-x = 755
# permission breakdown:
# - = regular file, d = directory, l = symlink
# r = read (4), w = write (2), x = execute (1)
# view all files in directory
ls -la
# view permissions recursively
ls -lR /var/www
# view in octal format
stat -c '%a %n' file.txt # 644 file.txt
stat -c '%a %n' *
# check current umask
umask # e.g., 0022
# directory permissions:
# r = list contents
# w = create/delete files
# x = access (cd into) directorychmod — Numeric Mode
Numeric (octal) notation: each digit represents owner, group, others. r=4, w=2, x=1 — sum them. 755 (rwxr-xr-x) for directories and executables; 644 (rw-r--r--) for regular files; 600 (rw-------) for private files. For web servers, the standard is dirs=755, files=644. The find-based recursive pattern applies different permissions to dirs and files — chmod -R can't do this.
# numeric (octal) notation
chmod 755 script.sh # rwxr-xr-x
chmod 644 file.txt # rw-r--r--
chmod 600 secrets.txt # rw------- (private)
chmod 777 publicdir/ # rwxrwxrwx (DANGEROUS)
chmod 700 ~/.ssh # rwx------ (for SSH)
# common patterns
chmod 755 directory/ # dirs: owner full, others read+execute
chmod 644 file.txt # files: owner read+write, others read
chmod 600 config.env # sensitive files: owner only
chmod 666 shared.txt # everyone read+write (no execute)
chmod 444 readonly.txt # everyone read only
# recursive
chmod -R 755 project/
chmod -R 644 *.txt # won't work; use find
# recursive: dirs 755, files 644 (common web server setup)
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
# permission reference:
# 7=rwx 6=rw 5=rx 4=r 3=wx 2=w 1=x 0=nonechmod — Symbolic Mode
Symbolic notation is more readable for incremental changes: u/g/o/a (who), +/-/= (action), r/w/x/X (what). X (capital) sets execute only for directories or files that already have execute — perfect for recursive chmod. --reference copies permissions from another file. The sticky bit (+t) on a directory means only file owners can delete their files (used on /tmp).
# symbolic notation
chmod u+x script.sh # add execute for user
chmod g-w file.txt # remove write for group
chmod o+r file.txt # add read for others
chmod a+x script.sh # add execute for all (a=all)
# combined changes
chmod u+x,g-w,o=r file.txt # user +x, group -w, others = read only
chmod ug+x file.txt # add execute for user and group
chmod a=r file.txt # set everyone to read only
# copy permissions from reference
chmod --reference=template.txt file.txt
# recursive
chmod -R u+rwX,go+rX . # X = execute only for dirs/executables
# symbolic targets:
# u = user (owner)
# g = group
# o = others
# a = all (default)
# operators: + (add), - (remove), = (set exactly)
# permissions: r, w, x, X (execute if dir or already exec), s (SUID/SGID), t (sticky)
# set sticky bit on directory
chmod +t /tmp/shared
chmod 1777 /tmp/sharedchown & chgrp
chown changes ownership (requires root or sudo). Format: owner:group — omit the group to change only owner, omit the owner (with colon) to change only group. -R recurses into directories. chgrp is a shortcut for chown :group. --from only changes if the current owner matches (conditional). For web servers, chown -R www-data:www-data is standard. Always use -h with symlinks to change the link, not the target.
# change owner
chown alice file.txt
chown alice file1.txt file2.txt
# change owner and group
chown alice:developers file.txt
chown alice:developers project/
# recursive
chown -R alice:developers project/
# change group only
chgrp developers file.txt
chown :developers file.txt # alternative
# verbose
chown -v alice file.txt
# reference (copy ownership from another file)
chown --reference=reference.txt file.txt
# change ownership only if currently owned by specific user
chown --from=bob alice file.txt
# symbolic links (by default, changes the link target)
chown -h alice symlink # change the link itself
# common patterns
chown -R www-data:www-data /var/www
chown root:root /etc/sudoers
# view ownership
ls -l file.txt # shows owner and group
stat -c '%U:%G' file.txt # just owner:groupumask
umask sets default permissions for newly created files and directories. It's a mask — bits are removed from the default (666 for files, 777 for dirs). Common values: 022 (standard, files 644/dirs 755), 077 (private, files 600/dirs 700), 002 (group-shared, files 664/dirs 775). Set it in ~/.bashrc or /etc/profile for persistence. Note: umask can't ADD permissions, only remove them.
# view current umask
umask # e.g., 0022
umask -S # symbolic: u=rwx,g=rx,o=rx
# set umask
umask 022 # new files: 644, dirs: 755
umask 077 # new files: 600, dirs: 700 (private)
umask 002 # new files: 664, dirs: 775 (group-writable)
# how umask works:
# default file perms: 666 (rw-rw-rw-)
# default dir perms: 777 (rwxrwxrwx)
# umask is SUBTRACTED (mask removed)
# umask 022: file = 666-022 = 644, dir = 777-022 = 755
# umask 077: file = 666-077 = 600, dir = 777-077 = 700
# set permanently: add to ~/.bashrc
echo 'umask 022' >> ~/.bashrc
# for security-sensitive environments
umask 077 # only owner can access new files
# for shared group work
umask 002 # group members can write new files
# umask does not affect execute bit on files
# (files are created without execute by default)Special Permissions (SUID, SGID, Sticky)
SUID (4xxx): program runs with the file owner's privileges — passwd needs this to modify /etc/shadow as root. SGID (2xxx): on executables, runs with group privileges; on directories, new files inherit the directory's group (useful for shared projects). Sticky bit (1xxx): on directories, only the file owner (or dir owner/root) can delete files — used on /tmp. Audit SUID/SGID files regularly as they're privilege escalation vectors.
# SUID (Set User ID): runs as file owner
chmod u+s /usr/bin/passwd # 4755 -> rwsr-xr-x
chmod 4755 myprogram
# SGID (Set Group ID): runs as group owner
chmod g+s /shared/dir # dirs: new files inherit group
chmod 2755 /shared/project
# Sticky bit: only file owner can delete
chmod +t /tmp # 1777 -> rwxrwxrwt
chmod 1777 /shared/upload
# view special permissions
ls -l /usr/bin/sudo
# -rwsr-xr-x (s in user position = SUID)
ls -ld /tmp
# drwxrwxrwt (t in others position = sticky)
# find SUID files (security audit)
find / -perm -4000 -type f 2>/dev/null
# find SGID files
find / -perm -2000 -type f 2>/dev/null
# find sticky directories
find / -perm -1000 -type d 2>/dev/null
# numeric special bits:
# 1 = sticky, 2 = SGID, 4 = SUID
# chmod 4755 = SUID + 755
# chmod 2755 = SGID + 755
# chmod 1777 = sticky + 777
# uppercase S/T means execute bit is NOT set (usually an error)
# rws = SUID + execute, rwS = SUID without executeUsers & Groups
User Management
useradd creates users (low-level; adduser is a friendlier wrapper on Debian). -m creates the home directory; -s sets the shell; -c sets the full name; -G adds to supplementary groups. usermod -aG appends to groups without removing existing ones (always use -a with -G). userdel -r removes the home directory. passwd sets passwords. /etc/passwd stores user info; /etc/shadow stores password hashes.
# add a new user
sudo useradd -m -s /bin/bash alice
# -m: create home directory
# -s: specify login shell
# add user with full options
sudo useradd -m -s /bin/bash -c "Alice Smith" -G sudo,docker alice
# set password
sudo passwd alice
# delete a user
sudo userdel alice
sudo userdel -r alice # remove home directory too
# modify user
sudo usermod -aG docker alice # append to group (-a = append)
sudo usermod -s /bin/zsh alice # change shell
sudo usermod -L alice # lock account
sudo usermod -U alice # unlock account
sudo usermod -l newname oldname # rename user
# view user info
id alice # uid, gid, groups
finger alice # detailed info (if installed)
# change login shell
chsh -s /bin/zsh
# list all users
cat /etc/passwd | cut -d: -f1
getent passwd | cut -d: -f1Group Management
Groups organize users for shared access. groupadd/groupdel/groupmod manage groups. usermod -aG (append to group) is the standard way to add a user — without -a, you'd replace ALL their supplementary groups. gpasswd is an alternative tool. /etc/group stores group definitions. newgrp starts a new shell with a different primary group (useful when creating files that should be group-owned).
# create a group
sudo groupadd developers
# create group with specific GID
sudo groupadd -g 2000 developers
# delete a group
sudo groupdel developers
# modify group
sudo groupmod -n devs developers # rename
sudo groupmod -g 2001 developers # change GID
# add user to group
sudo usermod -aG docker alice # append to group
sudo gpasswd -a alice developers # alternative
# remove user from group
sudo gpasswd -d alice developers
sudo deluser alice developers # Debian/Ubuntu
# list groups
cat /etc/group | cut -d: -f1
getent group
# view groups for a user
groups alice
id alice
# set group administrator
sudo gpasswd -A alice developers
# temporarily switch primary group
newgrp developers # new shell with group as primary
# view group members
getent group developers # lists members
grep developers /etc/groupPassword Management
passwd changes passwords. -l locks an account (adds ! to the hash); -u unlocks. -e forces a password change on next login. chage manages password aging policies: -M (max days), -m (min days), -W (warning days), -E (expiry date). Password hashes are stored in /etc/shadow (readable only by root), not /etc/passwd. Good password policy requires regular changes with complexity requirements.
# change your own password
passwd
# change another user's password (requires root)
sudo passwd alice
# set password interactively
sudo passwd alice
# Enter new password: ******
# Retype new password: ******
# lock/unlock a password
sudo passwd -l alice # lock (prepends !)
sudo passwd -u alice # unlock
# force password change on next login
sudo passwd -e alice
sudo chage -d 0 alice
# set password aging policy
sudo chage -M 90 alice # max 90 days
sudo chage -m 7 alice # min 7 days between changes
sudo chage -W 7 alice # warn 7 days before expiry
sudo chage -E 2025-12-31 alice # expire on date
# view password aging info
chage -l alice
# password quality: /etc/login.defs
# PASS_MAX_DAYS 90
# PASS_MIN_DAYS 7
# PASS_MIN_LEN 12
# PASS_WARN_AGE 7
# check password hash in /etc/shadow
sudo grep alice /etc/shadowUser Information Commands
whoami shows your current username; id shows uid/gid/groups. who lists all logged-in users; w adds what they're running. last shows login history from /var/log/wtmp; lastb shows failed attempts from /var/log/btmp. getent passwd queries the user database (including LDAP if configured). These commands are essential for system administration and auditing user activity.
# who am I
whoami # current username
id # uid, gid, groups
echo $USER # from environment
# who is logged in
who # all logged-in users
w # detailed (what they're doing)
users # just usernames
# last logins
last # recent logins
last -n 10 # last 10 logins
last alice # alice's logins
last reboot # reboot history
# failed login attempts
lastb # bad login attempts (requires root)
# user account info
finger alice # detailed info (if installed)
getent passwd alice # /etc/passwd entry
# group membership
groups # your groups
groups alice # alice's groups
id alice # uid, gid, all groups
# check if user exists
id alice && echo "exists" || echo "not found"
getent passwd alice
# list all users with home directories
getent passwd | awk -F: '{print $1, $6}'su & sudo
su switches users (needs target user's password); sudo runs a command as root (needs your password). su - starts a login shell (loads the target user's profile). sudo is preferred for auditing: commands are logged in /var/log/auth.log. visudo safely edits /etc/sudoers (validates syntax before saving). NOPASSWD rules are convenient but reduce security. sudo !! repeats the last command as root — the classic 'forgot sudo' fix.
# switch to root
su # needs root password
su - # login shell (loads root's profile)
# switch to another user
su - alice # login as alice
su alice # as alice, keep current env
# run a single command as root
sudo command
sudo apt update
# run a command as another user
sudo -u alice command
sudo -u www-data php script.php
# edit a file as root
sudo vim /etc/fstab
sudoedit /etc/fstab # safer alternative
# sudo with environment
sudo -E command # preserve environment variables
# list sudo privileges
sudo -l # what can I run?
sudo -l -U alice # what can alice run?
# sudo configuration
sudo visudo # edit /etc/sudoers safely
# /etc/sudoers.d/ for individual rules
# alice ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
# run previous command as root
sudo !! # classic fix for forgetting sudo
# sudo timeout: 5 min by default
sudo -v # extend timeout
sudo -k # kill timeout (require password next time)Process Management
ps — Process Listing
ps shows a snapshot of processes. aux (BSD) and -ef (System V) are the two common styles. The 'ps aux | grep' pattern is ubiquitous (use [n]ginx trick to avoid matching grep). pstree shows parent-child relationships. --sort=-%mem sorts by memory usage (find resource hogs). Key states: R=running, S=sleeping, D=uninterruptible sleep, Z=zombie, T=stopped. For real-time monitoring, use top or htop.
# list processes (BSD style)
ps aux # all processes, full format
ps aux | grep nginx # find specific process
# list processes (System V style)
ps -ef # all processes
ps -ef | grep python
# list processes in a tree
ps auxf # forest/tree view
pstree # dedicated tree tool
pstree -p # with PIDs
# show specific user's processes
ps -u alice
# show process by PID
ps -p 1234
ps -p 1234 -o pid,ppid,cmd
# custom output format
ps -eo pid,ppid,user,%mem,%cpu,cmd --sort=-%mem | head
# show threads
ps -eLf | grep java # show threads (LWP)
# ps output columns:
# PID process ID
# PPID parent process ID
# USER owner
# %CPU CPU usage
# %MEM memory usage
# VSZ virtual memory size (KB)
# RSS resident set size (KB)
# STAT process state (R=running, S=sleeping, Z=zombie)
# START start time
# TIME CPU time
# CMD commandtop & htop
top is the built-in real-time process monitor. Key commands: M (sort by memory), P (sort by CPU), k (kill), q (quit). -b (batch) mode outputs text for scripting. htop is a superior alternative (colorized, scrollable, mouse support, tree view) — install with apt install htop. glances provides a comprehensive system overview. For finding what's consuming resources, top/htop sorted by %CPU or %MEM is the go-to tool.
# real-time process monitor
top
# top interactive commands:
# P sort by CPU usage
# M sort by memory usage
# N sort by PID
# T sort by running time
# k kill a process (enter PID)
# r renice (change priority)
# 1 show per-CPU stats
# u filter by user
# H show threads
# c toggle full command path
# W write settings to ~/.toprc
# q quit
# run top with specific sorting
top -o %MEM # sort by memory
top -o %CPU # sort by CPU
# batch mode (for scripts)
top -b -n 1 # one snapshot
top -b -n 1 | head -20 # top 20 processes
# monitor specific user
top -u alice
# htop: better interactive viewer (install separately)
htop # colorized, mouse support
htop -p 1234 # monitor specific PID
# glance: alternative system monitor
glances # needs installationkill & killall
kill sends signals by PID; killall/pkill send by name. Always try SIGTERM (default) first — it allows graceful cleanup. SIGKILL (-9) is forceful and immediate; the process can't catch it, so it may leave resources in a bad state. Use as a last resort. pkill -f matches the full command line. kill -l lists all signals. The 'kill; sleep; kill -9' pattern gives the process a chance to clean up before forcing.
# send signal to a process by PID
kill 12345 # SIGTERM (graceful, default)
kill -15 12345 # SIGTERM explicitly
kill -9 12345 # SIGKILL (force, cannot be caught)
kill -HUP 12345 # SIGHUP (reload config)
# kill by name
killall nginx # kill all nginx processes
pkill -f "python app.py" # match full command line
pkill -u alice # kill all of alice's processes
# common signals
kill -l # list all signals
# SIGTERM (15) - graceful termination (default)
# SIGKILL (9) - force kill (cannot be caught)
# SIGINT (2) - interrupt (Ctrl+C)
# SIGHUP (1) - hangup (often reload config)
# SIGSTOP (19) - pause (cannot be caught)
# SIGCONT (18) - resume
# SIGUSR1/USR2 - user-defined signals
# graceful then forceful
kill 12345; sleep 2; kill -9 12345 2>/dev/null
# kill background job
kill %1 # kill job 1
# find and kill by port
fuser -k 8080/tcp # kill process using port 8080
kill $(lsof -t -i:8080) # alternativeBackground Jobs & Job Control
Appending & runs in background. jobs lists active jobs; fg/bg move between foreground and background. Ctrl+Z suspends the foreground job. wait blocks until background jobs finish. nohup and disown both prevent the process from dying when the terminal closes — nohup for new commands, disown for already-running ones. For persistent work, use tmux or screen instead.
# run in background (append &)
sleep 100 &
# [1] 12345 (job 1, PID 12345)
# list jobs
jobs
jobs -l # with PIDs
# bring to foreground / send to background
fg # foreground the current job
fg %1 # foreground job 1
bg %2 # background job 2
# Ctrl+Z: suspend foreground job, then resume in background
# Ctrl+Z sends SIGTSTP
bg # resume in background
# wait for background jobs
wait # wait for all
wait $! # wait for last background job
wait 12345 # wait for specific PID
# disown: detach from shell (survives logout)
long_task &
disown %1
# nohup: run immune to hangups (survives logout)
nohup ./script.sh > output.log 2>&1 &
# run at lower priority
nice -n 10 ./backup.sh &
# parallel execution
cmd1 & cmd2 & cmd3 &
wait # wait for all to finishnice & renice — Process Priority
nice values range from -20 (highest priority) to 19 (lowest). Regular users can only lower priority (positive values); root can raise it (negative). The default is 0. nice starts a process with a priority; renice changes a running process. For I/O-intensive tasks, ionice controls disk I/O priority (classes: 1=realtime, 2=best-effort, 3=idle). cpulimit throttles CPU usage — useful for background tasks that shouldn't interfere.
# nice: start a process with adjusted priority
nice ./script.sh # default: +10 (lower priority)
nice -n 10 ./backup.sh # priority 10 (lower)
nice -n -5 ./critical.sh # higher priority (needs root)
# renice: change priority of running process
renice 5 -p 12345 # PID 12345 to priority 5
renice 10 -u alice # all of alice's processes
renice -5 -p 12345 # higher priority (needs root)
# view priorities
ps -l # NI column shows nice value
top # NI column
# nice values:
# -20 highest priority (most favorable)
# 0 default
# 19 lowest priority (least favorable)
# only root can set negative (higher) priority
# practical: run CPU-intensive task at low priority
nice -n 19 make -j4 &
# ionice: I/O priority
ionice -c 3 ./backup.sh # idle I/O priority
ionice -c 2 -n 7 ./backup.sh # best-effort, low
# cpulimit: limit CPU usage (install separately)
cpulimit -p 12345 -l 50 # limit to 50% CPU/proc Filesystem
/proc is a virtual filesystem (no disk storage) that exposes kernel and process information. /proc/PID/ contains per-process data. /proc/cpuinfo, /proc/meminfo show hardware. /proc/sys/ contains tunable kernel parameters (equivalent to sysctl). Writing to /proc/sys/ changes kernel settings immediately (but use sysctl for persistence). /proc/mounts shows currently mounted filesystems. This is the primary interface between userspace and the kernel.
# /proc: virtual filesystem with kernel/process info
# process info by PID
cat /proc/1234/status # process status
cat /proc/1234/cmdline # command line (null-separated)
ls -l /proc/1234/fd/ # open file descriptors
cat /proc/1234/environ # environment variables (null-separated)
# system info
cat /proc/cpuinfo # CPU information
cat /proc/meminfo # memory information
cat /proc/version # kernel version
cat /proc/uptime # uptime in seconds
cat /proc/loadavg # load average
# network info
cat /proc/net/tcp # TCP connections
cat /proc/net/dev # network interface stats
# kernel settings
ls /proc/sys/ # sysctl parameters
cat /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/ip_forward # enable IP forwarding
# mount info
cat /proc/mounts # mounted filesystems
cat /proc/filesystems # supported filesystem types
# view process memory maps
cat /proc/1234/maps # memory mappings
pmap 1234 # formatted versionSystem Information
uname & hostname
uname shows kernel info (-a for everything). hostname shows/sets the system name — hostnamectl set-hostname makes it persistent. /etc/os-release is the standard way to identify the distribution (works on all modern Linux). lscpu gives detailed CPU architecture info. dmidecode reads BIOS/SMBIOS data. These commands are the first step in any system administration task to understand what you're working with.
# kernel and system info
uname # kernel name: Linux
uname -a # all info
uname -r # kernel release: 5.15.0-91-generic
uname -m # machine hardware: x86_64
uname -n # network node (hostname)
uname -p # processor type
uname -o # operating system: GNU/Linux
# hostname
hostname # current hostname
hostname -f # fully qualified domain name
hostname -I # all IP addresses
# set hostname (persistent)
sudo hostnamectl set-hostname myserver
# distribution info
cat /etc/os-release # distro name and version
lsb_release -a # Ubuntu/Debian specific
cat /etc/debian_version # Debian version
# kernel version
cat /proc/version
dmesg | grep "Linux version"
# system architecture
arch # e.g., x86_64
dpkg --print-architecture # Debian/Ubuntu
# BIOS/hardware info
sudo dmidecode -t system # system info
sudo dmidecode -t bios # BIOS info
lshw # hardware list
lscpu # CPU detailsdf — Disk Free Space
df reports filesystem disk space. -h (human-readable) is essential. -T shows the filesystem type (ext4, xfs, btrfs, tmpfs, nfs). -i shows inode usage — you can run out of inodes before running out of space (common with many small files). -x excludes filesystem types (useful to filter out virtual filesystems like tmpfs). When disk is full, check df -h first, then use du to find the space-consuming directories.
# show disk space for all filesystems
df # all mounted filesystems
df -h # human-readable (K, M, G)
df -H # SI units (KB, MB, GB)
# specific filesystem
df -h /home # space for /home
df -h /dev/sda1 # specific device
# filesystem type
df -T # show type (ext4, xfs, tmpfs, etc.)
df -Th # human-readable + type
# exclude specific types
df -x tmpfs -x devtmpfs # exclude virtual filesystems
# inodes (file count limit)
df -i # inode usage
df -ih # human-readable inode usage
# local filesystems only
df -l
# show total
df -h --total
# find the filesystem using the most space
df -h | sort -rh -k5 | head
# real-time: watch disk space
watch -n 5 df -hdu — Disk Usage
du measures disk usage. -s (summary) shows only totals; -h is human-readable. du -sh */ gives immediate subdirectory sizes. sort -rh sorts by human-readable values. --apparent-size shows logical file size vs actual disk blocks. --exclude skips patterns. For finding what's consuming disk space, the workflow is: df -h (which filesystem is full) → du -sh * (which directory) → find (which files).
# size of current directory
du -sh .
# size of specific directories
du -sh /var/log /tmp /home
# size of all immediate subdirectories
du -sh */
du -sh * | sort -rh # sorted largest first
# top 10 largest directories
du -sh * | sort -rh | head -10
# maximum depth
du -h -d 1 # depth 1 only
du -h --max-depth=2
# include hidden files
du -sh .[!.]*
# apparent size (logical, not disk blocks)
du -sh --apparent-size file
# exclude patterns
du -sh --exclude='*.log' .
du -sh --exclude=node_modules .
# total of multiple directories
du -sh /var/log /var/lib /tmp | tail -1
# find largest files (not just directories)
find / -type f -exec ls -lhS {} + 2>/dev/null | head -10
find . -type f -printf '%s %p\n' | sort -rn | head -10
# sort all subdirectories by size
du -sh ./*/* | sort -rh | head -20free — Memory Usage
free shows memory usage. The 'available' column is the most meaningful — it estimates how much memory is available for starting new applications without swapping. Linux intentionally uses free RAM as cache (buff/cache) to speed up disk reads; this is reclaimed on demand, so low 'free' is normal. Swap usage should be monitored — high swap usage with active swapping (si/so in vmstat) indicates memory pressure.
# show memory usage
free # in KB
free -h # human-readable
free -m # in MB
free -g # in GB
# continuous update
free -h -s 1 # update every 1 second
watch -n 1 free -h # alternative
# output:
# total used free shared buff/cache available
# Mem: 16G 4G 2G 0.5G 10G 11G
# Swap: 8G 0G 8G
# detailed memory info
cat /proc/meminfo
# key fields:
# total total RAM
# used used by applications
# free completely free
# buff/cache kernel buffers and page cache
# available estimate of memory available for new apps
# IMPORTANT: Linux uses free RAM for cache
# 'available' is more meaningful than 'free'
# Cache is reclaimed on demand, so don't panic if 'free' is low
# swap info
swapon --show # show swap devices
cat /proc/swaps
# clear cache (requires root, use with caution)
sudo sysctl vm.drop_caches=3 # free pagecache, dentries, inodes
# process memory usage
ps aux --sort=-%mem | head -10 # top memory consumersuptime & System Load
uptime shows how long the system has been running and the load average (1/5/15 minute averages). Load average represents the average number of processes waiting for CPU — should generally be below the number of CPU cores. vmstat shows real-time CPU, memory, and I/O stats — the 'r' column (runnable processes) and 'si/so' (swap in/out) are key indicators. iostat and sar (from sysstat package) provide detailed I/O and historical data.
# system uptime and load average
uptime
# 10:30:00 up 5 days, 3:21, 2 users, load average: 0.50, 0.35, 0.25
# load average: 1min, 5min, 15min
# = average number of processes waiting for CPU
# rule of thumb: should be < number of CPU cores
# check CPU count
nproc # number of CPU cores
lscpu | grep "^CPU(s):"
# detailed load and context switches
cat /proc/loadavg
# 0.50 0.35 0.25 2/500 12345
# vmstat: system stats over time
vmstat 1 5 # every 1 second, 5 times
# procs --memory-- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa
# iostat: I/O statistics (needs sysstat package)
iostat 1 # every 1 second
iostat -x 1 # extended stats
# mpstat: per-CPU statistics
mpstat -P ALL 1
# sar: historical system activity
sar -u # CPU usage history
sar -r # memory usage history
# who is logged in
who
w # with what they're doingNetworking
ping & traceroute
ping tests reachability and measures latency using ICMP echo. -c limits the count (otherwise it runs forever). traceroute shows each hop to the destination — useful for diagnosing where connectivity fails. mtr combines traceroute with continuous ping (excellent for diagnosing intermittent issues). dig queries DNS records (more detailed than nslookup). nc (netcat) -z tests if a TCP port is open without sending data.
# test connectivity
ping google.com
ping -c 4 google.com # 4 packets then stop
ping -i 0.5 google.com # 0.5s interval
ping -W 2 google.com # 2s timeout per packet
# ping with timestamp
ping -D google.com # unix timestamps
# check if host is reachable
ping -c 1 -W 1 host && echo "up" || echo "down"
# trace network path
traceroute google.com
traceroute -n google.com # no DNS resolution (faster)
mtr google.com # traceroute + ping (continuous)
# trace with TCP (bypasses ICMP blocking)
tcptraceroute google.com 443
# DNS lookup
dig example.com
dig +short example.com # just the IP
dig MX example.com # mail records
dig NS example.com # name servers
dig @8.8.8.8 example.com # specific DNS server
# alternative DNS tools
nslookup example.com
host example.com
host -t MX example.com
# check open ports
nc -zv google.com 443 # check if port 443 is open
nc -zv google.com 80 443 22 # check multiple portsnetstat & ss
ss is the modern replacement for netstat — faster and more detailed. -tlnp shows listening TCP ports with process names (needs root for -p). To find what's using a port: ss -tlnp | grep :PORT or lsof -i :PORT. ip replaces ifconfig/route (from the iproute2 package). Key: -n prevents DNS resolution (much faster). Check 'state established' for active connections. netstat is deprecated but still widely available.
# ss: modern socket statistics (replaces netstat)
ss -tlnp # listening TCP ports + process
ss -tunap # all TCP/UDP connections
ss -s # socket summary
# netstat: classic network statistics
netstat -tlnp # listening TCP ports
netstat -tunap # all connections
netstat -rn # routing table
netstat -i # interface statistics
# common ss/netstat flags:
# -t TCP
# -u UDP
# -l listening sockets only
# -n numeric (no DNS resolution, faster)
# -a all sockets
# -p show process using socket (needs root)
# -r routing table
# find what's listening on a port
ss -tlnp | grep :80
lsof -i :8080 # alternative
# check established connections
ss -tn state established
# show all connections to a specific host
ss -tn dst google.com
# view network interfaces
ip addr # modern
ip a # short form
ifconfig # legacy
# view routing table
ip route # modern
route -n # legacy
# view interface statistics
ip -s link
ifconfig eth0curl — HTTP Client
curl is the essential HTTP client for the command line. -X sets the method; -d sends POST data; -H sets headers; -L follows redirects; -O saves with the remote filename; -s is silent (for scripting); -w '%{http_code}' extracts just the status code. For JSON APIs, combine -H 'Content-Type: application/json' with -d. -u provides basic auth. For REST API testing, curl is the universal standard.
# basic GET request
curl https://api.example.com/data
# save to file
curl -O https://example.com/file.zip # save with remote filename
curl -o file.zip https://example.com/file.zip # custom name
# follow redirects
curl -L https://short.url/abc
# HTTP methods
curl -X POST https://api.example.com/users
curl -X PUT -d '{"name":"Alice"}' https://api.example.com/users/1
curl -X DELETE https://api.example.com/users/1
# send data
curl -d "name=Alice&age=30" https://api.example.com/form
curl -H "Content-Type: application/json" \
-d '{"name":"Alice"}' https://api.example.com/users
# headers
curl -H "Authorization: Bearer TOKEN" \
-H "Accept: application/json" \
https://api.example.com/data
# verbose / headers only
curl -v https://example.com # verbose
curl -I https://example.com # headers only
# authentication
curl -u user:pass https://api.example.com # basic auth
curl --netrc-file ~/.netrc https://... # netrc
# download with progress bar
curl --progress-bar -O https://example.com/largefile.iso
# get HTTP status code only
curl -s -o /dev/null -w "%{http_code}" https://example.com
# retry on failure
curl --retry 3 --retry-delay 5 https://example.com
# pass query parameters
curl "https://api.example.com/search?q=linux&page=2"wget — Download Files
wget downloads files non-interactively. -c resumes interrupted downloads (essential for large files). -m mirrors websites (with -k for link conversion). -r recurses with -l for depth. -i reads URLs from a file. wget is better than curl for recursive/mirror downloads and for resuming — curl uses -C - for resume. --limit-rate throttles bandwidth. wget handles redirects and cookies automatically.
# download a file
wget https://example.com/file.zip
# download with custom name
wget -O myfile.zip https://example.com/file.zip
# download in background
wget -b https://example.com/largefile.iso
# resume interrupted download
wget -c https://example.com/largefile.iso
# mirror a website
wget -m https://example.com
wget -m -k -K -E https://example.com # convert links for local viewing
# recursive download (limited depth)
wget -r -l 2 https://example.com
# download all files of a type
wget -r -A pdf https://example.com/documents/
# continue and retry
wget -t 5 --waitretry=10 https://example.com/file.zip
# quiet mode (no output)
wget -q https://example.com/file.zip
# download from a list of URLs
wget -i urls.txt
# authentication
wget --user=user --password=pass https://example.com/protected
# set user agent
wget --user-agent="Mozilla/5.0" https://example.com
# limit download speed
wget --limit-rate=200k https://example.com/file.zip
# ignore certificate errors
wget --no-check-certificate https://example.com/file.zipip & ifconfig
ip (from iproute2) is the modern replacement for ifconfig/route. ip a shows addresses; ip link shows layer 2; ip route shows the routing table. Changes with ip are temporary (lost on reboot) — for persistence, edit /etc/netplan/ (Ubuntu), /etc/network/interfaces (Debian), or use nmcli (NetworkManager). ifconfig is deprecated but still widely used. For wireless, use iw/iwconfig.
# view network interfaces (modern)
ip addr show # all interfaces
ip a # short form
ip a show eth0 # specific interface
# view link status
ip link show
ip -s link show eth0 # with statistics
# bring interface up/down
sudo ip link set eth0 up
sudo ip link set eth0 down
# assign IP address
sudo ip addr add 192.168.1.100/24 dev eth0
sudo ip addr del 192.168.1.100/24 dev eth0
# routing table
ip route show
ip route # short form
sudo ip route add default via 192.168.1.1
# view ARP table
ip neigh # neighbors (ARP)
arp -a # legacy
# DNS configuration
cat /etc/resolv.conf
# ifconfig (legacy, still common)
ifconfig # all interfaces
ifconfig eth0 # specific interface
sudo ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
# change MAC address
sudo ip link set dev eth0 address 00:11:22:33:44:55
# view wireless info
iwconfig # wireless interfaces
iw list # wireless capabilities
# network manager (desktop)
nmcli device status
nmcli connection showDNS Lookup (dig, nslookup)
dig is the most powerful DNS lookup tool. +short gives concise output. Common record types: A (IPv4), AAAA (IPv6), MX (mail), NS (nameservers), TXT (text/SPF), CNAME (alias). @server queries a specific DNS server. -x does reverse lookups (IP to hostname). +trace shows the full resolution path from root servers. host is a simpler alternative. DNS propagation checks compare results across multiple servers.
# dig: detailed DNS lookup
dig example.com
dig +short example.com # just the IP address
dig example.com A # A record (IPv4)
dig example.com AAAA # AAAA record (IPv6)
dig example.com MX # mail exchange
dig example.com NS # name servers
dig example.com TXT # TXT records (SPF, etc.)
dig example.com SOA # start of authority
# query specific DNS server
dig @8.8.8.8 example.com
dig @1.1.1.1 example.com MX
# reverse DNS lookup
dig -x 1.2.3.4
dig -x 8.8.8.8 +short
# trace DNS resolution path
dig +trace example.com
# JSON output (for scripting)
dig +json example.com
# nslookup: simpler alternative
nslookup example.com
nslookup example.com 8.8.8.8 # use specific server
# host: concise output
host example.com # IP address
host -t MX example.com # mail records
host -t NS example.com # name servers
host 8.8.8.8 # reverse lookup
# check DNS propagation
# compare results from multiple servers
for dns in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "=== $dns ==="
dig @$dns example.com +short
done
# flush DNS cache
sudo systemd-resolve --flush-caches # systemd
sudo resolvectl flush-caches # newer systemdPackage Management
apt (Debian/Ubuntu)
apt is the user-friendly frontend to dpkg/apt-get (Debian/Ubuntu). Always run apt update before install/upgrade to refresh the package list. upgrade keeps packages; full-upgrade may remove packages to resolve dependencies. purge removes config files. autoremove cleans orphaned dependencies. dpkg -S finds which package owns a file. apt-mark hold prevents a package from being upgraded (useful for pinned versions).
# update package list
sudo apt update
# upgrade installed packages
sudo apt upgrade # upgrade without removing
sudo apt full-upgrade # upgrade, may remove packages
sudo apt dist-upgrade # alias for full-upgrade
# install packages
sudo apt install nginx
sudo apt install nginx=1.18* # specific version
sudo apt install -y nginx # no prompt
# remove packages
sudo apt remove nginx # remove but keep config
sudo apt purge nginx # remove including config
sudo apt autoremove # remove unused dependencies
sudo apt clean # clean downloaded packages
# search for packages
apt search nginx
apt-cache search web server
# show package info
apt show nginx
apt-cache policy nginx # available versions
# list installed packages
apt list --installed
dpkg -l | grep nginx
# list files installed by a package
dpkg -L nginx
dpkg -S /usr/bin/curl # which package owns a file
# add PPA (Ubuntu)
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
# hold package (prevent upgrade)
sudo apt-mark hold nginx
sudo apt-mark unhold nginxdnf/yum (RHEL/Fedora/CentOS)
dnf replaces yum on modern Fedora/RHEL/CentOS 8+. The commands are similar. dnf provides (or yum provides) finds which package provides a file — essential for finding missing dependencies. rpm -ql lists files in an installed package; rpm -qf finds the owner of a file. EPEL provides additional packages for RHEL/CentOS. dnf groupinstall installs package groups (like Development Tools). yum still works as a symlink to dnf on many systems.
# dnf: modern Fedora/RHEL/CentOS
sudo dnf install nginx
sudo dnf update # update all packages
sudo dnf upgrade # same as update
sudo dnf remove nginx
# search
dnf search nginx
dnf list available # all available packages
dnf list installed # installed packages
# package info
dnf info nginx
dnf provides /usr/bin/curl # which package provides a file
# clean cache
sudo dnf clean all
# group install
dnf groupinstall "Development Tools"
dnf group list
# yum: older CentOS/RHEL (still available)
sudo yum install nginx
sudo yum update
sudo yum remove nginx
yum search nginx
yum provides /usr/bin/curl
# enable EPEL repository (Extra Packages for Enterprise Linux)
sudo dnf install epel-release
# list dependencies
dnf repoquery --requires nginx
dnf repoquery --deplist nginx
# downgrade a package
sudo dnf downgrade nginx
# view package files
rpm -ql nginx # files installed by package
rpm -qi nginx # package info
rpm -qf /usr/bin/curl # which package owns filepacman (Arch Linux)
pacman is Arch Linux's package manager. -Syu is the essential system update command (always update the full system, never partial). -S installs, -R removes, -Q queries local. -Rs removes with unused dependencies; -Rns also removes config. Arch is a rolling release — partial upgrades are unsupported and can break the system. The AUR (Arch User Repository) provides community packages via helpers like yay/paru.
# update system
sudo pacman -Syu # sync repos + upgrade all
# ALWAYS update the full system on Arch
# install packages
sudo pacman -S nginx
sudo pacman -S --needed nginx # don't reinstall if up to date
# remove packages
sudo pacman -R nginx # remove package only
sudo pacman -Rs nginx # remove + unused deps
sudo pacman -Rns nginx # remove + deps + config
# search packages
pacman -Ss nginx # search repos
pacman -Qs nginx # search installed
# package info
pacman -Si nginx # info from repos
pacman -Qi nginx # info from installed
pacman -Ql nginx # list files
pacman -Qo /usr/bin/curl # which package owns file
# list orphaned packages
pacman -Qdt
# clean cache
sudo pacman -Sc # clean old packages
sudo pacman -Scc # clean all cache
# query local database
pacman -Q # all installed packages
pacman -Qe # explicitly installed
pacman -Qm # foreign (AUR) packages
# AUR (Arch User Repository)
# install yay or paru for AUR support
yay -S package-name # install from AUR
# downgrade a package
sudo pacman -U /var/cache/pacman/pkg/package-1.0-1.x86_64.pkg.tar.zstFinding Packages
Finding packages by keyword (apt/dnf search) or by file (apt-file/dnf provides/pacman -F) is essential when you get 'command not found'. The 'which package owns this file' query (dpkg -S / rpm -qf / pacman -Qo) is invaluable for understanding what's installed. apt-file (Debian) and pacman -F (Arch) search file databases of packages not yet installed — useful for finding which package provides a needed header or library.
# Debian/Ubuntu
apt search keyword
apt-cache search keyword
apt list --installed | grep keyword
# RHEL/Fedora
dnf search keyword
yum search keyword
# Arch
pacman -Ss keyword
# search by file name/path
# Debian: apt-file (separate package)
sudo apt install apt-file
sudo apt-file update
apt-file find /usr/bin/curl
apt-file search curl.h
# RHEL/Fedora
dnf provides /usr/bin/curl
dnf provides *curl.h
yum whatprovides /usr/bin/curl
# Arch
pacman -F /usr/bin/curl
pacman -Fy # update file database first
# check if package is installed
dpkg -l | grep nginx # Debian
rpm -q nginx # RHEL
pacman -Q nginx # Arch
# list files installed by a package
dpkg -L nginx # Debian
rpm -ql nginx # RHEL
pacman -Ql nginx # Arch
# find which package owns a file
dpkg -S /usr/bin/curl # Debian
rpm -qf /usr/bin/curl # RHEL
pacman -Qo /usr/bin/curl # Arch
# show package dependencies
apt-cache depends nginx # Debian
dnf repoquery --requires nginx # RHEL
pacman -Si nginx # ArchRepository Management
Repositories are package sources. On Debian/Ubuntu, PPAs (Personal Package Archives) provide third-party software — always add the signing key to verify packages. /etc/apt/sources.list.d/ holds custom repo files. On RHEL/Fedora, dnf config-manager manages repos; EPEL is essential for RHEL. On Arch, edit /etc/pacman.conf. Always verify GPG keys to prevent installing malicious packages. apt policy and dnf repolist show configured repositories.
# Debian/Ubuntu: add repository
sudo add-apt-repository ppa:deadsnakes/ppa
sudo add-apt-repository "deb https://packages.example.com/ubuntu jammy main"
# remove repository
sudo add-apt-repository --remove ppa:deadsnakes/ppa
# add repository manually
echo "deb https://packages.example.com/ubuntu jammy main" | \
sudo tee /etc/apt/sources.list.d/example.list
# add signing key (Debian)
wget -qO- https://example.com/key.gpg | \
sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/example.gpg
# list repositories
apt policy # show configured repos
grep -r "" /etc/apt/sources.list.d/
# RHEL/Fedora: enable repository
sudo dnf config-manager --add-repo https://example.com/repo.repo
sudo dnf install epel-release # enable EPEL
sudo dnf repolist # list enabled repos
sudo dnf repolist all # list all repos
# enable/disable a repo
sudo dnf config-manager --set-enabled epel
sudo dnf config-manager --set-disabled epel
# Arch: edit /etc/pacman.conf
# [custom]
# Server = https://example.com/$repo/os/$arch
# verify package integrity
dpkg --verify # Debian
rpm -Va # RHELService Management (systemd)
Service Control
systemctl manages systemd services. start/stop/restart are the basic operations. reload sends a signal to re-read config without full restart (graceful). status shows current state and recent logs. mask completely prevents a service from being started (even manually) — stronger than disable. is-active and is-enabled are useful for scripts (return 0 for active/enabled).
# start/stop/restart services
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx # reload config without restart
# try-restart (only if running)
sudo systemctl try-restart nginx
# reload or restart (reload if possible, otherwise restart)
sudo systemctl reload-or-restart nginx
# send signal to service
sudo systemctl kill nginx
sudo systemctl kill -s HUP nginx
# check service status
sudo systemctl status nginx
systemctl is-active nginx # active/inactive
systemctl is-enabled nginx # enabled/disabled
systemctl is-failed nginx # failed/running
# show service logs (recent)
sudo systemctl status nginx
# mask a service (prevent from starting)
sudo systemctl mask nginx
sudo systemctl unmask nginx
# reset failed state
sudo systemctl reset-failed
# emergency: reboot/poweroff
sudo systemctl reboot
sudo systemctl poweroff
sudo systemctl halt
sudo systemctl suspendService Status & Details
systemctl status gives a comprehensive view: state, PID, memory/CPU usage, and recent logs. systemctl cat shows the unit file (including any overrides). systemctl edit creates a drop-in override file without modifying the original — safe for customizing vendor-provided services. After editing unit files, run daemon-reload to tell systemd about the changes. list-dependencies shows what a service requires.
# detailed status
systemctl status nginx
# Shows: loaded state, active state, recent log entries, cgroup info
# just the active state
systemctl is-active nginx # "active" or "inactive"
# is it enabled at boot?
systemctl is-enabled nginx # "enabled" or "disabled"
# check if service failed
systemctl is-failed nginx
# list dependencies
systemctl list-dependencies nginx
# show service file
systemctl cat nginx
systemctl cat nginx.service
# show service properties
systemctl show nginx
systemctl show -p ExecStart nginx
# view service file location
systemctl show -p FragmentPath nginx
# edit service file (override)
sudo systemctl edit nginx # creates override file
sudo systemctl edit --full nginx # edit full unit file
# reload systemd after editing unit files
sudo systemctl daemon-reload
# list sockets
systemctl list-sockets
# check all failed services
systemctl --failedEnable/Disable Services
enable makes a service start at boot (creates symlinks); disable removes the symlinks. --now combines enable/disable with start/stop. list-unit-files shows all available services (installed but maybe not running); list-units shows loaded/active ones. The default target (multi-user = text mode, graphical = GUI) replaces traditional runlevels. Use --state= to filter by status.
# enable service to start at boot
sudo systemctl enable nginx
# enable and start in one command
sudo systemctl enable --now nginx
# disable (don't start at boot)
sudo systemctl disable nginx
# disable and stop in one command
sudo systemctl disable --now nginx
# re-enable (apply symlink changes)
sudo systemctl reenable nginx
# check if enabled
systemctl is-enabled nginx
# list enabled services
systemctl list-unit-files --state=enabled
# list all service unit files
systemctl list-unit-files --type=service
# list running services
systemctl list-units --type=service --state=running
# list all loaded services
systemctl list-units --type=service
# list services by state
systemctl list-units --type=service --state=failed
systemctl list-units --type=service --state=exited
# default boot target
systemctl get-default # usually graphical.target or multi-user.target
# change boot target
sudo systemctl set-default multi-user.target # text mode (runlevel 3)
sudo systemctl set-default graphical.target # GUI mode (runlevel 5)Listing & Filtering Services
list-units shows what's currently loaded in memory; list-unit-files shows what's installed on disk. --type filters by unit type (service, socket, timer, mount, target). --state filters by status (running, exited, failed, enabled, disabled). Timers (systemd timers) are the systemd-native replacement for cron jobs — they support dependency-based scheduling and are visible via list-timers. --reverse shows what depends on a unit.
# list all active units
systemctl list-units
# list only services
systemctl list-units --type=service
# list by state
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=exited
systemctl list-units --type=service --state=failed
# list all unit files (installed)
systemctl list-unit-files --type=service
# list by state (unit files)
systemctl list-unit-files --state=enabled
systemctl list-unit-files --state=disabled
systemctl list-unit-files --state=masked
# list by target (runlevel)
systemctl list-units --type=target
# list sockets
systemctl list-units --type=socket
systemctl list-sockets
# list timers (replaces cron for many tasks)
systemctl list-timers
systemctl list-timers --all
# list mounts
systemctl list-units --type=mount
# show all units (including inactive)
systemctl list-units --all
# show specific unit type
systemctl list-units --type=service --all
# count services by state
systemctl list-units --type=service --no-legend | wc -l
systemctl list-units --type=service --state=running --no-legend | wc -l
# find services that depend on a unit
systemctl list-dependencies --reverse nginxjournalctl — System Logs
journalctl queries the systemd journal (structured logging). -f follows in real-time. -u filters by unit (service). -b filters by boot (-b -1 = previous boot). --since/--until use flexible time expressions. -p filters by priority (emerg to debug). The journal is persistent across reboots (stored in /var/log/journal/). --vacuum-time and --vacuum-size control disk usage. journalctl replaces /var/log/messages for systemd systems.
# view all logs (newest last)
journalctl
# follow logs (like tail -f)
journalctl -f
# today's logs
journalctl --since today
journalctl -u nginx --since today
# specific time range
journalctl --since "2024-01-01" --until "2024-01-02"
journalctl --since "1 hour ago"
journalctl --since "30 min ago"
# logs for a specific service
journalctl -u nginx
journalctl -u nginx -f # follow
journalctl -u nginx --since "1 hour ago"
# logs for current boot
journalctl -b # current boot
journalctl -b -1 # previous boot
journalctl --list-boots # list all boots
# filter by priority
journalctl -p err # errors only
journalctl -p warning # warnings and above
# 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug
# filter by process
journalctl _PID=1234
journalctl _UID=1000
# show kernel messages only
journalctl -k
# output format
journalctl -o json # JSON format
journalctl -o short-iso # ISO timestamps
journalctl --no-pager # no pager (for scripts)
# clear old logs
sudo journalctl --vacuum-time=7d # keep last 7 days
sudo journalctl --vacuum-size=100M # keep 100MB maxScheduled Tasks (Cron)
crontab Basics
crontab -e edits your scheduled tasks. The format is: minute hour day-of-month month day-of-week command. * means 'every'. */N means 'every N'. Cron runs with a minimal environment — always use absolute paths for commands and scripts. Scripts should set PATH explicitly. Output is emailed to the user (configure MAILTO in crontab) or redirect to a log file. System-wide tasks go in /etc/crontab or /etc/cron.*/ directories.