Working with Files and Directories

Summary: in this tutorial, you will learn master creating, viewing, copying, moving, and deleting files and directories using essential bash commands.

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.txt
 

Practical 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.sh
 

Viewing 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.txt
 

When NOT to use cat:

  • Large files: Use less instead—cat dumps everything at once
  • Binary files: Use hexdump, xxd, or file instead
  • Just counting lines: Use wc -l instead of cat 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.txt
 

less — 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 previous
 

Navigation in less:

KeyActionUse Case
Space / fPage forwardQuick browsing
bPage backwardReview previous content
d / uHalf-page down / upFine-grained control
g / GGo to beginning / endJump to start/end
/patternSearch forwardFind specific text
?patternSearch backwardFind earlier occurrences
n / NNext / previous matchNavigate search results
qQuitExit viewer
FFollow mode (like tail -f)Monitor log files
vOpen in editorEdit the file
&patternShow only matching linesFilter view
hHelpSee all commands
m[a-z]Mark positionBookmark location
'[a-z]Return to markJump 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.log
 

Practical 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 -101
 

wc — 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.txt
 

Practical 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 -l
 

Copying 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'? n
 

Many 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: mv simply updates the directory entry (instant, even for huge files)
  • Different filesystem: mv copies 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:

  1. Always preview with ls first:

    ls *.log        # Check what matches
    rm *.log        # Then delete
  2. Use -i for important deletions:

    rm -i important_*
  3. 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 /
  4. Create a trash function:

    # Add to ~/.bashrc
    trash() {
        mkdir -p ~/.trash
        mv "$@" ~/.trash/
    }
     
    # Now use trash instead of rm
    trash old_file.txt
  5. 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

    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.txt
 
Was this page helpful?
SR

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 →