Skip to content

Instantly share code, notes, and snippets.

@skyzyx
Last active October 9, 2024 21:26
Show Gist options
  • Save skyzyx/3438280b18e4f7c490db8a2a2ca0b9da to your computer and use it in GitHub Desktop.
Save skyzyx/3438280b18e4f7c490db8a2a2ca0b9da to your computer and use it in GitHub Desktop.
Using GNU command line tools in macOS instead of FreeBSD tools

macOS is a Unix, and not built on Linux.

I think most of us realize that macOS isn't a Linux OS, but what that also means is that instead of shipping with the GNU flavor of command line tools, it ships with the FreeBSD flavor. As such, writing shell scripts which can work across both platforms can sometimes be challenging.

Homebrew

Homebrew can be used to install the GNU versions of tools onto your Mac, but they are all prefixed with "g" by default.

All commands have been installed with the prefix "g". If you need to use these commands with their normal names, you can add a "gnubin" directory to your PATH from your bashrc.

Choosing GNU for Consistency

You can install most of the GNU flavored tools with:

brew install autoconf bash binutils coreutils diffutils ed findutils flex gawk \
    gnu-indent gnu-sed gnu-tar gnu-which gpatch grep gzip less m4 make nano \
    screen watch wdiff wget zip

Assuming you have a fairly standard Terminal/shell environment, and assuming that you want to use the GNU versions instead of the BSD versions for everything you've installed with Homebrew, you can append the following to your ~/.profile file.

UPDATE (2022-06-16): I now have an Intel MacBook Pro and an Apple Silicon Mac Studio. Homebrew moved with the new CPU architecture, so the following is what I now use that works across both machines (my shell profile is shared across machines with Dropbox and symlinks).

BREW_BIN="/usr/local/bin/brew"
if [ -f "/opt/homebrew/bin/brew" ]; then
    BREW_BIN="/opt/homebrew/bin/brew"
fi

if type "${BREW_BIN}" &> /dev/null; then
    export BREW_PREFIX="$("${BREW_BIN}" --prefix)"
    for bindir in "${BREW_PREFIX}/opt/"*"/libexec/gnubin"; do export PATH=$bindir:$PATH; done
    for bindir in "${BREW_PREFIX}/opt/"*"/bin"; do export PATH=$bindir:$PATH; done
    for mandir in "${BREW_PREFIX}/opt/"*"/libexec/gnuman"; do export MANPATH=$mandir:$MANPATH; done
    for mandir in "${BREW_PREFIX}/opt/"*"/share/man/man1"; do export MANPATH=$mandir:$MANPATH; done
fi

coreutils provides:

[, b2sum, base32, base64, basename, basenc, cat, chcon, chgrp, chmod, chown, chroot, cksum, comm, cp, csplit, cut, date, dd, df, dir, dircolors, dirname, du, echo, env, expand, expr, factor, false, fmt, fold, groups, head, hostid, id, install, join, kill, link, ln, logname, ls, md5sum, mkdir, mkfifo, mknod, mktemp, mv, nice, nl, nohup, nproc, numfmt, od, paste, pathchk, pinky, pr, printenv, printf, ptx, pwd, readlink, realpath, rm, rmdir, runcon, seq, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, shred, shuf, sleep, sort, split, stat, stdbuf, stty, sum, sync, tac, tail, tee, test, timeout, touch, tr, true, truncate, tsort, tty, uname, unexpand, uniq, unlink, uptime, users, vdir, wc, who, whoami, yes

ed provides:

ed, red

gawk provides:

awk

grep provides:

egrep, fgrep, grep

gnu-sed provides:

sed

gnu-tar provides:

tar

make provides:

make

findutils provides:

find, locate, updatedb, xargs

zip provides:

zip

@knownothingsnow
Copy link

@e0267744

try replace

export GNUBINS="$(find /usr/local/opt -type d -follow -name gnubin -print)";

with

GNUBINS=( $(find /usr/local/opt -type d -follow -name gnubin -print) )

it should do the work.

@evanrrees
Copy link

The proposed find based solution takes about 5 seconds to execute on my system. The following bash snippet executes in a fraction of a second.

if type brew &>/dev/null; then
  HOMEBREW_PREFIX=$(brew --prefix)
  # gnubin; gnuman
  for d in ${HOMEBREW_PREFIX}/opt/*/libexec/gnubin; do export PATH=$d:$PATH; done
  # I actually like that man grep gives the BSD grep man page
  #for d in ${HOMEBREW_PREFIX}/opt/*/libexec/gnuman; do export MANPATH=$d:$MANPATH; done
fi

Works like a charm! Thanks @skyzyx and @abouteiller.

@OriBenHur
Copy link

OriBenHur commented Jan 17, 2022

if you are like me and you don't like duplicates use this

if type brew &>/dev/null; then
  HOMEBREW_PREFIX=$(brew --prefix)
  NEWPATH=${PATH}
  # gnubin; gnuman
  for d in ${HOMEBREW_PREFIX}/opt/*/libexec/gnubin; do NEWPATH=$d:$NEWPATH; done
  # I actually like that man grep gives the BSD grep man page
  #for d in ${HOMEBREW_PREFIX}/opt/*/libexec/gnuman; do export MANPATH=$d:$MANPATH; done
 export PATH=$(echo ${NEWPATH} | tr ':' '\n' | cat -n | sort -uk2 | sort -n | cut -f2- | xargs | tr ' ' ':')
fi

@obs-gh-robnavarro
Copy link

obs-gh-robnavarro commented Apr 24, 2024

If you have an M3 Mac AND use Bash (brew install bash) AND have brew in your PATH then the following executes quickly within the ~/.bash_profile:

BREWP=$(brew --cellar)
if [[ -d "$BREWP" ]] ; then	# running on a M? Apple CPU
   unset dirs
   mapfile -t dirs < <( shopt -s globstar; ls -1d "${BREWP}"/**/libexec/gnubin; )
   export PATH=$(IFS=: ; echo "${dirs[*]}"):$PATH
fi

The idea here is to use shell executable ls to create list of directories instead of running a slower shell loop to get the same output. You can extend this for the other directories as needed, just repeat and modify the two rows: unset ... mapfile ... and then follow with appropriate export PATH...

@weirane
Copy link

weirane commented Jun 29, 2024

If I add the MANPATHs, my man command gets very slow with fast-syntax-highlighting (after I type man in the terminal, typing another character takes a long time). I'm assuming the syntax highlighting script needs to be run every time the current command is updated, and the long MANPATH caused the long time. So I have to disable it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment