How to Recursively Compare File Permissions and Ownership Between Two Directories in Linux


2 views

When managing Linux systems or deploying applications, verifying consistent file permissions and ownership between directories is crucial. This becomes especially important when:

  • Migrating data between servers
  • Synchronizing development and production environments
  • Troubleshooting permission-related issues
  • Auditing security configurations

The ls command with appropriate flags provides the foundation:

ls -laR /path/to/directory | grep -v "^total"

For more targeted output:

ls -ln /path | awk '{print $1,$3,$4,$9}'

Here's a Bash script that recursively compares permissions and ownership:

#!/bin/bash

compare_dirs() {
    dir1=$1
    dir2=$2
    
    # Create temporary files
    tmp1=$(mktemp)
    tmp2=$(mktemp)
    
    # Collect permission data
    find "$dir1" -printf "%M %u %g %p\n" | sort > "$tmp1"
    find "$dir2" -printf "%M %u %g %p\n" | sort > "$tmp2"
    
    # Compare and highlight differences
    diff -y --suppress-common-lines "$tmp1" "$tmp2"
    
    # Cleanup
    rm "$tmp1" "$tmp2"
}

# Usage example
compare_dirs "/path/to/first/dir" "/path/to/second/dir"

For enterprise use cases, consider:

1. Using rsync with dry-run:

rsync -n -a -i --dry-run /source/ /destination/

2. Metamorphosis (meta-data comparison tool):

metamorphosis compare --permissions --ownership dir1 dir2

For complex directory structures, generate a visual report:

#!/bin/bash

generate_report() {
    dir=$1
    report_file=$2
    
    echo "Permission Report for: $dir" > "$report_file"
    echo "Generated: $(date)" >> "$report_file"
    echo "---------------------------------" >> "$report_file"
    
    find "$dir" -printf "%M\t%u\t%g\t%p\n" | \
    awk 'BEGIN {print "Perms\tOwner\tGroup\tPath"} 
    {print $0}' >> "$report_file"
}

generate_report "/path/to/dir" "permission_report.txt"

Case 1: Web server directory synchronization

diff <(find /var/www/html -printf "%M %u %g %p\n") \
     <(find /backup/www -printf "%M %u %g %p\n")

Case 2: Configuration management verification

#!/bin/bash
# Verify /etc permissions match golden image
GOLDEN_DIR="/opt/golden_configs"
LIVE_DIR="/etc"

for file in $(find $GOLDEN_DIR -type f); do
    rel_path=${file#$GOLDEN_DIR}
    live_file="$LIVE_DIR$rel_path"
    
    if [ -e "$live_file" ]; then
        golden_perms=$(stat -c "%a" "$file")
        live_perms=$(stat -c "%a" "$live_file")
        
        if [ "$golden_perms" != "$live_perms" ]; then
            echo "Mismatch: $rel_path (Golden: $golden_perms, Live: $live_perms)"
        fi
    fi
done

When managing servers or deploying applications, you'll often need to verify that directory structures maintain consistent permissions and ownership. While diff excels at content comparison, it doesn't handle metadata differences. Here's how to tackle this common sysadmin challenge.

The most flexible approach combines find with stat to generate comparable output:


#!/bin/bash
# Compare permissions between dir1 and dir2
compare_perms() {
    local dir1=$1
    local dir2=$2
    
    # Generate permission reports
    find "$dir1" -printf "%M %u %g %p\n" | sort > /tmp/dir1_perms.txt
    find "$dir2" -printf "%M %u %g %p\n" | sort > /tmp/dir2_perms.txt
    
    # Diff the outputs
    diff -y --suppress-common-lines /tmp/dir1_perms.txt /tmp/dir2_perms.txt
}

compare_perms "/path/to/first/dir" "/path/to/second/dir"

For a dry-run comparison without actual transfer:


rsync -n -av --dry-run --info=NAME,STATS,FLIST \
    --itemize-changes "/source/" "/destination/" | grep -E "^[.](...){3}"

This outputs permission changes (.f.....), owner changes (.....o.), and group changes (......g).

For more sophisticated needs, consider this Python script:


import os
import sys
from collections import defaultdict

def get_permissions(root):
    permissions = defaultdict(dict)
    for dirpath, dirnames, filenames in os.walk(root):
        for name in dirnames + filenames:
            path = os.path.join(dirpath, name)
            stat = os.stat(path)
            permissions[path] = {
                'mode': stat.st_mode,
                'uid': stat.st_uid,
                'gid': stat.st_gid
            }
    return permissions

def compare_dirs(dir1, dir2):
    perms1 = get_permissions(dir1)
    perms2 = get_permissions(dir2)
    
    all_paths = set(perms1.keys()).union(set(perms2.keys()))
    
    for path in sorted(all_paths):
        data1 = perms1.get(path)
        data2 = perms2.get(path)
        
        if not data1:
            print(f"Only in {dir2}: {path}")
            continue
        if not data2:
            print(f"Only in {dir1}: {path}")
            continue
            
        if data1['mode'] != data2['mode']:
            print(f"Permission mismatch: {path}")
            print(f"  {dir1}: {oct(data1['mode'])[-4:]}")
            print(f"  {dir2}: {oct(data2['mode'])[-4:]}")
            
        if data1['uid'] != data2['uid']:
            print(f"Owner mismatch: {path}")
            
        if data1['gid'] != data2['gid']:
            print(f"Group mismatch: {path}")

if __name__ == "__main__":
    compare_dirs(sys.argv[1], sys.argv[2])

For enterprise environments, consider these alternatives:

  • tree: tree -ugp /path shows permissions in tree format
  • git: Track permission changes in version control with git config core.filemode true
  • auditd: For continuous monitoring of permission changes

Add -L to find commands to follow symlinks, or use this modified stat approach:


find -L /path -exec stat -c "%a %U %G %n" {} +