Working with Files and Directories
Working with Files and Directories
Now that you can navigate the filesystem, it's time to learn how to actually manipulate it—create files, modify them, move them around, delete them, and control who can access them. These operations form the core of daily terminal work, whether you're organizing documents, deploying code, managing servers, or processing data.
Why file operations matter:
- Essential skills: Nearly every terminal task involves file manipulation
- Automation: Scripts that create reports, organize downloads, clean up logs
- Server management: Configuring services, managing logs, deploying applications
- Data processing: Transforming, filtering, and analyzing files at scale
- Security: Proper permissions prevent unauthorized access
This tutorial covers the fundamental commands for file and directory operations, permissions management, and file searching—the building blocks of Linux system administration and scripting.
Creating Files
touch — Create Empty Files (or Update Timestamps)
The touch command serves dual purposes: creating new empty files and updating the timestamp of existing files.
# Create a new empty file
touch newfile.txt
# Create multiple files at once
touch file1.txt file2.txt file3.txt
# Create files with a pattern using brace expansion
touch report_{jan,feb,mar,apr}.txt
# Creates: report_jan.txt, report_feb.txt, report_mar.txt, report_apr.txt
# Create numbered files
touch log_{01..10}.txt
# Creates: log_01.txt through log_10.txt
# Create a complex structure
touch {2023,2024,2025}_report_{Q1,Q2,Q3,Q4}.txt
# Creates: 2023_report_Q1.txt, 2023_report_Q2.txt, ... 2025_report_Q4.txt
# Update the timestamp of an existing file (without modifying content)
touch existingfile.txt
# Set specific timestamp
touch -t 202401151030 file.txt # Jan 15, 2024 10:30
touch -d "2024-01-15 10:30:00" file.txt # Same, more readableℹ️ What touch really does
Despite its name, touch was originally designed to update file timestamps (access time and modification time). Creating empty files is just a side effect—if the file doesn't exist, it creates it. If the file already exists, it only updates the timestamps without modifying content.
Why this matters:
- Makefile tricks: Update timestamps to trigger rebuilds
- Placeholder files: Create empty files as flags or locks
- Testing: Generate test files quickly
- Timestamp manipulation: Reset modification times for various reasons
Creating Files with Content
Often you need files with actual content, not just empty placeholders:
# Using echo (for simple one-liners)
echo "Hello, World!" > greeting.txt
# Append to existing file
echo "Another line" >> greeting.txt
# Using printf (for formatted text)
printf "Name: %s\nAge: %d\nCity: %s\n" "Alice" 30 "Seattle" > profile.txt
# Using cat with heredoc (for multi-line content)
cat > notes.txt << 'EOF'
Meeting Notes - January 15, 2024
=================================
1. Project update - on track
2. Budget review - approved
3. Next steps - deploy to staging
EOF
# Heredoc with variable expansion
cat > config.sh << EOF
#!/bin/bash
USER=$USER
HOME=$HOME
DATE=$(date +%Y-%m-%d)
EOF
# Using tee (write to file AND display on screen)
echo "Important log entry" | tee logfile.txt
# Tee with append
echo "Another entry" | tee -a logfile.txt
# Write to multiple files at once
echo "Same content everywhere" | tee file1.txt file2.txt file3.txt
# Create file from command output
ls -la > directory_listing.txt
df -h > disk_usage.txt
ps aux > process_list.txtPractical example: Quick script creation
# Create a quick backup script
cat > backup.sh << 'EOF'
#!/bin/bash
tar -czf backup_$(date +%Y%m%d).tar.gz /home/$USER/Documents
echo "Backup completed: backup_$(date +%Y%m%d).tar.gz"
EOF
chmod +x backup.sh
./backup.shViewing Files
cat — Display File Contents
The cat command (concatenate) displays file contents or combines multiple files:
# Display a file
cat file.txt
# Display with line numbers
cat -n file.txt
# Display with line numbers (non-blank lines only)
cat -b file.txt
# Show invisible characters (tabs as ^I, end-of-line as $)
cat -A file.txt
# Useful for debugging whitespace issues
# Show non-printing characters
cat -v file.txt
# Squeeze blank lines (show only one blank line)
cat -s file.txt
# Concatenate multiple files
cat file1.txt file2.txt file3.txt
# Concatenate and save to a new file
cat header.txt body.txt footer.txt > complete.txt
# Append one file to another
cat additional.txt >> main.txt
# Number all output lines
cat -n file1.txt file2.txt > numbered_combined.txtWhen NOT to use cat:
- Large files: Use
lessinstead—catdumps everything at once - Binary files: Use
hexdump,xxd, orfileinstead - Just counting lines: Use
wc -linstead ofcat file | wc -l
Common cat pitfalls (useless use of cat):
# Inefficient (spawns unnecessary process)
cat file.txt | grep "pattern"
# Efficient (grep can read files directly)
grep "pattern" file.txt
# Inefficient
cat file.txt | wc -l
# Efficient
wc -l < file.txt
# Or even better:
wc -l file.txtless — Page Through Files
For large files, cat dumps everything at once and scrolls off the screen. less provides a paginated, interactive viewer:
# View a file
less largefile.txt
# View with line numbers
less -N file.txt
# Case-insensitive search by default
less -i file.txt
# Follow mode (like tail -f)
less +F /var/log/syslog
# Press Ctrl+C to stop following, then navigate
# Press F again to resume following
# Open multiple files
less file1.txt file2.txt
# Use :n for next file, :p for previousNavigation in less:
| Key | Action | Use Case |
|---|---|---|
Space / f | Page forward | Quick browsing |
b | Page backward | Review previous content |
d / u | Half-page down / up | Fine-grained control |
g / G | Go to beginning / end | Jump to start/end |
/pattern | Search forward | Find specific text |
?pattern | Search backward | Find earlier occurrences |
n / N | Next / previous match | Navigate search results |
q | Quit | Exit viewer |
F | Follow mode (like tail -f) | Monitor log files |
v | Open in editor | Edit the file |
&pattern | Show only matching lines | Filter view |
h | Help | See all commands |
m[a-z] | Mark position | Bookmark location |
'[a-z] | Return to mark | Jump to bookmark |
💡 Less is more... literally
less was created as an improvement over the older more program. Hence the Unix joke: "less is more." Always prefer less over more—it supports backward scrolling, searching, and many more features. On most modern systems, more is actually just less in disguise!
head and tail — View Parts of Files
Extract the beginning or end of files efficiently:
# First 10 lines (default)
head file.txt
# First 20 lines
head -n 20 file.txt
head -20 file.txt # Shorthand
# First 100 bytes
head -c 100 file.txt
# First lines of multiple files (shows filenames)
head file1.txt file2.txt file3.txt
# Last 10 lines (default)
tail file.txt
# Last 20 lines
tail -n 20 file.txt
tail -20 file.txt
# Last 100 bytes
tail -c 100 file.txt
# Follow a file in real-time (perfect for logs!)
tail -f /var/log/syslog
# Press Ctrl+C to stop
# Follow and retry if file is recreated (better for logs that rotate)
tail -F /var/log/application.log
# Show all lines EXCEPT the first 5
tail -n +6 file.txt
# Show all lines EXCEPT the last 5
head -n -5 file.txt
# Lines 10-20 of a file
head -20 file.txt | tail -11
# Or with sed:
sed -n '10,20p' file.txt
# Follow multiple files simultaneously
tail -f /var/log/syslog /var/log/auth.logPractical use cases:
# Monitor a web server access log in real-time
tail -f /var/log/nginx/access.log
# Watch the last 50 lines of a growing log file
tail -n 50 -f application.log
# Show just the header of a CSV file
head -1 data.csv
# Remove the header from a CSV
tail -n +2 data.csv > data_without_header.csv
# See the first and last lines of a file
head -n 1 file.txt && tail -n 1 file.txt
# Extract lines 100-200 from a massive file
sed -n '100,200p' huge_file.txt
# Or:
head -200 huge_file.txt | tail -101wc — Count Lines, Words, Characters
The wc command (word count) provides statistics about files:
# Count lines, words, and characters
wc file.txt
# Output: 42 156 1024 file.txt
# (42 lines, 156 words, 1024 bytes)
# Count only lines
wc -l file.txt
# Count only words
wc -w file.txt
# Count only characters
wc -m file.txt
# Count only bytes
wc -c file.txt
# Count for multiple files (shows total)
wc -l *.txt
# Count lines in command output
ls | wc -l # How many files?
grep "error" logfile.txt | wc -l # How many errors?
ps aux | wc -l # How many processes?
# Find longest line
wc -L file.txtPractical examples:
# Count total lines of code in a project
find . -name "*.py" | xargs wc -l
# Count files in a directory
ls -1 | wc -l
# Count unique IP addresses in access log
awk '{print $1}' access.log | sort -u | wc -l
# Count occurrences of a word
grep -o "error" logfile.txt | wc -lCopying Files and Directories
cp — Copy
The cp command creates duplicates of files and directories:
# Copy a file (basic syntax)
cp source.txt destination.txt
# Copy a file to a directory (keeps original name)
cp file.txt /path/to/directory/
# Copy multiple files to a directory
cp file1.txt file2.txt file3.txt /path/to/directory/
# Copy with wildcards
cp *.jpg /path/to/photos/
cp report_*.pdf /path/to/archive/
# Copy a directory (must use -r for recursive)
cp -r source_dir/ destination_dir/
# Copy and preserve attributes (timestamps, permissions, ownership)
cp -p file.txt backup.txt
# Useful for backups where you want original timestamps
# Copy preserving everything (archive mode)
cp -a source_dir/ destination_dir/
# Equivalent to: cp -dpR
# -a preserves: permissions, ownership, timestamps, symlinks
# Interactive: ask before overwriting
cp -i file.txt existing_file.txt
# Verbose: show what's being copied
cp -v file1.txt file2.txt /backup/
# Output: 'file1.txt' -> '/backup/file1.txt'
# 'file2.txt' -> '/backup/file2.txt'
# Don't overwrite existing files
cp -n file.txt existing_file.txt
# Silently skips if destination exists
# Update: copy only if source is newer
cp -u source.txt destination.txt
# Only copies if source.txt is newer than destination.txt
# Force: always overwrite without prompting
cp -f file.txt destination.txt
# Create backup of existing destination file
cp -b file.txt existing.txt
# Creates existing.txt~ as backup
# Copy hard links as links (not as separate files)
cp -d link destination/
# Combine options for comprehensive copy
cp -av source/ backup/
# Archive mode + verbose⚠️ cp without -i silently overwrites!
By default, cp will overwrite the destination file without asking, potentially destroying data. To protect yourself:
# Add to your ~/.bashrc:
alias cp='cp -i'
# Now cp always asks before overwriting:
$ cp important.txt existing.txt
cp: overwrite 'existing.txt'? nMany distributions include this alias by default.
Practical examples:
# Backup a configuration file before editing
cp -p /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
# Copy entire directory structure preserving everything
cp -a /var/www/html /var/www/html.backup
# Copy only PDF files from multiple subdirectories
find . -name "*.pdf" -exec cp {} /backup/pdfs/ \;
# Copy files modified today
cp -u $(find . -mtime 0) /backup/today/
# Mirror a directory to another location
cp -auv /source/ /destination/Moving and Renaming
mv — Move and Rename
In Linux, moving and renaming are the same operation—changing the file's path:
# Rename a file (same directory)
mv oldname.txt newname.txt
# Move a file to a directory
mv file.txt /path/to/directory/
# Move multiple files to a directory
mv file1.txt file2.txt file3.txt /path/to/directory/
# Move with wildcards
mv *.log /var/log/archive/
# Move a directory (to another location)
mv old_dir/ /new/location/
# Rename a directory
mv old_dirname/ new_dirname/
# Interactive: ask before overwriting
mv -i file.txt existing_file.txt
# Verbose: show what's being moved
mv -v *.txt /archive/
# Output: 'file1.txt' -> '/archive/file1.txt'
# 'file2.txt' -> '/archive/file2.txt'
# Don't overwrite existing files
mv -n file.txt destination.txt
# Move only if source is newer (update mode)
mv -u source.txt destination.txt
# Force: overwrite read-only files
mv -f file.txt /destination/
# Backup existing destination file
mv -b file.txt existing.txt
# Creates existing.txt~ as backupℹ️ mv is actually renaming
In Linux, renaming and moving are the same operation at the filesystem level. Both change the file's directory entry. The difference:
- Same filesystem:
mvsimply updates the directory entry (instant, even for huge files) - Different filesystem:
mvcopies the file then deletes the original (slower)
This is why mv is so fast even for gigabyte files—it's not actually moving data, just updating metadata.
Practical examples:
# Organize downloads by file type
mv ~/Downloads/*.pdf ~/Documents/PDFs/
mv ~/Downloads/*.jpg ~/Pictures/
# Rename all .txt files to .md
for file in *.txt; do
mv "$file" "${file%.txt}.md"
done
# Move files older than 30 days to archive
find . -mtime +30 -exec mv {} /archive/ \;
# Rename with date prefix
mv report.txt "$(date +%Y%m%d)_report.txt"
# Swap two files (requires temp file)
mv file1.txt temp.tmp
mv file2.txt file1.txt
mv temp.tmp file2.txt
# Move files matching pattern with confirmation
mv -i log_*.txt /archive/Deleting Files and Directories
rm — Remove (Delete)
The rm command permanently deletes files and directories. There is no undo!
# Delete a file
rm file.txt
# Delete multiple files
rm file1.txt file2.txt file3.txt
# Delete with wildcard
rm *.tmp
# Interactive: ask before each deletion
rm -i file.txt
rm: remove regular file 'file.txt'? y
# Interactive for 3+ files (less annoying)
rm -I *.txt
rm: remove 5 arguments? y
# Force: don't ask, don't error on missing files
rm -f file.txt
rm -f nonexistent.txt # Doesn't complain
# Delete a directory and all its contents (recursive)
rm -r directory/
rm -rf directory/ # Force + recursive (most common for directories)
# Verbose: show what's being deleted
rm -v *.log
# Output: removed 'app.log'
# removed 'error.log'
# Delete empty directories only
rmdir empty_directory/
# Delete a directory tree of empty directories
rmdir -p a/b/c/ # Removes c/, then b/, then a/ (if all empty)🚫 rm is permanent and irreversible!
Unlike your GUI's trash/recycle bin, rm permanently deletes files at the filesystem level. There is no "undo" button. Important safety practices:
-
Always preview with ls first:
ls *.log # Check what matches rm *.log # Then delete -
Use -i for important deletions:
rm -i important_* -
Never run these commands:
rm -rf / # Deletes EVERYTHING (most systems have safeguards) rm -rf /* # Same disaster rm -rf $VAR/ # If $VAR is empty, becomes rm -rf / -
Create a trash function:
# Add to ~/.bashrc trash() { mkdir -p ~/.trash mv "$@" ~/.trash/ } # Now use trash instead of rm trash old_file.txt -
Install trash-cli for a safer alternative:
sudo apt install trash-cli trash file.txt # Move to trash trash-list # View trash contents trash-restore # Restore from trash trash-empty # Empty trash
Practical examples:
# Remove all .tmp files older than 7 days
find . -name "*.tmp" -mtime +7 -delete
# Remove all but the most recent 10 backup files
ls -t backup_*.tar.gz | tail -n +11 | xargs rm -f
# Remove all empty files
find . -type f -empty -delete
# Remove files interactively from a list
find . -name "*.log" -size +100M -ok rm {} \;
# Clean up compiled Python files
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -delete
# Remove backup files
rm *~ *.bak *.old
# Safely remove a directory with confirmation
rm -rI large_directory/Creating Directories
mkdir — Make Directory
Create new directories efficiently:
# Create a single directory
mkdir new_directory
# Create nested directories (parent directories too)
mkdir -p projects/webapp/src/components
# Without -p, would fail if projects/ doesn't exist
# Create multiple directories at once
mkdir dir1 dir2 dir3
# Create with specific permissions
mkdir -m 755 public_dir
mkdir -m 700 private_dir
# Same as: mkdir dir && chmod 755 dir
# Verbose: show what's being created
mkdir -v new_dir
# Output: mkdir: created directory 'new_dir'
# Create a project structure in one command
mkdir -p myproject/{src,tests,docs,config}
# Creates:
# myproject/
# ├── src/
# ├── tests/
# ├── docs/
# └── config/
# More complex structure
mkdir -p myapp/{src/{components,utils,hooks},public/{images,css,js},tests/{unit,integration}}
# Creates:
# myapp/
# ├── src/
# │ ├── components/
# │ ├── utils/
# │ └── hooks/
# ├── public/
# │ ├── images/
# │ ├── css/
# │ └── js/
# └── tests/
# ├── unit/
# └── integration/
# Create date-stamped backup directories
mkdir -p backups/$(date +%Y/%m/%d)
# Creates: backups/2024/01/15/
# Create numbered directories
mkdir -p project/{phase1,phase2,phase3}/{planning,execution,review}Practical examples:
# Set up a standard web project structure
mkdir -p website/{css,js,images,fonts,pages}
# Create log directories with proper permissions
sudo mkdir -p /var/log/myapp
sudo chmod 755 /var/log/myapp
# Organize photos by date
mkdir -p ~/Pictures/$(date +%Y)/$(date +%m)
# Create a complete Python project structure
mkdir -p python_project/{src/package,tests,docs,scripts}
touch python_project/src/package/__init__.py
touch python_project/README.md
touch python_project/requirements.txtFile Permissions
File permissions control who can read, write, and execute files. This is fundamental Linux security.
Understanding Permission Notation
Every file has permissions for three categories of users:
-rwxr-xr-- 1 alice staff 4096 Jan 15 10:30 script.sh
│││ │││ │││
│││ │││ └┴┴── Others: read (r), no write (-), no execute (-)
│││ └┴┴───── Group: read (r), no write (-), execute (x)
└┴┴──────── Owner: read (r), write (w), execute (x)
Three categories of users:
- Owner (u): The user who owns the file (usually the creator)
- Group (g): The group assigned to the file
- Others (o): Everyone else on the system
Three types of permissions:
- Read (r):
- Files: View contents (
cat,less, copy) - Directories: List contents (
ls)
- Files: View contents (
- Write (w):
- Files: Modify contents, delete, rename
- Directories: Create/delete files within, rename directory
- Execute (x):
- Files: Run as a program/script
- Directories: Enter (
cd) into the directory
ℹ️ Directory permissions are tricky
Directory permissions work differently than file permissions:
- r (read): List files in directory (
ls) - w (write): Create/delete files IN the directory
- x (execute): Enter the directory (
cd) and access files
You need execute (x) permission to cd into a directory! You can have read permission but still not be able to enter it without execute.
Common directory permissions:
755(rwxr-xr-x): Standard directory - everyone can read and enter700(rwx------): Private directory - only owner can access750(rwxr-x---): Shared with group - owner full access, group can read/enter
Numeric (Octal) Permissions
Each permission has a numeric value that gets summed:
| Permission | Symbol | Value |
|---|---|---|
| Read | r | 4 |
| Write | w | 2 |
| Execute | x | 1 |
| None | - | 0 |
Combine values for each category (owner, group, others):
| Numeric | Binary | Permission | Meaning |
|---|---|---|---|
| 7 | 111 | rwx | Read + Write + Execute (4+2+1) |
| 6 | 110 | rw- | Read + Write (4+2) |
| 5 | 101 | r-x | Read + Execute (4+1) |
| 4 | 100 | r-- | Read only (4) |
| 3 | 011 | -wx | Write + Execute (2+1) |
| 2 | 010 | -w- | Write only (2) |
| 1 | 001 | --x | Execute only (1) |
| 0 | 000 | --- | No permissions (0) |
Common permission patterns:
| Numeric | Symbolic | Use Case |
|---|---|---|
| 755 | rwxr-xr-x | Executable scripts, directories |
| 644 | rw-r--r-- | Regular files (documents, code) |
| 700 | rwx------ | Private directories (~/. ssh/) |
| 600 | rw------- | Private files (SSH keys, passwords) |
| 666 | rw-rw-rw- | World-writable files (usually bad!) |
| 777 | rwxrwxrwx | World-writable/executable (dangerous!) |
chmod — Change Permissions
Modify file permissions using numeric or symbolic notation:
# Numeric method (octal)
chmod 755 script.sh # rwxr-xr-x (owner: full, others: read+execute)
chmod 644 document.txt # rw-r--r-- (owner: read+write, others: read-only)
chmod 700 private/ # rwx------ (owner-only access)
chmod 600 secrets.txt # rw------- (owner can read/write, no one else)
# Symbolic method (more flexible)
chmod u+x script.sh # Add execute for owner (user)
chmod g+w file.txt # Add write for group
chmod o-r file.txt # Remove read for others
chmod a+r file.txt # Add read for all (a = all = ugo)
# Multiple changes at once
chmod u+rwx,g+rx,o+r file.txt # Owner: full, group: read+execute, others: read
# Set exact permissions (= instead of +/-)
chmod u=rwx,g=rx,o=r file.txt # Same as 754
# Copy permissions from one category to another
chmod g=u file.txt # Group gets same permissions as owner
# Recursive (apply to directory and all contents)
chmod -R 755 project/
# Recursive for files and directories differently
find project/ -type f -exec chmod 644 {} \; # Files: 644
find project/ -type d -exec chmod 755 {} \; # Directories: 755
# Add execute permission to all .sh files
chmod +x *.sh
# Remove write permission for group and others
chmod go-w *
# Make file readable by everyone, writable only by owner
chmod 644 document.txt
# Or symbolically:
chmod u=rw,go=r document.txtPractical permission scenarios:
# Web server files
chmod 644 *.html *.css *.js # Web assets
chmod 755 cgi-bin/*.cgi # CGI scripts
chmod 600 config/database.php # Database credentials
# SSH keys (must be restricted!)
chmod 700 ~/.ssh # SSH directory
chmod 600 ~/.ssh/id_rsa # Private key
chmod 644 ~/.ssh/id_rsa.pub # Public key
chmod 644 ~/.ssh/authorized_keys # Authorized keys
chmod 644 ~/.ssh/known_hosts # Known hosts
# Shell scripts
chmod +x script.sh # Make executable
chmod 755 /usr/local/bin/myscript # System-wide script
# Log files
chmod 640 /var/log/myapp.log # Owner+group read, owner write
# Shared project directory
chmod 775 shared_project/ # Owner+group can do everythingchown — Change Ownership
Change file owner and/or group (requires root):
# Change owner
sudo chown alice file.txt
# Change owner and group
sudo chown alice:staff file.txt
# Change group only
sudo chown :staff file.txt
# Or use chgrp:
sudo chgrp staff file.txt
# Recursive (directory and all contents)
sudo chown -R alice:staff project/
# Follow symbolic links (change link targets, not links)
sudo chown -L alice file.txt
# Change from one user to another (security cleanup)
sudo chown -R newuser:newuser --from=olduser:olduser /var/www/
# Verbose: show what's being changed
sudo chown -v alice file.txt
# Output: changed ownership of 'file.txt' from root to alice
# Reference file (copy ownership from another file)
sudo chown --reference=template.txt newfile.txt⚠️ Only root can change ownership
Regular users cannot change file ownership (except group, if they belong to the target group). This prevents users from avoiding disk quotas by giving files away. Always use sudo with chown.
Exception: Users can change group to any group they belong to:
groups # See your groups
chgrp mygroup file.txt # Change to a group you're in (no sudo needed)Practical ownership scenarios:
# Fix ownership after sudo operations
sudo chown -R $USER:$USER ~/myproject
# Set up web server files
sudo chown -R www-data:www-data /var/www/html
# Give ownership to a specific user
sudo chown -R bob:developers /opt/project
# Change ownership of all files owned by old user
sudo find /home -user olduser -exec chown newuser {} \;Finding Files
find — Search for Files
The find command is one of the most powerful tools in Linux, searching the filesystem based on virtually any criteria:
# Basic syntax: find [path] [tests] [actions]
# Find by name (case-sensitive)
find /home -name "report.txt"
find . -name "*.py"
# Case-insensitive name search
find . -iname "readme*"
# Find by type
find . -type f # Regular files only
find . -type d # Directories only
find . -type l # Symbolic links only
find . -type b # Block devices
find . -type c # Character devices
# Find by size
find . -size +100M # Files larger than 100MB
find . -size -1k # Files smaller than 1KB
find . -size 50M # Files exactly 50MB
find /var/log -size +1G # Huge log files
# Find by modification time
find . -mtime -7 # Modified in the last 7 days
find . -mtime +30 # Modified more than 30 days ago
find . -mtime 0 # Modified today
find . -mmin -60 # Modified in the last 60 minutes
# Find by access time
find . -atime -1 # Accessed in the last 24 hours
# Find by change time (metadata change)
find . -ctime -7 # Changed in the last 7 days
# Find by permissions
find . -perm 644 # Exact match
find . -perm -u+x # At least owner execute
find . -perm /u+w # Owner has write (any)
# Find by owner
find . -user alice
find . -group staff
find /home -user bob -group developers
# Find empty files/directories
find . -empty
find /tmp -type f -empty # Empty files only
# Find by depth
find . -maxdepth 2 -name "*.txt" # Only 2 levels deep
find . -mindepth 2 -name "*.log" # At least 2 levels deep
# Combine criteria (AND by default)
find . -name "*.log" -size +10M -mtime +30
# Finds .log files larger than 10MB, modified more than 30 days ago
# OR criteria
find . -name "*.jpg" -o -name "*.png"
find . \( -name "*.txt" -o -name "*.md" \) -size +1M
# NOT criteria
find . ! -name "*.tmp"
find . -not -name "*.log"
# Exclude directories
find . -name "*.py" -not -path "*/venv/*"
find . -name "*.js" -not -path "*/node_modules/*"find with Actions
The real power of find comes from executing actions on matched files:
# Delete found files (BE CAREFUL!)
find /tmp -name "*.tmp" -mtime +7 -delete
# Execute a command on each found file
find . -name "*.txt" -exec cat {} \;
find . -name "*.sh" -exec chmod +x {} \;
# {} is replaced with the found filename
# \; ends the -exec command
# Execute with confirmation (interactive)
find . -name "*.log" -ok rm {} \;
# Asks: < rm ... ./file.log > ? y
# Execute multiple commands
find . -name "*.txt" -exec echo "Processing: {}" \; -exec wc -l {} \;
# Use + instead of \; to pass multiple files at once (faster)
find . -name "*.txt" -exec grep "pattern" {} +
# Equivalent to: grep "pattern" file1.txt file2.txt file3.txt
# Print with details (like ls -l)
find . -name "*.py" -ls
# Print full path
find . -name "*.txt" -print
# Or with null separator (safe for filenames with spaces)
find . -name "*.txt" -print0
# Use xargs for better performance (handles spaces correctly)
find . -name "*.txt" -print0 | xargs -0 grep "error"
find . -type f -name "*.log" -print0 | xargs -0 gzipPractical find examples:
# Find and count all Python files
find . -name "*.py" | wc -l
# Find large log files and compress them
find /var/log -name "*.log" -size +100M -exec gzip {} \;
# Find files not accessed in 90 days and archive
find /data -type f -atime +90 -exec mv {} /archive/ \;
# Find broken symbolic links
find . -type l ! -exec test -e {} \; -print
# Find duplicate files by size (first pass)
find . -type f -exec ls -lS {} + | awk '{print $5, $NF}' | sort -n
# Find world-writable files (security audit)
find / -perm -002 -type f -print 2>/dev/null
# Find SetUID/SetGID programs (security audit)
find / -perm /6000 -type f -ls 2>/dev/null
# Find all shell scripts
find . -type f -name "*.sh" -o -name "*.bash"
# Find files owned by specific user
find /home -user olduser -exec chown newuser:newuser {} +
# Find and remove empty directories
find . -type d -empty -delete
# Find files modified between two dates
find . -type f -newermt "2024-01-01" ! -newermt "2024-02-01"
# Find files by inode number (useful after accidental deletion)
find . -inum 123456locate — Fast File Search
locate searches a pre-built database, making it much faster than find but potentially less current:
# Update the database first (run daily via cron usually)
sudo updatedb
# Search for a file
locate report.txt
# Case-insensitive search
locate -i readme
# Count matches
locate -c "*.py"
# Show only existing files (database might be stale)
locate -e report.txt
# Limit number of results
locate -l 10 "*.log"
# Show database statistics
locate -S
# Use basic regex
locate -r "report.*\.pdf$"
# Match only basename (not full path)
locate -b "\report.txt"When to use find vs locate:
- find: Real-time, accurate, slow, complex criteria, actions on files
- locate: Fast, possibly outdated, simple name searching, read-only
# Fast search for filenames
locate nginx.conf
# Comprehensive real-time search with criteria
find / -name "nginx.conf" -type f -newer /tmp/marker 2>/dev/nullwhich, whereis, type — Find Commands
Locate executable commands in your PATH:
# Find where a command's binary is
which python
# /usr/bin/python
which ls
# /usr/bin/ls
which -a python
# Show ALL occurrences in PATH
# /usr/bin/python
# /usr/local/bin/python
# Find binary, source, and manual pages
whereis ls
# ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz
whereis python
# python: /usr/bin/python /usr/lib/python3.10 /usr/share/man/man1/python.1.gz
# Show how the shell interprets a command
type cd
# cd is a shell builtin
type ls
# ls is aliased to 'ls --color=auto'
type python
# python is /usr/bin/python
type -a python
# Show all definitions
# python is /usr/bin/python
# python is /usr/local/bin/python
type -t ls
# Show type only: alias, keyword, function, builtin, file
# aliasPractical use:
# Verify which Python you're using
which python && python --version
# Find all installed versions of a program
whereis -b python
# Check if a command is available
if command -v docker &> /dev/null; then
echo "Docker is installed"
fi
# Find what conflicts with your script name
type -a mycommandDisk Usage
df — Disk Free Space
Show available disk space on mounted filesystems:
# Show disk space for all filesystems
df
# Human-readable sizes (KB, MB, GB)
df -h
# Show specific filesystem
df -h /home
# Show filesystem type
df -hT
# Show only local filesystems (exclude network mounts)
df -hl
# Show inodes instead of blocks
df -i
# Show only specific filesystem type
df -t ext4
# Exclude specific filesystem type
df -x tmpfs
# Total summary
df -h --totalExample output:
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 35G 13G 74% /
/dev/sda2 100G 78G 17G 83% /home
tmpfs 7.8G 12M 7.8G 1% /tmp
Practical examples:
# Check root filesystem usage
df -h /
# Alert if filesystem is >90% full
df -h | awk '$5+0 > 90 {print "WARNING: " $0}'
# Monitor specific mount point
df -h /var/log
# Check all ext4 filesystems
df -hT | grep ext4du — Directory Usage
Show disk usage of files and directories:
# Size of current directory (summary)
du -sh .
# Size of each item in current directory
du -sh *
# Size with depth limit (show top-level only)
du -h --max-depth=1
# Size with depth=2
du -h --max-depth=2
# Find the 10 largest directories
du -h --max-depth=1 | sort -hr | head -10
# Size of a specific directory
du -sh /var/log
# Exclude certain patterns
du -sh --exclude="*.log" /var
# Show grand total at the end
du -ch /var/log | tail -1
# All files, not just directories
du -ah
# Apparent size (actual file size, not disk blocks)
du -sh --apparent-size file.txt
# Show timestamps
du -h --time /var/log/*Practical examples:
# Find what's eating disk space
du -h /home | sort -hr | head -20
# Disk usage by file type
find . -name "*.log" -exec du -ch {} + | tail -1
find . -name "*.mp4" -exec du -ch {} + | tail -1
# Space used by Docker
sudo du -sh /var/lib/docker
# Find large directories in /var
sudo du -h /var | grep "^[0-9\.]*G"
# Compare sizes before and after cleanup
du -sh project/ > before.txt
# ... cleanup operations ...
du -sh project/ > after.txt
diff before.txt after.txt
# Disk usage excluding hidden files
du -sh --exclude=".*" ~Exercises
Task 1: Create the following directory structure in one command:
playground/
├── src/
│ ├── css/
│ └── js/
├── images/
├── docs/
└── tests/
Show Solution
mkdir -p playground/{src/{css,js},images,docs,tests}
# Verify structure
tree playground
# Or:
ls -R playgroundTask 2: Inside the playground directory, create 5 numbered text files (file_01.txt through file_05.txt), each containing its own filename as content.
💡 Hint
Show Solution
cd playground
# Method 1: Using a for loop
for i in {01..05}; do
echo "file_$i.txt" > "file_$i.txt"
done
# Method 2: Using tee
for i in {01..05}; do
echo "file_$i.txt" | tee "file_$i.txt" > /dev/null
done
# Verify
ls -la file_*.txt
cat file_03.txt
# Output: file_03.txtTask 3: Copy all .txt files to the docs/ directory, then move file_05.txt from the current directory to tests/ and rename it to test_data.txt.
Show Solution
# Copy all .txt files to docs/
cp *.txt docs/
# Move and rename file_05.txt
mv file_05.txt tests/test_data.txt
# Verify
ls file_*.txt # Should show file_01 through file_04 (05 is gone)
ls docs/ # Should show file_01 through file_05
ls tests/ # Should show test_data.txt
cat tests/test_data.txt
# Output: file_05.txtTask 4: Set the following permissions:
docs/directory: owner can do everything, group can read and enter, others have no access (750)- All
.txtfiles indocs/: owner can read/write, group can read, others have no access (640)
Show Solution
# Set directory permissions
chmod 750 docs/
# 7 (rwx) = 4+2+1 owner: read+write+execute
# 5 (r-x) = 4+1 group: read+execute
# 0 (---) = 0 others: nothing
# Set file permissions
chmod 640 docs/*.txt
# 6 (rw-) = 4+2 owner: read+write
# 4 (r--) = 4 group: read
# 0 (---) = 0 others: nothing
# Verify
ls -la docs/
# Output should show:
# drwxr-x--- for the directory
# -rw-r----- for the .txt files
# Alternative symbolic method:
chmod u=rwx,g=rx,o= docs/
chmod u=rw,g=r,o= docs/*.txtTask 5: Find all empty files in the playground directory and delete them.
Show Solution
# First, let's create an empty file to test
touch playground/empty_file.txt
# Find empty files (preview first!)
find playground/ -type f -empty
# Delete them
find playground/ -type f -empty -delete
# Or with confirmation
find playground/ -type f -empty -ok rm {} \;Q1: Find all .conf files in /etc that were modified in the last 7 days.
Show Solution
find /etc -name "*.conf" -mtime -7 2>/dev/null
# -name "*.conf" : filename ends with .conf
# -mtime -7 : modified within last 7 days
# 2>/dev/null : suppress "Permission denied" errors
# With details
find /etc -name "*.conf" -mtime -7 -ls 2>/dev/null
# Count how many
find /etc -name "*.conf" -mtime -7 2>/dev/null | wc -lQ2: Find all files larger than 50MB in your home directory, showing their sizes.
Show Solution
# Basic find
find ~ -type f -size +50M 2>/dev/null
# With details (sizes, etc)
find ~ -type f -size +50M -ls 2>/dev/null
# Human-readable with ls
find ~ -type f -size +50M -exec ls -lh {} \; 2>/dev/null
# Sorted by size (largest first)
find ~ -type f -size +50M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hrQ3: Find all empty directories under /tmp and list them.
Show Solution
# Find empty directories
find /tmp -type d -empty 2>/dev/null
# With full details
find /tmp -type d -empty -ls 2>/dev/null
# Count them
find /tmp -type d -empty 2>/dev/null | wc -l
# Delete them (if safe to do so)
find /tmp -type d -empty -delete 2>/dev/nullQ4: Find all .log files in /var/log that haven't been modified in 30 days and show their sizes.
Show Solution
# Find old log files
find /var/log -name "*.log" -mtime +30 2>/dev/null
# With details
find /var/log -name "*.log" -mtime +30 -ls 2>/dev/null
# Human-readable sizes
find /var/log -name "*.log" -mtime +30 -exec ls -lh {} \; 2>/dev/null
# Total size of old logs
find /var/log -name "*.log" -mtime +30 -exec du -ch {} + 2>/dev/null | tail -1Q5: Find all files owned by your user in /tmp and delete them (safely with confirmation).
Show Solution
# Preview first
find /tmp -user $USER -type f 2>/dev/null
# Count them
find /tmp -user $USER -type f 2>/dev/null | wc -l
# Delete with confirmation for each file
find /tmp -user $USER -type f -ok rm {} \; 2>/dev/null
# Or delete all at once (careful!)
find /tmp -user $USER -type f -delete 2>/dev/nullScenario 1: You have a shared project directory /project. Users in the developers group should be able to create, modify, and delete files. Others should only be able to read files. Set this up.
Show Solution
# Set directory permissions
sudo chown -R :developers /project
sudo chmod 775 /project
# Owner: rwx, Group: rwx, Others: r-x
# For existing files in the directory
sudo find /project -type f -exec chmod 664 {} \;
# Owner: rw-, Group: rw-, Others: r--
sudo find /project -type d -exec chmod 775 {} \;
# Directories need execute for entry
# Set default permissions for new files (using setgid)
sudo chmod g+s /project
# Now new files inherit the group
# Verify
ls -la /projectScenario 2: You need to secure your SSH directory. Set correct permissions for ~/.ssh/ directory, private key, and public key.
Show Solution
# SSH directory: owner-only access
chmod 700 ~/.ssh
# Private key: owner read/write only (CRITICAL!)
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_ed25519 # If using ed25519
# Public key: owner read/write, others read
chmod 644 ~/.ssh/id_rsa.pub
# Authorized keys: owner read/write
chmod 600 ~/.ssh/authorized_keys
# Known hosts: owner read/write
chmod 644 ~/.ssh/known_hosts
# Config file: owner read/write
chmod 600 ~/.ssh/config
# Verify all at once
ls -la ~/.ssh
# Expected output:
# drwx------ .ssh/
# -rw------- id_rsa
# -rw-r--r-- id_rsa.pub
# -rw------- authorized_keys
# -rw-r--r-- known_hosts
# -rw------- configScenario 3: Make a shell script executable by everyone, but only you should be able to modify it.
Show Solution
# Create the script
echo '#!/bin/bash' > myscript.sh
echo 'echo "Hello from script!"' >> myscript.sh
# Set permissions: owner read/write/execute, others read/execute
chmod 755 myscript.sh
# Or symbolically:
chmod u=rwx,go=rx myscript.sh
# Verify
ls -l myscript.sh
# -rwxr-xr-x myscript.sh
# Test
./myscript.sh
# Output: Hello from script!
# Explanation of 755:
# 7 (rwx) = owner: read+write+execute
# 5 (r-x) = group: read+execute (no write!)
# 5 (r-x) = others: read+execute (no write!)Summary
File and directory operations are the foundation of Linux system administration and scripting:
- Creating:
touch(empty files),mkdir -p(directories),echo/cat(content) - Viewing:
cat(small files),less(large files),head/tail(parts),wc(counts) - Copying:
cp(files),cp -r(directories), preserve with-por-a - Moving/Renaming:
mv(same operation), instant on same filesystem - Deleting:
rm(permanent!),rm -r(directories),rmdir(empty directories) - Permissions:
chmod(change permissions),chown(change owner), understand rwx for files vs directories - Finding:
find(powerful search),locate(fast database),which/type(find commands) - Disk Usage:
df(filesystem space),du(directory sizes)
Key safety practices:
- Always preview with
lsbefore using wildcards withrm - Use
-ifor interactive confirmation on important operations - Test
findcommands with-printbefore using-deleteor-exec - Backup before making bulk changes
- Understand permission implications before
chmod -R
Master these commands and you'll be comfortable managing files on any Linux system, from personal laptops to production servers.
In the next tutorial, you'll learn about variables and the environment—how to store values, use shell variables, and understand environment configuration.