File Permissions and Finding Files
Summary: in this tutorial, you will learn understand linux file permissions, ownership, chmod, chown, and master finding files with find, locate, and analyzing disk usage.
File Permissions and Finding Files
Every file and directory on a Linux system has an owner, a group, and a set of permissions that control who can read, write, or execute it. Understanding permissions is essential for security, multi-user systems, and server administration. This tutorial also covers finding files across the filesystem and analyzing disk usage.
File 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.txt
Practical 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 everything
chown — 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 gzip
Practical 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 123456
locate — 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/null
which, 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
# alias
Practical 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 mycommand
Disk 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 --total
Example 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 ext4
du — 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 playground
Task 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.txt
Task 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.txt
Task 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/*.txt
Task 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 -l
Q2: 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 -hr
Q3: 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/null
Q4: 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 -1
Q5: 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/null
Scenario 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 /project
Scenario 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------- config
Scenario 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.
Written by the ShellRAG Team
The ShellRAG editorial team writes practical, beginner-friendly Bash Shell tutorials with tested code examples and real-world use cases. Every article is technically reviewed for accuracy and updated regularly.
Learn more about us →