Linux File Operations
File operations, permissions, and filesystem management.
Find Patterns
# BASIC FIND
find /path -name "*.txt" # By name (case sensitive)
find /path -iname "*.txt" # Case insensitive
find /path -type f # Files only
find /path -type d # Directories only
find /path -type l # Symlinks only
# SIZE FILTERS
find /path -size +100M # Larger than 100MB
find /path -size -1k # Smaller than 1KB
find /path -size 50M # Exactly 50MB
find /path -empty # Empty files/dirs
# TIME FILTERS
find /path -mtime -1 # Modified in last 24h
find /path -mtime +7 # Modified more than 7 days ago
find /path -mmin -30 # Modified in last 30 minutes
find /path -newer reference_file # Newer than reference
# TIME FILTER EXPLAINED:
# -mtime n : Modified n*24 hours ago
# -mtime -n : Modified less than n*24 hours ago
# -mtime +n : Modified more than n*24 hours ago
# Same pattern for -atime (access), -ctime (change)
# PERMISSION FILTERS
find /path -perm 644 # Exact permissions
find /path -perm -644 # At least these permissions
find /path -perm /u+x # User executable (any match)
find /path -perm -u+w,g+w # User AND group writable
# OWNER FILTERS
find /path -user evanusmodestus # By user
find /path -group wheel # By group
find /path -nouser # No user (orphaned)
find /path -nogroup # No group
# COMBINING FILTERS
find /path -type f -name "*.log" -size +10M -mtime +7
find /path \( -name "*.txt" -o -name "*.md" \) # OR logic
find /path -type f ! -name "*.bak" # NOT logic
# EXEC ACTIONS
find /path -type f -name "*.bak" -delete # Delete (careful!)
find /path -type f -exec chmod 644 {} \; # Change permissions
find /path -type f -exec chmod 644 {} + # Batch mode (faster)
# EXEC vs + EXPLAINED:
# {} \; - Runs command once per file: chmod 644 file1; chmod 644 file2
# {} + - Batches files: chmod 644 file1 file2 file3 (much faster)
# XARGS PATTERNS (even faster)
find /path -type f -name "*.log" -print0 | xargs -0 rm -f
find /path -type f -name "*.txt" | xargs grep "pattern"
# -print0 and xargs -0 handle filenames with spaces/special chars
# ADVANCED: Find + AWK
# Large files with sizes
find /var -type f -size +50M -exec ls -lh {} \; 2>/dev/null | \
awk '{print $5, $9}' | sort -hr
# Files modified today by hour
find /var/log -type f -mtime 0 -ls 2>/dev/null | \
awk '{print $9}' | cut -d: -f1 | sort | uniq -c
# PRUNE DIRECTORIES (don't descend)
find / -path /proc -prune -o -name "*.conf" -print
find / \( -path /proc -o -path /sys \) -prune -o -type f -name "*.log" -print
# INFRASTRUCTURE: Find configs across systems
for host in vault-01 bind-01 ise-01; do
echo "=== $host ==="
ssh "$host" "find /etc -name '*.conf' -mtime -1 2>/dev/null" | head -5
done
Permissions
# PERMISSION BASICS
# rwx = 4+2+1 = 7
# r-x = 4+0+1 = 5
# r-- = 4+0+0 = 4
# CHMOD - Change Mode
chmod 755 file # rwxr-xr-x
chmod 644 file # rw-r--r--
chmod 600 file # rw-------
# Symbolic mode
chmod u+x file # Add user execute
chmod g-w file # Remove group write
chmod o= file # Remove all other permissions
chmod a+r file # Add read for all
chmod u=rwx,g=rx,o= file # Explicit
# RECURSIVE
chmod -R 755 dir # All files and dirs
chmod -R u+rwX,go+rX dir # Capital X = execute only on dirs
# X (capital) EXPLAINED:
# x = always add execute
# X = add execute only if directory OR already has execute
# Perfect for mixed file/dir recursion
# SPECIAL PERMISSIONS
# setuid (4xxx) - Execute as file owner
chmod u+s /path/to/binary # Or chmod 4755
# Only useful for binaries, dangerous if misused
# setgid (2xxx) - Execute as file group / inherit group for dirs
chmod g+s /path/to/dir # Or chmod 2755
# New files inherit directory's group - great for shared folders
# sticky bit (1xxx) - Only owner can delete files
chmod +t /tmp # Or chmod 1777
# Standard for /tmp - anyone can create, only owner can delete
# CHOWN - Change Owner
chown user file # Change owner
chown user:group file # Change owner and group
chown :group file # Change group only
chown -R user:group dir # Recursive
# UMASK - Default Permissions
umask # Show current umask
umask 022 # New files: 644, dirs: 755
umask 077 # New files: 600, dirs: 700
# HOW UMASK WORKS:
# Files: 666 - umask = permissions
# Dirs: 777 - umask = permissions
# umask 022: files=644, dirs=755
# umask 077: files=600, dirs=700
# SECURITY PATTERNS
# SSH keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
chmod 600 ~/.ssh/config
# Web directories
chown -R www-data:www-data /var/www
chmod -R u=rwX,g=rX,o= /var/www
# Secrets
chmod 600 /etc/ssl/private/*.key
chmod 644 /etc/ssl/certs/*.crt
# FIND PERMISSION ISSUES
# World-writable files (security risk)
find /etc -type f -perm /o+w 2>/dev/null
# SUID binaries (potential privilege escalation)
find / -perm -4000 -type f 2>/dev/null
# Files without owner
find / -nouser -o -nogroup 2>/dev/null
Access Control Lists (ACLs)
# ACLs provide fine-grained permissions beyond user/group/other
# Filesystem must be mounted with acl option (default on ext4)
# CHECK ACL SUPPORT
mount | grep acl # Check mount options
getfacl /path/to/file # View ACLs
# VIEW ACLs
getfacl file # Full ACL
getfacl -R dir # Recursive
ls -l file # + at end means ACL exists
# -rw-r--r--+ means ACLs are set
# SET ACLs
setfacl -m u:evanusmodestus:rwx file # Add user permission
setfacl -m g:devops:rx file # Add group permission
setfacl -m o::r file # Modify other
# MULTIPLE ACLs AT ONCE
setfacl -m u:user1:rwx,u:user2:rx,g:team:rwx file
# REMOVE ACLs
setfacl -x u:username file # Remove specific user ACL
setfacl -b file # Remove all ACLs
# DEFAULT ACLs (for directories - new files inherit)
setfacl -d -m u:evanusmodestus:rwx dir # Default for new files
setfacl -d -m g:team:rx dir # Group default
# VIEW DEFAULT ACLs
getfacl dir # Shows default: entries
# RECURSIVE
setfacl -R -m u:backup:rx /var/log # Apply to all files
setfacl -R -d -m u:backup:rx /var/log # Set defaults recursively
# COPY ACLs
getfacl file1 | setfacl --set-file=- file2
# BACKUP AND RESTORE ACLs
getfacl -R /path/to/dir > acls.txt
setfacl --restore=acls.txt
# ACL MASK
# The mask limits maximum effective permissions for named users/groups
setfacl -m m::rx file # Set mask to r-x
# EFFECTIVE PERMISSIONS
# getfacl shows "effective" when mask limits permissions:
# user:dev:rwx #effective:r-x
# INFRASTRUCTURE: Shared backup directory
# Allow backup user to read all, team to read/write
setfacl -R -m u:backup:rx /backup
setfacl -R -m g:ops:rwx /backup
setfacl -R -d -m u:backup:rx /backup
setfacl -R -d -m g:ops:rwx /backup
# VERIFY
getfacl /backup | grep -E '^(user|group|default):'
File Attributes (chattr/lsattr)
# Extended attributes beyond standard permissions
# Common on ext4, may vary on other filesystems
# VIEW ATTRIBUTES
lsattr file # Single file
lsattr -R dir # Recursive
lsattr -d dir # Directory itself
# ATTRIBUTE FLAGS:
# i = immutable (can't modify, delete, rename, or link)
# a = append only (can only append, not modify)
# s = secure delete (zeros on delete)
# u = undeletable (recoverable after delete)
# c = compressed
# A = no atime updates
# S = synchronous updates
# SET ATTRIBUTES
chattr +i file # Make immutable
chattr -i file # Remove immutable
chattr +a /var/log/audit.log # Append only
chattr +A file # Don't update atime
# IMMUTABLE FILE (even root can't modify!)
chattr +i /etc/resolv.conf # Prevent DNS changes
# To modify: chattr -i first
# APPEND ONLY (great for logs)
chattr +a /var/log/secure # Can only append
# Prevents log tampering (must remove attr to truncate)
# SECURITY PATTERN: Protect critical configs
chattr +i /etc/passwd
chattr +i /etc/shadow
chattr +i /etc/sudoers
# WARNING: You must remove immutable before updates!
# System updates will fail if package configs are immutable
# CHECK BEFORE UPDATING
lsattr /etc/passwd /etc/shadow 2>/dev/null | grep -E '^....i'
# INFRASTRUCTURE: Find immutable files
find /etc -type f -exec lsattr {} \; 2>/dev/null | grep -E '^....i'
# EXTENDED ATTRIBUTES (xattr)
# Key-value pairs attached to files (used by SELinux, capabilities, etc.)
getfattr -d file # List xattrs
getfattr -n user.myattr file # Get specific
setfattr -n user.myattr -v "value" file # Set
setfattr -x user.myattr file # Remove
# SELinux context (on RHEL/Rocky)
ls -Z file # View SELinux context
getfattr -n security.selinux file # Raw xattr
Links
# SYMBOLIC (SOFT) LINKS
ln -s /path/to/target /path/to/link # Create symlink
ln -sf /new/target /path/to/link # Force (replace existing)
# Symlink properties:
# - Points to a path (can be relative or absolute)
# - Can point to files or directories
# - Can cross filesystems
# - Breaks if target is deleted/moved
# HARD LINKS
ln /path/to/target /path/to/link # Create hard link
# Hard link properties:
# - Points to inode (same data blocks)
# - Only for files (not directories)
# - Must be on same filesystem
# - Target can be deleted, link still works
# - Both names are "equal" - no original
# VIEW LINK INFO
ls -l link # Shows -> target for symlinks
ls -li file # Shows inode number
stat file # Detailed info including links
# FIND HARD LINKS TO A FILE
find / -samefile /path/to/file 2>/dev/null
# Or by inode:
find / -inum 12345 2>/dev/null
# FIND BROKEN SYMLINKS
find /path -xtype l # Broken symlinks
find /path -type l ! -exec test -e {} \; -print # Alternative
# FIX BROKEN SYMLINKS
# Find and show targets
find /usr/local/bin -type l -exec ls -l {} \; | grep -E 'broken|No such'
# INFRASTRUCTURE: Dotfiles with stow
cd ~/dotfiles
stow -v vim # Symlink vim configs
stow -v -D vim # Remove symlinks
stow -v -R vim # Restow (fix)
# COMMON SYMLINK PATTERNS
# Executable in PATH
ln -s ~/bin/myscript.sh ~/.local/bin/myscript
# Version switching
ln -sf /opt/java-17 /opt/java # Point to active version
ln -sf python3.11 /usr/bin/python
# Config management
ln -s ~/.dotfiles/vimrc ~/.vimrc
ln -s ~/.dotfiles/zshrc ~/.zshrc
# READLINK - resolve symlinks
readlink link # Direct target
readlink -f link # Full canonical path
realpath link # Full canonical path (alternative)
# RELATIVE SYMLINKS
ln -sr target link # -r makes relative
# Useful for portable directory structures
File Integrity
# CHECKSUMS
md5sum file # MD5 (legacy, not secure)
sha256sum file # SHA-256 (preferred)
sha512sum file # SHA-512
# Generate checksums for multiple files
sha256sum *.tar.gz > SHA256SUMS
sha256sum -c SHA256SUMS # Verify all
# VERIFY DOWNLOAD
curl -LO https://example.com/file.tar.gz
curl -LO https://example.com/file.tar.gz.sha256
sha256sum -c file.tar.gz.sha256
# FILE METADATA
stat file # Full metadata
stat -c '%n %s %Y' file # Custom format
# %n=name, %s=size, %Y=mtime (epoch)
stat -c '%a %U:%G %n' * # Permissions owner:group name
# COMPARE FILES
diff file1 file2 # Text diff
diff -u file1 file2 # Unified format
diff -r dir1 dir2 # Recursive directory diff
cmp file1 file2 # Byte-by-byte (binary safe)
# CONFIG DRIFT DETECTION
# Baseline
sha256sum /etc/ssh/sshd_config > ~/baselines/sshd_config.sha256
# Check for changes
sha256sum -c ~/baselines/sshd_config.sha256
# sshd_config: OK or sshd_config: FAILED
# INFRASTRUCTURE: Config drift across hosts
baseline_hash="abc123..."
for host in vault-01 bind-01 ise-01; do
hash=$(ssh "$host" "sha256sum /etc/ssh/sshd_config 2>/dev/null" | awk '{print $1}')
if [[ "$hash" == "$baseline_hash" ]]; then
echo "$host: OK"
else
echo "$host: DRIFTED ($hash)"
fi
done
# AIDE (Advanced Intrusion Detection Environment)
# Install: pacman -S aide / apt install aide
aide --init # Create initial database
aide --check # Check for changes
aide --update # Update after changes
# /etc/aide.conf
# /etc CONTENT_EX # Monitor /etc
# /bin BIN_EX # Monitor binaries
# !/var/log # Exclude logs
# TRIPWIRE (alternative to AIDE)
tripwire --init # Initialize
tripwire --check # Check integrity
# RSYNC DRY-RUN FOR COMPARISON
rsync -avnc /etc/ backup:/etc/ # -n = dry-run, shows differences
# c = checksum (slower but accurate)
File Monitoring (inotify)
# INOTIFYWAIT - monitor file/directory events
# Install: pacman -S inotify-tools / apt install inotify-tools
# Watch single file
inotifywait -m /etc/passwd # Monitor continuously
# Watch directory
inotifywait -m /var/log # Changes in directory
inotifywait -mr /var/log # Recursive
# EVENTS:
# access - File read
# modify - File modified
# attrib - Attributes changed
# close - File closed
# open - File opened
# create - File created
# delete - File deleted
# move - File moved
# Filter specific events
inotifywait -m -e modify,create,delete /etc
# OUTPUT FORMATS
inotifywait -m --format '%T %w%f %e' --timefmt '%Y-%m-%d %H:%M:%S' /etc
# PRACTICAL: Watch config changes and log
inotifywait -mr -e modify,create,delete --format '%T %w%f %e' \
--timefmt '%Y-%m-%d %H:%M:%S' /etc 2>/dev/null | \
tee -a /var/log/config-changes.log
# TRIGGER ACTION ON CHANGE
inotifywait -m -e modify /etc/nginx/nginx.conf | while read path action file; do
echo "Config changed, reloading nginx..."
systemctl reload nginx
done
# AUTO-RELOAD ON SAVE (development)
inotifywait -mr -e close_write --format '%w%f' /path/to/src | while read file; do
echo "File changed: $file"
make build # or whatever build command
done
# ENTR - simpler alternative for development
# Install: pacman -S entr / apt install entr
ls *.py | entr python test.py # Run tests on change
find . -name '*.go' | entr go build # Rebuild on change
# WATCH COMMAND (poll-based, simpler)
watch -n 1 'ls -la /tmp' # Every 1 second
watch -d 'df -h' # Highlight changes
watch -d -n 5 'kubectl get pods' # k8s pod status
# INFRASTRUCTURE: Monitor critical configs
inotifywait -m -e modify \
/etc/ssh/sshd_config \
/etc/pam.d/sshd \
/etc/sudoers | while read path action file; do
echo "[$(date)] ALERT: $path was modified"
# Could send to Wazuh/Slack here
done
Disk Usage Analysis
# BASIC USAGE
df -h # Filesystem usage
df -i # Inode usage
du -sh /path # Directory size
# SORTED BY SIZE
du -h --max-depth=1 /var | sort -hr # One level, sorted
du -ah /var | sort -hr | head -20 # All files, top 20
# STAY ON FILESYSTEM
du -xh --max-depth=2 / 2>/dev/null | sort -hr | head -30
# -x prevents crossing mount points
# NCDU - Interactive exploration
ncdu /var # Navigate with arrows
ncdu -x / # Stay on filesystem
ncdu -e / # Show hidden files
# FIND LARGE FILES
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20
# With awk formatting
find / -type f -size +100M -ls 2>/dev/null | \
awk '{size=$7; name=$11; printf "%10.2f MB %s\n", size/1024/1024, name}' | \
sort -rn | head -20
# FIND OLD LARGE FILES
find /var/log -type f -size +50M -mtime +30 -ls 2>/dev/null
# ANALYZE LOG ROTATION
ls -lhS /var/log/*.log # Sorted by size
ls -lt /var/log/*.log | head -10 # Sorted by time
# SPACE BY FILE TYPE
find /path -type f -name "*.log" -exec du -ch {} + | tail -1
find /path -type f -name "*.tar.gz" -exec du -ch {} + | tail -1
# JOURNAL USAGE (systemd)
journalctl --disk-usage # Show journal size
journalctl --vacuum-size=500M # Reduce to 500MB
journalctl --vacuum-time=7d # Keep only 7 days
# DOCKER/CONTAINER CLEANUP
docker system df # Docker disk usage
docker system prune -a # Clean unused
# INFRASTRUCTURE: Multi-host analysis
for host in kvm-01 vault-01 bind-01 ise-01; do
echo "=== $host ==="
ssh "$host" "df -h / /var 2>/dev/null | tail -n +2" 2>/dev/null | \
awk -v h="$host" '{printf " %s: %s used of %s (%s)\n", $6, $3, $2, $5}'
done
# Find what's filling up
ssh kvm-01 "du -xh --max-depth=2 /var 2>/dev/null | sort -hr | head -10"
# CLEAN PACKAGE CACHE
# Arch
paccache -rk 2 # Keep last 2 versions
pacman -Sc # Clean uninstalled packages
# RHEL/Rocky
dnf clean all
dnf autoremove
# Debian/Ubuntu
apt clean
apt autoremove
File Gotchas
# WRONG: find -exec with filenames containing spaces
find /path -name "*.txt" -exec cat {} \;
# May break on spaces
# CORRECT: Use -print0 and xargs -0
find /path -name "*.txt" -print0 | xargs -0 cat
# WRONG: rm -rf with variable that might be empty
rm -rf $DIR/ # If DIR is empty, this becomes rm -rf /
# CORRECT: Quote and verify
[[ -n "$DIR" ]] && rm -rf "$DIR/"
# Or use parameter expansion
rm -rf "${DIR:?Variable is empty!}/"
# WRONG: Relying on ls output in scripts
for f in $(ls *.txt); do # Breaks on spaces
echo "$f"
done
# CORRECT: Use glob directly
for f in *.txt; do
[[ -e "$f" ]] || continue # Handle no matches
echo "$f"
done
# WRONG: chmod -R 755 on everything
chmod -R 755 /var/www # Files become executable!
# CORRECT: Different permissions for files/dirs
find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;
# Or use X
chmod -R u=rwX,g=rX,o=rX /var/www
# WRONG: Removing immutable file
rm -f /etc/resolv.conf # Operation not permitted
# CORRECT: Remove immutable first
chattr -i /etc/resolv.conf
rm -f /etc/resolv.conf
# WRONG: Symlink to relative path from wrong directory
cd /opt
ln -s myapp/bin/run /usr/local/bin/run # Broken! Points to /usr/local/bin/myapp/...
# CORRECT: Use absolute path or -r
ln -s /opt/myapp/bin/run /usr/local/bin/run
# Or relative from link location
ln -sr /opt/myapp/bin/run /usr/local/bin/run
# WRONG: ACL not preserved in cp
cp file1 file2 # ACLs lost
# CORRECT: Preserve with -a or --preserve=all
cp -a file1 file2
cp --preserve=all file1 file2
# WRONG: Checking file exists with ls
if ls file 2>/dev/null; then # Wrong exit code check
# CORRECT: Use test/[[ ]]
[[ -f file ]] && echo "exists"
[[ -d dir ]] && echo "is directory"
[[ -L link ]] && echo "is symlink"
Quick Reference
# FIND
find /path -name "*.log" -mtime -1 # Modified today
find /path -size +100M # Large files
find /path -type f -exec chmod 644 {} + # Batch chmod
# PERMISSIONS
chmod 755 dir; chmod 644 file # Standard
chmod -R u=rwX,g=rX,o= /path # Recursive safe
chown -R user:group /path # Change owner
# ACLs
getfacl file # View
setfacl -m u:user:rwx file # Set
setfacl -b file # Remove all
# ATTRIBUTES
lsattr file # View
chattr +i file # Immutable
chattr +a file # Append only
# LINKS
ln -s target link # Symlink
ln target link # Hard link
readlink -f link # Resolve
# INTEGRITY
sha256sum file > file.sha256 # Generate
sha256sum -c file.sha256 # Verify
# MONITORING
inotifywait -mr -e modify /path # Watch changes
watch -d -n 5 'command' # Poll changes
# DISK USAGE
du -xh --max-depth=2 / | sort -hr # Space usage
ncdu /var # Interactive
find / -type f -size +100M # Large files