Problems and fixes with shell script execution

Revised article

This is a revised version of an article originally published on larsolino.dev on January 8, 2025.

A shell script can fail for several different reasons on macOS. The exact message matters: permission denied, operation not permitted and bad interpreter do not necessarily have the same cause.

Start by inspecting the file and identifying the interpreter it expects. Replace script.sh in the examples with your script's path.

ls -l ./script.sh
head -n 1 ./script.sh
file ./script.sh

Read the script before running it, particularly if it came from a download.

Permission denied: check the executable bit

For direct execution with ./script.sh, the file needs execute permission. Grant it to the owner:

chmod u+x ./script.sh
./script.sh

The parent directories also need appropriate traversal permissions. An ACL or a filesystem mounted with execution disabled can block execution even when the file's execute bit is set; changing file permissions will not resolve those cases.

You can explicitly invoke the intended interpreter to distinguish direct-execution problems from problems inside the script:

/bin/zsh ./script.sh

Use /bin/bash instead if the script is written for Bash. Invoking an interpreter does not bypass macOS privacy controls and is not a reason to run an untrusted script.

Bad interpreter: check the shebang and line endings

The first line identifies the interpreter for direct execution. For example:

#!/bin/zsh

or:

#!/bin/bash

Check that the selected executable exists. A script that uses features of a newer Bash may not work with the Bash supplied by macOS; select the intended installed version explicitly when necessary.

An error containing ^M, such as /bin/bash^M: bad interpreter, usually points to Windows CRLF line endings. Save the file with Unix LF line endings in your editor and try again.

Operation not permitted: check file access

If the script or a command inside it accesses Desktop, Documents, Downloads or another protected location, check the privacy permissions of the application hosting the shell, such as Terminal, iTerm or an IDE.

Open System Settings → Privacy & Security → Files & Folders and inspect the relevant app's access. Grant only the access needed for the script. Apple's file and folder access guide explains these controls.

Identify the command that fails: a script can start correctly and then encounter a denied file operation. chmod +x on the script does not fix access to a protected directory, and sudo is not a general solution to privacy restrictions.

Downloaded files: inspect quarantine separately

A downloaded file may have a quarantine attribute. Inspect its attributes with:

xattr -l ./script.sh

The attribute's presence alone does not prove it caused the execution error. If macOS shows a security alert, verify the source and use the applicable steps in Apple's guide to safely opening software. A blanket removal of quarantine or disabling Gatekeeper is not an appropriate first troubleshooting step.

After correcting the specific cause, run the script again and check its output. If it still fails, keep the full error message and the failing command; they are more useful than treating every failure as an executable-permission problem.

Pappenheimer Logo

Site powered by Pappenheimer U-DXP