While Linux systems provide the convenient getfacl
and setfacl
commands for ACL management, macOS takes a different approach by integrating ACL handling directly into chmod
. This creates a challenge when you need to copy ACLs from one file to another.
The ls -le
command displays ACLs in a verbose format that needs processing before it can be fed to chmod -E
. Here's what the raw output looks like:
$ ls -led testfile
-rw-r--r--+ 1 user staff 0 Jan 1 12:34 testfile
0: user:johndoe allow read
1: group:admin allow write
Here's a more robust version of the shell pipeline that handles edge cases better:
ls -led source_file | awk 'NR > 1 {sub(/^[[:space:]]*[0-9]+:[[:space:]]*/, ""); print}' | chmod -E target_file
This version:
- Uses awk instead of sed for more reliable pattern matching
- Properly handles spaces in usernames/groupnames
- Skips the first line (file metadata) cleanly
For those who prefer a Python solution using only built-in modules:
import subprocess
def copy_acls(source, target):
# Get ACLs from source file
ls_process = subprocess.Popen(['ls', '-led', source], stdout=subprocess.PIPE)
awk_process = subprocess.Popen(['awk', 'NR > 1 {sub(/^[[:space:]]*[0-9]+:[[:space:]]*/, ""); print}'],
stdin=ls_process.stdout, stdout=subprocess.PIPE)
ls_process.stdout.close()
# Apply ACLs to target file
chmod_process = subprocess.Popen(['chmod', '-E', target], stdin=awk_process.stdout)
awk_process.stdout.close()
chmod_process.wait()
# Usage example
copy_acls('source.txt', 'destination.txt')
When dealing with inherited ACLs or complex permission sets, consider these additional steps:
# For directories with default ACLs
ls -led source_dir | awk 'NR > 1 {gsub(/^[[:space:]]*[0-9]+:[[:space:]]*|default:[[:space:]]*/, ""); print}' | chmod -E target_dir
# To preserve ACL inheritance flags
ls -le source_dir | grep -E '^[[:space:]]*[0-9]+:' | sed -E 's/^[[:space:]]*[0-9]+:[[:space:]]*//; s/inherited//' | chmod -E target_dir
For a more direct (but less portable) solution, you can work with the raw extended attributes:
# Copy all ACL-related xattrs
xattr -px com.apple.acl.text source_file | xattr -wx com.apple.acl.text target_file
# For directories (including default ACLs)
xattr -px com.apple.acl.text source_dir | xattr -wx com.apple.acl.text target_dir
Unlike traditional Unix systems that use getfacl
/setfacl
, macOS implements Access Control Lists through extended chmod
functionality. The chmod -E
command accepts ACL specifications in a specific format.
Here's a robust method to copy ACLs between files using built-in macOS tools:
# Extract ACLs from source file
SOURCE_ACLS=$(ls -led source_file | tail -n +2 | sed -E 's/^[[:space:]]*[0-9]+:[[:space:]]*//')
# Apply to destination file
echo "$SOURCE_ACLS" | chmod -E destination_file
For Python implementations without third-party dependencies:
import subprocess
def copy_acls(source, dest):
# Get ACLs from source
proc = subprocess.Popen(['ls', '-led', source],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
raise Exception(f"Error reading ACLs: {err.decode()}")
# Process output
acls = []
for line in out.decode().split('\n')[1:]:
if not line.strip():
continue
# Extract the ACL part (after the numeric index)
acl = line.split(':', 1)[1].strip()
acls.append(acl)
# Apply ACLs to destination
acl_input = '\n'.join(acls)
proc = subprocess.Popen(['chmod', '-E', dest],
stdin=subprocess.PIPE)
proc.communicate(input=acl_input.encode())
if proc.returncode != 0:
raise Exception("Failed to apply ACLs")
Some important considerations when working with ACLs on macOS:
# Preserve inheritance flags
ls -le source_file | grep 'inherited' | while read -r line; do
echo "${line#*: }" | chmod -E destination_file
done
# Handle multiple ACEs (Access Control Entries)
for entry in $(ls -led source_file | tail -n +2 | awk -F': ' '{print $2}'); do
echo "$entry" | chmod -E destination_file
done
For batch operations, consider these variations:
# Copy ACLs to multiple files
SOURCE_ACLS=$(ls -led template_file | tail -n +2 | sed -E 's/^[[:space:]]*[0-9]+:[[:space:]]*//')
find . -name "*.conf" -exec sh -c 'echo "$0" | chmod -E "$1"' "$SOURCE_ACLS" {} \;