When working with shell scripts on macOS, you'll quickly discover that the readlink
command behaves differently than its Linux counterpart. The Linux version has useful flags like -m
(canonicalize) and -n
(no newline), but macOS's BSD-based implementation lacks these features.
Here's a function that works consistently across both Linux and macOS:
get_absolute_script_path() {
# Try Linux-style readlink first
if command -v readlink >/dev/null 2>&1 && \
readlink --version 2>&1 | grep -q GNU; then
readlink -mn "$0"
else
# Fallback for macOS/BSD
perl -MCwd -e 'print Cwd::abs_path(shift)' "$0"
fi
}
SCRIPT_PATH=$(get_absolute_script_path)
echo "Running script from: $SCRIPT_PATH"
If you prefer not to use Perl, consider these options:
# Using Python (macOS ships with Python)
python -c "import os; print(os.path.realpath('$0'))"
# Using Homebrew-installed coreutils (recommended for frequent use)
if brew list | grep -q coreutils; then
greadlink -f "$0"
fi
Getting the correct script path is crucial when:
- Your script needs to reference files relative to its location
- You're running through symlinks in /usr/local/bin
- You need to determine installation paths for dependencies
The solution accounts for:
# Multiple levels of symlinks
ln -s /path/to/script /tmp/link1
ln -s /tmp/link1 /tmp/link2
# Paths containing spaces
SCRIPT_WITH_SPACES="My Scripts/test.sh"
When writing shell scripts on macOS, developers often need to reliably determine the absolute path of the currently executing script, including resolving any symbolic links. While Linux systems provide straightforward solutions using readlink -f
or realpath
, macOS's BSD-based tools behave differently.
The common Linux solution:
$(readlink -f "$0")
won't work on macOS because:
- BSD
readlink
doesn't support the-f
flag - macOS lacks the
realpath
command entirely
Here are three reliable methods to get the absolute script path on macOS:
1. Using Python (Cross-Platform)
SCRIPT_PATH=$(python -c "import os; print(os.path.realpath('$0'))")
2. Pure Shell Solution
SCRIPT_PATH=$(
cd "$(dirname "$0")" || exit
pwd -P
)/$(basename "$0")
3. Using Homebrew-installed coreutils
If you have Homebrew:
brew install coreutils
SCRIPT_PATH=$(greadlink -f "$0")
For production scripts, consider these scenarios:
# Handle spaces in path
SCRIPT_PATH=$(
cd "$(dirname "$0")" &> /dev/null || exit 1
pwd -P
)/$(basename "$0")
SCRIPT_PATH=${SCRIPT_PATH%/*} # If you just need the directory
The Python method is most reliable but incurs interpreter startup overhead. For frequently-called scripts, the pure shell solution offers better performance.