Overview & Root Cause Summary: The error
zsh: permission deniedoccurs in Z shell (the default terminal shell in macOS and widely used in Linux) when the user attempts to execute a file lacking the executable bit, attempts to run a directory as an executable, tries to redirect output into a protected system file where the shell itself lacks elevated permissions (e.g.,sudo echo "..." > /etc/hosts), or encounters security restrictions such as macOS quarantine attributes ornoexecfilesystem mounts.
Understanding the Root Causes
- Missing Executable Permission (
+x): Newly created shell scripts, Python scripts, or downloaded binaries default to read-and-write permissions (644orrw-r--r--), lacking execute rights. - Shell Output Redirection with
sudo: Runningsudo echo "data" > /etc/filefails because standard terminal redirection (>) is evaluated by the unprivileged user’s shell beforesudoexecutes the command. - Attempting to Execute a Directory: Pasting or invoking a directory path directly (e.g.,
./build/or~/scripts/) instead of changing into it (cd) prompts Zsh to attempt execution and fail. - macOS Gatekeeper Quarantine Attribute: Binaries downloaded via web browsers or curl often receive the
com.apple.quarantineextended attribute, blocking local execution. - Filesystem Mounted with
noexec: Storage partitions, USB drives, or shared virtual machine folders mounted with thenoexecflag reject all binary and script execution regardless of file permissions.
Step 1: Quick Fix (Execution Permissions & sudo Redirection)
Grant the necessary executable permissions or bypass shell redirection pitfalls using tee.
# 1. Grant execute permissions to a script or binary:
chmod +x ./script.sh
# Now execute normally:
./script.sh
# 2. Fix 'sudo echo ... > /protected/file' permission denied errors:
# WRONG (fails with zsh: permission denied):
# sudo echo "127.0.0.1 dev.local" >> /etc/hosts
# CORRECT: Use 'sudo tee -a' to write with elevated privileges:
echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts
# Alternatively, run an elevated subshell:
sudo sh -c 'echo "127.0.0.1 dev.local" >> /etc/hosts'
Step 2: Resolving Directory Execution and macOS Gatekeeper Flags
Ensure you are not accidentally executing a directory and remove macOS quarantine security flags from trusted binaries.
# 1. Check if the target path is a directory:
ls -ld ./target_path
# If the output starts with 'd' (e.g., drwxr-xr-x), navigate into it rather than running it:
cd ./target_path
# 2. On macOS: Check and remove the quarantine attribute from downloaded scripts/binaries:
xattr -l ./my_custom_binary
# If 'com.apple.quarantine' is present, remove it:
xattr -d com.apple.quarantine ./my_custom_binary
# 3. Ensure the script has an explicit Shebang line:
# The first line of your script must define the interpreter, e.g.:
# #!/usr/bin/env zsh
# or
# #!/usr/bin/env bash
Step 3: Managing File Ownership and Filesystem noexec Mounts
Reclaim ownership of files created under other user contexts and verify filesystem mount options.
# 1. Change file ownership back to your current user and group:
sudo chown $(whoami):$(id -gn) ./script.sh
# 2. Inspect filesystem mount flags (Linux / macOS):
mount | grep -i noexec
# If your target directory (e.g., /tmp or /mnt/data) is mounted with 'noexec':
# Run the script by explicitly invoking the shell interpreter directly:
zsh ./script.sh
# or
bash ./script.sh
# or
python3 ./script.py
Verification & Testing Steps
Confirm that the file has appropriate permissions and executes seamlessly in your shell.
# 1. Inspect file mode bits:
ls -la ./script.sh
# Should display executable bits: -rwxr-xr-x
# 2. Test execution:
./script.sh
# 3. Check shell exit code (should return 0 upon successful completion):
echo $?
Summary Comparison Table
| Scenario / Cause | Diagnostic Indicator | Recommended Solution | Platform |
|---|---|---|---|
| Missing Execute Bit | ls -l shows -rw-r--r-- |
chmod +x script.sh |
macOS & Linux |
| Root Redirection Conflict | sudo ... > /etc/file fails |
echo "..." | sudo tee -a /file |
macOS & Linux |
| macOS Quarantine Block | xattr -l shows quarantine |
xattr -d com.apple.quarantine <file> |
macOS |
| noexec Mount Partition | mount contains noexec |
Invoke interpreter explicitly (zsh <file>) |
macOS & Linux |
Leave a Reply