MacOS's subtle differences in command line tools
Some tools like sed, readlink, wc, and stat behave different on MacOS. See how to get those Linux commands running.
I'm a Linux guy. Nevertheless I've been setting up a development environment on MacOS. As this is still a Unix based system running zsh
, I wasn't expecting any issues at all.
I was wrong.
It turns out, that many command line tools, work slightly different on MacOS than they do on Linux. Here's what I found so far.
sed
The stream editor sed
is very handy to quickly replace text in files. To edit a file in-place on Linux, you'd run
$ sed -i 's/TEXT/REPLACEMENT/d' *.sh
On MacOS this command fails with
$ sed -i 's/TEXT/REPLACEMENT/d' *.sh
sed 1: "patchUP.sh": command c expects \ followed by text
The reason is that on Mac OS -i
expects an argument for a backup extensions. Hence the command to in-place edit files is
$ sed -i '' 's/TEXT/REPLACEMENT/d' *.sh
readlink
readlink
can print the value of a symbolic link or canonical file name. The MacOS version does not have the -f
(canonicalize symlink), and -e
(canonicalize existing symlink) options. Thus running the command with such option will result in
$ readlink -e $directory
readlink: illegal option -- e
usage: readlink [-fn] [file ...]
The solution here isn't straightforward. It's either using greadlink
from Homebrew's coreutils
or involves scripting around pwd -P
. For the latter have a look into this Gist: How to get GNU's readlink -f behavior on OS X.
wc
We've been using a variant of Git's predefined pre-commit hook. On MacOS this hook was constantly failing on each commit. One offending line was
test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
To see what's going on, let's have a look at the output of following script
# /usr/bin/env bash
set -o xtrace
echo "single line" | wc -l
On Linux this results in
$ ./wcount.sh
+ echo 'single line'
+ wc -l
1
While on MacOS
$ ./wcount.sh
+ echo 'single line'
+ wc -l
1
So wc -l
is prefixing its result with some spaces.
To get rid of those, they are trimmed within the test
command
test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c | LC_ALL=C tr -d ' ') != 0
stat
Checking file sizes is also part of the pre-commit hook. The command run on Linux is
stat -c %s $file
On MacOS this command fails with
$ stat -c %s /etc/hosts
stat: illegal option -- c
usage: stat [-FLnq] [-f format | -l | -r | -s | -x] [-t timefmt] [file ...]
The man
page reveals the format to display the file size
stat -f %z /etc/hosts
Summary
Having these subtle but breaking changes in such basic commands is really unexpected. It seems like MacOS is trying to different only for the sake of being different here. So from now on I'm cluttering my scripts with switches like
if [ "$(uname)" = "Darwin" ]; then
# do Mac stuff
else
# do Linux stuff
fi