Created
May 1, 2026 15:15
-
-
Save jamestomasino/3bdfaacb80c4bd4f1f6e06629ec66ba9 to your computer and use it in GitHub Desktop.
Make macOS shell run in parity with Linux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| log() { | |
| printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*" | |
| } | |
| append_if_missing() { | |
| local file="$1" | |
| local line="$2" | |
| touch "$file" | |
| if ! grep -Fqx "$line" "$file"; then | |
| printf '%s\n' "$line" >> "$file" | |
| fi | |
| } | |
| detect_brew_prefix() { | |
| if [[ -x /opt/homebrew/bin/brew ]]; then | |
| echo "/opt/homebrew" | |
| elif [[ -x /usr/local/bin/brew ]]; then | |
| echo "/usr/local" | |
| else | |
| echo "" | |
| fi | |
| } | |
| ensure_homebrew() { | |
| if ! command -v brew >/dev/null 2>&1; then | |
| echo "Homebrew is required but not installed." | |
| echo "Install it first from https://brew.sh and re-run this script." | |
| exit 1 | |
| fi | |
| } | |
| main() { | |
| ensure_homebrew | |
| local brew_bin | |
| brew_bin="$(command -v brew)" | |
| local brew_prefix | |
| brew_prefix="$(brew --prefix)" | |
| log "Updating Homebrew" | |
| brew update | |
| log "Installing Linux-parity CLI packages" | |
| brew install \ | |
| bash \ | |
| coreutils \ | |
| findutils \ | |
| gawk \ | |
| gnu-getopt \ | |
| gnu-sed \ | |
| gnu-tar \ | |
| grep \ | |
| make \ | |
| diffutils \ | |
| less \ | |
| ed \ | |
| watch \ | |
| jq | |
| log "Upgrading existing formulae" | |
| brew upgrade | |
| log "Ensuring modern Bash is listed in /etc/shells" | |
| local bash_path | |
| bash_path="$(brew --prefix)/bin/bash" | |
| if ! grep -Fxq "$bash_path" /etc/shells 2>/dev/null; then | |
| echo "Adding $bash_path to /etc/shells, sudo may prompt once." | |
| printf '%s\n' "$bash_path" | sudo tee -a /etc/shells >/dev/null | |
| fi | |
| if [[ "${SHELL:-}" != "$bash_path" ]]; then | |
| log "Switching default shell to Homebrew Bash" | |
| chsh -s "$bash_path" || true | |
| fi | |
| log "Configuring shell startup files" | |
| local zprofile="$HOME/.zprofile" | |
| local zshrc="$HOME/.zshrc" | |
| local bash_profile="$HOME/.bash_profile" | |
| local bashrc="$HOME/.bashrc" | |
| append_if_missing "$zprofile" 'eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null || /usr/local/bin/brew shellenv)"' | |
| append_if_missing "$bash_profile" '[[ -f ~/.bashrc ]] && source ~/.bashrc' | |
| local parity_block_start="# >>> linux-parity-bootstrap >>>" | |
| local parity_block_end="# <<< linux-parity-bootstrap <<<" | |
| local parity_block | |
| parity_block=$(cat <<'EOF' | |
| # >>> linux-parity-bootstrap >>> | |
| export HOMEBREW_NO_AUTO_UPDATE=1 | |
| if command -v brew >/dev/null 2>&1; then | |
| eval "$(brew shellenv)" | |
| PATH="$(brew --prefix coreutils)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix findutils)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix gnu-sed)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix gawk)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix grep)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix make)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix gnu-tar)/libexec/gnubin:$PATH" | |
| PATH="$(brew --prefix gnu-getopt)/bin:$PATH" | |
| MANPATH="$(brew --prefix coreutils)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix findutils)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix gnu-sed)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix gawk)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix grep)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix make)/libexec/gnuman:${MANPATH:-}" | |
| MANPATH="$(brew --prefix gnu-tar)/libexec/gnuman:${MANPATH:-}" | |
| fi | |
| export NVM_DIR="$HOME/.nvm" | |
| if [[ -s "$(brew --prefix nvm 2>/dev/null)/nvm.sh" ]]; then | |
| . "$(brew --prefix nvm)/nvm.sh" | |
| elif [[ -s "$NVM_DIR/nvm.sh" ]]; then | |
| . "$NVM_DIR/nvm.sh" | |
| fi | |
| # <<< linux-parity-bootstrap <<< | |
| EOF | |
| ) | |
| for rc in "$zshrc" "$bashrc"; do | |
| touch "$rc" | |
| if ! grep -Fq "$parity_block_start" "$rc"; then | |
| printf '\n%s\n' "$parity_block" >> "$rc" | |
| fi | |
| done | |
| log "Running brew cleanup" | |
| brew cleanup | |
| log "Basic verification" | |
| echo "brew: $(brew --version | head -n 1)" | |
| echo "bash: $("$bash_path" --version | head -n 1)" | |
| echo "sed: $(sed --version 2>/dev/null | head -n 1 || true)" | |
| echo "awk: $(awk --version 2>/dev/null | head -n 1 || true)" | |
| echo "make: $(make --version | head -n 1)" | |
| echo "tar: $(tar --version | head -n 1)" | |
| echo "find: $(find --version 2>/dev/null | head -n 1 || true)" | |
| cat <<EOF | |
| Done. | |
| Next steps: | |
| 1. Open a new terminal session. | |
| 2. Verify: | |
| which bash sed awk make tar find | |
| 3. Expected: | |
| bash -> $brew_prefix/bin/bash | |
| sed/awk/make/tar/find should resolve to Homebrew GNU versions first. | |
| Note: | |
| - BSD and Apple tools still exist on the system. | |
| - This script makes GNU tools win via PATH, which is the safest parity model. | |
| - If your repos hardcode /bin/bash, update those shebangs to /usr/bin/env bash. | |
| EOF | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment