OPEN SOURCE · MIT · SEPTEMBER 2026

Will it run on a Mac? Ask the Mac.

Paste a shell script. Every command flag that does not exist on macOS or on BusyBox is marked, and the proof for each verdict is that platform's own man page line and a real execution of the binary there. Shell constructs are judged under the interpreter your shebang reaches: bash 3.2, zsh, dash or BusyBox ash. The checks run in your browser; nothing is uploaded.

Published 2026-09-13 · updated 2026-09-16 · 81 tools, 86 find primaries and 142 shell constructs · 3 platforms, 14 shells, zsh also under 8 option settings · 21,775 recorded results from 39,088 executions, re-recorded weekly

A release script from a Linux laptop is entered a line at a time while the verdicts update: by the sixth line macOS already has one flag that breaks, by the end 7 break on macOS and 4 on Alpine; then the page scrolls to the findings and opens the evidence on date -d, where the macOS binary answered 'date: illegal option -- d' although its man page still lists a -d
Each flag gets three verdicts. Expand one to see the man page line and the exact command that was executed on that platform, with its exit code and the first line of stderr.

The problem

"Works on my Linux, breaks on my Mac." sed -i without a suffix, date -d yesterday, grep -P, find -printf, xargs -d, cp --reflink, stat -c, timeout: the same dozen flags have broken macOS and Alpine CI jobs for fifteen years. ShellCheck has been asked to warn about them since 2015 (#479, #973, #1455, #2902). Its maintainer's answer was reasonable: ShellCheck knows which shell you target, not which userland, and somebody would have to maintain the list of what every tool accepts on every platform. Nobody wanted to maintain that list by hand.

This page does not maintain it. It measures it.

How it works

A GitHub Actions matrix runs every week on ubuntu-latest (Ubuntu 24.04, GNU coreutils), macos-latest (macOS 26, the BSD userland) and an alpine container (BusyBox). On each platform, for 81 common tools, the collector records:

StepWhat is recorded
collect.sh--help output, the usage line a BSD tool prints on an unknown option, the rendered man page and its mdoc source
probe.sh87 scenario commands (sed -i 's/a/b/' f, date -d yesterday, find . -printf, …) with exit code, first stderr line and first stdout line
probe-all.shfor 74 of the tools (network, process and host-changing ones are left out: curl, wget, hostname, kill, pkill, rsync, openssl), every short flag -a-z, -A-Z, -0-9 and every long option any platform documents, executed with and without an operand file; the answer is classified as rejected or recognized by comparing it with the tool's own unknown-option wording, learned from two canary invocations
probe-all.sh (find)86 find primaries and operators (-newermt, -printf, -delete, -regextype, -a, …) executed as find . <primary> … with arguments chosen to be valid wherever the primary exists, so a rejection is about the primary and not its argument; classified against find’s own unknown-primary wording, learned from its own canary
shells/probe-shells.sh142 shell constructs plus a version check (arrays, ${x,,}, [[ ]], (( )), <( ), mapfile, an unquoted $var, a glob that matches nothing, cmd | read v, …), chosen by scanning the shell scripts of 28 widely run repositories, run as one-line scripts under every shell each platform has: bash 5.2 and 3.2, zsh 5.9, dash, ksh93, BusyBox ash, 14 interpreters in all. zsh is run eight more times on each platform, under shwordsplit, ksharrays, nonomatch, octalzeroes, bashrematch, all five at once, emulate sh and emulate ksh, which makes 38 interpreter configurations. Each run records exit code, first stdout line and first stderr line

That is 33,654 flag executions, 11,696 on Ubuntu, 11,304 on macOS and 10,654 on Alpine (every short and long flag runs twice, with and without an operand file, and every find primary once), plus 429 canary runs. The database keeps one result per flag per platform, 16,341 in all: 5,682 on Ubuntu, 5,415 on macOS and 5,244 on Alpine. Tools whose canaries print nothing to compare with (echo everywhere, zip on Ubuntu and macOS, getopt on macOS, printf on Alpine) are run but not kept. The shells add 5,434 one-liner runs (the 142 constructs and the version check), 1,859 on Ubuntu, 1,716 on macOS and 1,859 on Alpine; 2,002 of them under the 14 interpreters with their default options and 3,432 under the zsh option settings. The recordings become a database in which every flag, on every platform, carries the verbatim line that documents it and the result of actually running it. The page loads one small file per tool.

In the browser, tree-sitter-bash finds every simple command in the script: inside pipelines, && chains, subshells, functions, loops, $(...) and unquoted heredocs. sudo, env, nohup, timeout, xargs CMD, find -exec CMD and sh -c '...' are unwrapped so the inner command is checked. Combined flags are split the way the tool would split them (-rf, -n5, --in-place=.bak), and everything after -- is an operand.

The order of trust. A real execution of the flag on that platform beats the man page. The man page beats silence. Silence is reported as unknown, not guessed. A dynamic word ($OPTS, "$@") is reported as not checked. A shell builtin (echo -e) is marked as depending on the shell, not the userland. A tool that does not exist on a platform at all (timeout and tac on macOS) is a break by itself.

The shell is judged separately from the userland. The page reads the shebang (#!/bin/bash is bash 3.2 on macOS; #!/bin/sh is dash on Ubuntu, bash-as-sh on macOS and BusyBox ash on Alpine; no shebang means the default shell, which is zsh on a Mac) and compares each construct's recorded run under that interpreter with bash 5.2 on Ubuntu: same, differs (both run, different output) or breaks (bash runs it, this shell errors). Both recorded runs are shown.

What the runs showed

Things the recordings pinned down that the cheat sheets get wrong or do not say. Every line below is backed by a recorded execution the page can show you.

FindingRecorded
macOS 26 has more GNU-style flags than folklore claimsexit 0 on the macOS runner for readlink -f, realpath, sort -V, sort -h, uniq -D, wc -L, date -Iseconds, ls --color. The lists that say otherwise were true in 2015.
The classic breaks are still breaksrejected on macOS 26 with the binary's own message: sed -z, grep -P, date -d, stat -c, find -printf, xargs -d, du -b, cp --reflink, cp -u, ln -r, tar --wildcards. head -n, base64 -w, touch -d and install -D are recognized as flags, but the GNU-style commands head -n -1 f (a negative count: illegal line count -- -1), base64 -w0 f, touch -d '2020-01-01' f (the binary wants YYYY-MM-DDThh:mm:SS) and install -D f d/sub/f2 fail there. timeout, tac and nproc are not there at all.
sed -i is the subtle oneThe flag exists on all three. On macOS it takes a required argument, so sed -i 's/foo/bar/' f uses the script as the backup suffix and fails on the file name; on GNU sed the macOS fix sed -i '' fails instead. The page shows both recorded failures next to the flag that "exists".
BusyBox is its own islandrejected on Alpine: uniq -D, split -d, ls -G, rm -I, base64 -b, which -s, date -v. mktemp -t is accepted, but the macOS command mktemp -t x fails there (Invalid argument). date -d exists there but does not understand yesterday.
zsh is not bash with a different promptUnder zsh 5.9 on the macOS runner (the macOS default shell), 28 of the 142 constructs that bash 5.2 runs are errors, among them a glob with no match (no matches found), [ a == a ], ${x,,}, ${x^^}, ${!name}, mapfile, read -p, read -a, shopt, declare -n, ${x@Q}, $EPOCHSECONDS, type -t, compgen, complete, wait -n, ${!arr[@]}, ${x@U}, ;;&. Nineteen more run and print something else; among them, ${arr[1]} is the first element, an unquoted $var is one word, echo x | read v keeps v, $((010)) is 10, ${PIPESTATUS[@]} and ${BASH_REMATCH[1]} are unset, a trap … EXIT set inside a function fires when the function returns. zsh's compatibility options close less of that gap than their names promise: the best of the eight recorded settings, emulate sh, puts 13 of those 47 (28 errors plus 19 differences) right and gets 2 wrong that were right ({1..3} brace expansion and declare -a now print something else), and 33 are put right by none of them (these knob counts are the same on all three platforms). bash 3.2, the /bin/bash Apple ships, refuses 21 of the 142 (among them ;&, |&, wait -n and an empty array under set -u); Ubuntu's dash 0.5.12 refuses 83, the dash on Alpine 80, and BusyBox ash 57.
Documentation and binaries disagreemacOS's date(1) man page still documents -d; the binary answers illegal option -- d. BusyBox accepts cp --reflink, grep --color and od -A that its usage text never mentions. This is why a run outranks a man page.
date +%N is not an error, it is wrongOn Alpine, BusyBox's date +%s%N exits 0 and prints ten digits: %N becomes nothing, so the nanoseconds are silently gone. Ubuntu and macOS 26 print nineteen. Exit codes alone would have called it fine; the scenario probes record the first line of output for exactly this reason.

What it does not do

Wrong verdict? Open an issue with the command and the platform. If a real run disagrees with the page, the run wins and becomes a probe.

How it was built

In one evening, with Claude Fable 5.1 writing the code and a human deciding what to measure, what to trust and what to ship. What makes that acceptable is the same thing that makes the tool acceptable: nothing in the database is written by anyone. The platforms answered every question themselves, and they will answer again next Monday.

It follows Why didn't my job run?, which applies the same rule to GitHub Actions job conditions: evidence comes from real runs, not from documentation read by a model.

Türkçe özet

"Will it run on a Mac?", bir shell betiğini yapıştırdığınızda hangi komut bayraklarının macOS'ta (BSD araçları) ya da Alpine'de (BusyBox) olmadığını gösteren açık kaynak bir sayfa. Farkı kanıtında: her karar için o platformun kendi man sayfası satırı ve o platformda gerçekten çalıştırılmış komutun sonucu gösteriliyor. Veritabanı elle yazılmıyor; GitHub Actions her hafta Ubuntu, macOS ve Alpine koşucularında 81 araçtan 74'ünün her kısa bayrağını ve belgelenmiş her uzun seçeneğini, bir dosyayla ve dosyasız iki kez gerçekten çalıştırıp aracın cevabını kaydediyor: platform başına yaklaşık 11 bin koşu, bayrak başına tek sonuç. ShellCheck'ten 2015'ten beri istenen ama hiç yapılmamış özellik bu; bakımcının "o listeyi kim tutacak" itirazına cevap, listeyi tutmamak, ölçmek. Sınırları açık: bayrağın varlığına bakıyor, anlamına değil; değişkenleri açmıyor; üç platformun o haftaki sürümüyle sınırlı. Betik tarayıcıdan çıkmıyor, lisans MIT.