#!/bin/bash # install-bash - install Homebrew bash and set it as the login shell (macOS) # # Usage: # install-bash # install-bash -h # # Works on both Intel (/usr/local) and Apple Silicon (/opt/homebrew) Macs # The actual bash install path is resolved dynamically via `brew --prefix` _install_bash() ( local SCRIPT_NAME; SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" case "${BASH_SOURCE[0]}" in /dev/*|/proc/*) SCRIPT_NAME="" ;; esac case "$SCRIPT_NAME" in ""|bash|sh|zsh|dash) SCRIPT_NAME="install-bash" ;; esac _show_help() { echo "NAME" echo " $SCRIPT_NAME - install Homebrew bash and set it as the login shell" echo "SYNOPSIS" echo " $SCRIPT_NAME" echo " $SCRIPT_NAME -h" echo "DESCRIPTION" echo " Performs the following steps (skipping any already completed):" echo " 1. Install Homebrew if not present" echo " 2. Install bash via \`brew install bash\`" echo " 3. Add the new bash to /etc/shells (requires sudo)" echo " 4. Set the current user's shell via \`chsh\` (permanent login-shell change)" echo " 5. Append \`brew shellenv\` output to ~/.bash_profile if not already present" echo "" echo " The install location is resolved dynamically via \`brew --prefix\`, so the" echo " script works on both Intel (/usr/local) and Apple Silicon (/opt/homebrew)." echo "PRECONDITIONS" echo " - macOS only (uses Homebrew, chsh, and /etc/shells)" echo " - sudo access (for editing /etc/shells)" echo " - chsh changes the login shell permanently; revert with \`chsh -s /bin/zsh\`" echo "OPTIONS" echo " -h, --help Show this help message" echo "EXIT STATUS" echo " 0 Success" echo " 1 Runtime failure (install step failed, sudo/chsh rejection, etc.)" echo " 2 Precondition error (not running on macOS)" echo "DEPENDENCIES" echo " curl, sudo, chsh, uname" } _error() { echo "[ERR][$SCRIPT_NAME] $*" >&2; } case "$1" in -h|--help) _show_help; return 0 ;; esac # Prefix messages for clarity local px="[$SCRIPT_NAME]" # macOS-only check local os; os="$(uname -s)" if [ "$os" != "Darwin" ]; then _error "macOS only (detected: $os)" return 2 fi # Install Homebrew if command -v brew >/dev/null 2>&1; then echo "$px Homebrew (brew) is already installed, skipping this step" else if /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; then echo "$px Installed Homebrew (brew)" else _error "Homebrew installation failed (\`/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\`)" return 1 fi fi # If brew was just installed (or isn't on PATH yet in this shell), source its # shellenv from the standard install location before continuing if ! command -v brew >/dev/null 2>&1; then if [ -x "/opt/homebrew/bin/brew" ]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [ -x "/usr/local/bin/brew" ]; then eval "$(/usr/local/bin/brew shellenv)" else _error "\`brew\` is not on PATH after install" return 1 fi fi # Install Bash using Homebrew if brew install bash; then echo "$px Installed Bash via Homebrew (\`brew install bash\`)" else _error "Failed to install Bash via Homebrew" return 1 fi # Resolve the actual bash install path dynamically (Intel vs Apple Silicon) local brew_prefix; brew_prefix="$(brew --prefix)" if [ -z "$brew_prefix" ]; then _error "Failed to resolve Homebrew prefix (\`brew --prefix\`)" return 1 fi local bash_path="$brew_prefix/bin/bash" # Add to /etc/shells first so `chsh` will allow it if grep -qxF "$bash_path" "/etc/shells"; then echo "$px \"$bash_path\" already added to /etc/shells, skipping this step" else # `tee` is required because you can't sudo redirect to a file if echo "$bash_path" | sudo tee -a "/etc/shells" >/dev/null; then echo "$px Added \"$bash_path\" to /etc/shells" else _error "Failed to add \"$bash_path\" to /etc/shells (\`echo \"$bash_path\" | sudo tee -a \"/etc/shells\" >/dev/null\`)" return 1 fi fi # Change user's shell if chsh -s "$bash_path"; then echo "$px Shell for $USER set to $bash_path" else _error "Failed to change user's shell (\`chsh -s $bash_path\`)" return 1 fi # If brew detects that shellenv has already been eval'd by inspecting $PATH nothing will be printed, # but in this case we want to make sure we get output so we set the path to the bare minimum, for this single command local shellenv; shellenv="$(PATH=/usr/bin "$brew_prefix/bin/brew" shellenv)" if ! grep -qF "$shellenv" "$HOME/.bash_profile"; then if printf '\n%s\n' "$shellenv" >>"$HOME/.bash_profile"; then echo "$px Added Homebrew environment setup to ~/.bash_profile (\`brew shellenv\`)" else _error "Failed to add \`brew shellenv\` output to ~/.bash_profile" return 1 fi else echo "$px Homebrew environment setup (\`brew shellenv\`) already present in ~/.bash_profile, skipping this step" fi ) _install_bash "$@" __install_bash_rc=$? unset -f _install_bash if [ -n "${BASH_SOURCE[0]}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then eval "unset __install_bash_rc; return $__install_bash_rc" fi eval "unset __install_bash_rc; exit $__install_bash_rc"