Install Bash 5 on macOS

The default bash on macOS is still bash v3:

$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

Just recently, bash v5 was released. The discrepancy comes from the fact that bash has been licensed as GPL v3 since version 4. Apple does not include GPL v3 licensed tools with macOS.

However, nothing is keeping you from downloading and installing the latest bash version.

New features include, among many other things, associated arrays (i.e. dictionaries) and better auto-completion setup.

While you would think this is a common desire, most pages I have found will simply point to Homebrew to download and install a newer bash version.

The main challenge with using brew is that it does not work on the scale that MacAdmins require. brew is designed for single user installation, where the user has administrator privileges. brew’s workflows do not scale to large deployments controlled with a management system.

Ideally, there would be package installer for the latest bash version. Unfortunately, the bash project does not provide one.

In this post, I will show how you can install the latest bash version without brew and how to build an installer package for deployment.

Manual Installation

This requires Xcode or the Developer Command Line Tools to be installed.

First, download the source for the latest bash version from this page. As of this writing the latest version is bash-5.0 and the file you want is bash-5.0.tar.gz. Once downloaded, you can expand the archive in Finder by double-clicking.

Update: I have a post with some updated instructions to include the patches to bash 5.0.

Open a Terminal window and change directory to the newly expanded bash-5.0 directory. Then run the configure script there.

$ cd ~/Downloads/bash-5.0
$ ./configure

The configure process will take a while, there will be plenty of messages showing progress.

Once the configure process is complete. You can build bash with the make command.

$ make

This will build the bash binary and the supporting files in the current directory. That’s not where we want it in the end, but it is probably a good idea see if the build process works. This will (again) take a while. There will be some odd looking warnings, but you can ignore those.

When make succeeds, you can actually install bash v5 with

$ sudo make install

This will build and install the bash binary and supporting files in /usr/local/bin and /usr/local. sudo is required to modify /usr/local.

If you were just looking for a way to install bash v5 without brew, you are done!

There is more useful information in the rest of the post, though, so keep reading!

How the new and the old bash interact

By default, the bash v5 binary is called bash and will be installed in /usr/local/bin. The macOS default PATH lists /usr/local/bin before /bin where the default bash v3 binary, also called bash, is located.

This means, that when a user types bash in to a shell, the version in /usr/local/bin will be preferred over the pre-installed bash v3.

You can test this behavior in Terminal. Since the default shell has not yet been changed from /bin/bash the Terminal still opens to bash v3. You can test this by showing the BASH_VERSION environment variable:

$ echo $BASH_VERSION
3.2.57(1)-release

But when you then run bash it will invoke /usr/local/bin/bash, so it will run the new bash v5. It will show this in the prompt, but you can also verify the BASH_VERSION.

$ bash
bash-5.0$ echo $BASH_VERSION
5.0.0(2)-release

This might be the setup you want, when you want to use bash v5 always. It might lead to some unexpected behavior for some users, though.

One option to avoid this ambiguity is to rename the binary in /usr/local/bin to bash5. But then other tools such as env (mentioned below) will not find the binary any more.

Note: the PATH in other contexts will likely not contain /usr/local/bin and further confuse matters.

bash v5 and Scripting

Scripts using bash, should have the full path to the binary in the shebang. This way, the script author can control whether a script is executed by the default bash v3 (/bin/bash) or the newer bash v5 (/usr/local/bin/bash or /usr/local/bin/bash5).

It is often recommended to use the env command in the shebang:

#!/usr/bin/env bash

The env command will determine the path to the bash binary in the current environment. (i.e. using the current PATH) This is useful when the script has to run in various environments where the location of the bash binary is unknown, in other words across multiple Unix and Unix-like platforms. However, this renders the actual version of bash that will interpret the script unpredictable.

For example, assume you have bash v5 installed in the default configuration (as /usr/local/bin/bash. A script with the shebang #!/usr/bin/env bash launched in the user environment (i.e. from Terminal) will use the newer bash, as /usr/local/bin comes before /bin in the search order.

When you launch the same script in a different context, e.g. as an installation script, an AppleScript, or a management system, /usr/local/bin will likely not be part of the PATH in that environment. Then the env shebang will choose /bin/bash (v3). The script will be interpreted and might behave differently.

Administrators prefer certainty in their managed environments. Administrators should know the location and versions of the binaries on their systems. For management scripts, you should avoid env and use the proper full path to the desired interpreter binary.

The solutions to resolve the ambiguity are

  • use the full path to the binary in the shebang
  • manage and update the additional custom version of bash with a management system
  • (optional) rename the newer bash binary to bash5 or bash4 (this also allows you to have bash v4 and bash v5 available on the same system)
  • Scripting OS X: On the Shebang
  • Scripting OS X: Setting the PATH in Scripts

Changing a user’s default Shell to bash v5

Even though we have installed bash v5, the default shell of a new Terminal window will still use the built-in bash v3.

The path to the default shell is stored in the user record. You can directly change the UserShell attribute with dscl, in the ‘Advanced Options’ of the ‘Users & Groups’ preference pane, or in Directory Utility.

There is also a command to set the default shell:

$ chsh -s /usr/local/bin/bash
Changing shell for armin.
Password for armin: 
chsh: /usr/local/bin/bash: non-standard shell

The chsh (change shell) command will check for allowed shells in the /etc/shells file. You can easily append a line with /usr/local/bin/bash to this file, and then chsh will work fine.

$ chsh -s /usr/local/bin/bash
Changing shell for armin.
Password for armin: 

Note: if you choose to rename the bash binary, you have to use the changed name in /etc/shells and with chsh.

Remember that just running chsh will not change the shell in the current Terminal window. It is best to close the old Terminal window and open a new one to get the new shell.

Packaging bash v5 for mass deployment

While these steps to install and configure bash v5 on a single Mac are simple enough, they would not work well with a management system for hundreds or thousands of Macs. We want to wrap all the files that make install creates into a package installer payload.

The --help option of the configure script yields this useful information:

By default, make install' will install all the files in/usr/local/bin,/usr/local/libetc. You can specify an installation prefix other than/usr/localusing–prefix, for instance–prefix=$HOME`.

When we run the configure script with the --prefix option it creates a folder suitable as a payload for a package installer. We can then use pkgbuild to build to create an installer pkg:

$ cd ~/Downloads/bash-5.0
$ mkdir payload
$ ./configure --prefix=/Users/armin/Downloads/bash-5.0/payload
$ make install
$ pkgbuild --root payload --install-location /usr/local --identifier org.gnu.bash --version 5.0 bash-5.0.pkg
pkgbuild: Inferring bundle components from contents of payload
pkgbuild: Wrote package to bash-5.0.pkg

(Note: the --prefix argument requires an absolute path.)

Automate the package creation

So, we have our workflow for building an installer package to distribute and configure bash v5:

  • download the archive
  • extract the archive
  • run configure with the --prefix argument
  • run make install to create the files in a payload folder
  • optional: rename the resulting bash binary to bash5 to avoid conflicts
  • add a postinstall script that adds /usr/local/bin/bash[5] to /etc/shells if not yet present
  • build the installer with pkgbuild

This sounds like a workflow ripe for automation. You can get the script from this repository.

You can pass a different (valid) bash version number as an argument to the script, e.g. 4.4.18. (I did not test anything significantly older.) The script does not autodetect the latest version and defaults to version 5.0 when no argument is given. When an update to bash v5 is published, you will have to modify the version line or run the script with an argument.

I have not (yet) figured out how to detect the latest version from the download web page. An autopkg recipe will have to wait for that. (If someone else wants to tackle that, please do!)

Published by

ab

Mac Admin, Consultant, and Author

20 thoughts on “Install Bash 5 on macOS”

  1. Greetings, just ran across your post, the latest version of bash can be found with this (on MacOS using curl, no wget by default). Here’s to hoping that all the tics, slashes, quotes, etc are faithfully reproduced in the command below:

    “`
    $ latestversion=$(curl -s https://ftp.gnu.org/gnu/bash/ | grep href | sed ‘s/.*href=”//’ | sed ‘s/”.*//’ | grep ‘bash-[0-9]*’ | grep -v doc | grep -v \/ | cut -c6-8 | sort -n | uniq | tail -1) ; echo “latest version == $latestversion”
    latest version == 5.0
    $
    “`

    Using $latest-version, you should be able to extract the tar.gz and tar.gz.sig files with a slightly modified curl

  2. Hey there,

    Great post.

    At the beginning are you saying that you *can’t* use brew to install the latest version of bash, on your own, single user MacBook Pro, using sudo?

    1. No. What I am saying is that brew does _not_ work well (or at all) at scale, i.e when you need to deploy bash5 (or anything) to dozens, or even hundreds or thousands of Macs.

  3. Hi,

    Great post.

    Wanted to add. I recently upgraded to Catalina, and when I tried to run `./configure`

    I got the error “cannot run C compiled programs”. apparently this is a somewhat generic issue that can be caused by multiple problems.

    In this case, it turns out the that Catalina update (and Mojave too apparently) have deprecated the /usr/include directory.

    [this](https://stackoverflow.com/questions/58278260/cant-compile-a-c-program-on-a-mac-after-upgrading-to-catalina-10-15/58278392#58278392) stack question has the solution that worked for me.

    1. The instructions given worked for me on my production Catalina system as well as a clean Catalina install without problems.

      Nevertheless, your link might help others who encounter the same problem. Thank you!

  4. Armin, nice, thorough work as usual. A question for an individual user like me: what’s the downside of archiving the old bash v3 and putting v5 into /bin/bash so that any shebang points to the new one?

    1. First, /bin/bash is protected by SIP. That should be your first warning sign. You would have to disable SIP to replace /bin/bash
      (not recommended).

      Second, there are hundreds and thousands of bash scripts from Apple and third parties, that you may not encounter directly but that will be running in installation processes or background tasks, that _assume_ /bin/bash is version 3.2, not version 5. While most bash scripts should behave the same with the newer version, unforeseen problems with other software is bound to happen and will be really hard to diagnose.

      Third, bash depends on the readline command, you’d have to replace that with the latest version as well, with the same caveats and limitations.

      Finally, macOS updates might overwrite your replacement. So, even if all else is going well, you have to re-apply your change frequently.

      It is safer to place bash v5 in its own location and use the full path in the shebang to make sure the script is executed with the version of bash you expect. If your scripts have to work cross-platform where the paths to the bash binary may differ, you can use /usr/bin/env bash as the shebang. This means ‘resolve the path to bash using the current environment and use that.’

    1. You remove the files it has installed. Most important are `/usr/local/bin/bash` and `/usr/local/bin/bashbug`. The process above installs a lot of other libraries and documentation files, but those will be mostly inert when you delete the binaries. You can get a full of list of files by building a pkg and listing the files from that pkg:

      pkgutil --payload-files GNU-bash-5.0.11.pkg
      .
      ./bin
      ./bin/bashbug
      ./bin/bash5
      ./include
      ./include/bash
      ./include/bash/jobs.h
      ./include/bash/error.h
      ./include/bash/signames.h
      ./include/bash/quit.h
      ./include/bash/bashansi.h
      ./include/bash/shell.h
      ./include/bash/version.h
      ./include/bash/syntax.h
      ./include/bash/dispose_cmd.h
      ./include/bash/xmalloc.h
      ./include/bash/alias.h
      ./include/bash/config.h
      ./include/bash/general.h
      ./include/bash/include
      ./include/bash/include/systimes.h
      ./include/bash/include/posixwait.h
      ./include/bash/include/posixstat.h
      ./include/bash/include/shtty.h
      ./include/bash/include/maxpath.h
      ./include/bash/include/filecntl.h
      ./include/bash/include/posixtime.h
      ./include/bash/include/posixjmp.h
      ./include/bash/include/unionwait.h
      ./include/bash/include/chartypes.h
      ./include/bash/include/stdc.h
      ./include/bash/include/typemax.h
      ./include/bash/include/gettext.h
      ./include/bash/include/ocache.h
      ./include/bash/include/ansi_stdlib.h
      ./include/bash/include/shmbchar.h
      ./include/bash/include/shmbutil.h
      ./include/bash/include/posixdir.h
      ./include/bash/include/memalloc.h
      ./include/bash/include/stat-time.h
      ./include/bash/conftypes.h
      ./include/bash/subst.h
      ./include/bash/assoc.h
      ./include/bash/builtins
      ./include/bash/builtins/bashgetopt.h
      ./include/bash/builtins/builtext.h
      ./include/bash/builtins/getopt.h
      ./include/bash/builtins/common.h
      ./include/bash/bashjmp.h
      ./include/bash/arrayfunc.h
      ./include/bash/builtins.h
      ./include/bash/make_cmd.h
      ./include/bash/sig.h
      ./include/bash/pathnames.h
      ./include/bash/externs.h
      ./include/bash/array.h
      ./include/bash/config-bot.h
      ./include/bash/bashintl.h
      ./include/bash/config-top.h
      ./include/bash/bashtypes.h
      ./include/bash/command.h
      ./include/bash/unwind_prot.h
      ./include/bash/y.tab.h
      ./include/bash/variables.h
      ./include/bash/siglist.h
      ./include/bash/hashlib.h
      ./lib
      ./lib/pkgconfig
      ./lib/pkgconfig/bash.pc
      ./lib/bash
      ./lib/bash/tee
      ./lib/bash/pathchk
      ./lib/bash/tty
      ./lib/bash/basename
      ./lib/bash/finfo
      ./lib/bash/setpgid
      ./lib/bash/whoami
      ./lib/bash/unlink
      ./lib/bash/sleep
      ./lib/bash/strftime
      ./lib/bash/logname
      ./lib/bash/printenv
      ./lib/bash/seq
      ./lib/bash/uname
      ./lib/bash/realpath
      ./lib/bash/truefalse
      ./lib/bash/print
      ./lib/bash/head
      ./lib/bash/push
      ./lib/bash/loadables.h
      ./lib/bash/mkdir
      ./lib/bash/fdflags
      ./lib/bash/rmdir
      ./lib/bash/Makefile.inc
      ./lib/bash/dirname
      ./lib/bash/mypid
      ./lib/bash/id
      ./lib/bash/ln
      ./lib/bash/sync
      ./share
      ./share/man
      ./share/man/man1
      ./share/man/man1/bash.1
      ./share/man/man1/bashbug.1
      ./share/locale
      ./share/locale/sl
      ./share/locale/sl/LC_MESSAGES
      ./share/locale/sl/LC_MESSAGES/bash.mo
      ./share/locale/sk
      ./share/locale/sk/LC_MESSAGES
      ./share/locale/sk/LC_MESSAGES/bash.mo
      ./share/locale/pl
      ./share/locale/pl/LC_MESSAGES
      ./share/locale/pl/LC_MESSAGES/bash.mo
      ./share/locale/vi
      ./share/locale/vi/LC_MESSAGES
      ./share/locale/vi/LC_MESSAGES/bash.mo
      ./share/locale/sv
      ./share/locale/sv/LC_MESSAGES
      ./share/locale/sv/LC_MESSAGES/bash.mo
      ./share/locale/ga
      ./share/locale/ga/LC_MESSAGES
      ./share/locale/ga/LC_MESSAGES/bash.mo
      ./share/locale/da
      ./share/locale/da/LC_MESSAGES
      ./share/locale/da/LC_MESSAGES/bash.mo
      ./share/locale/pt_BR
      ./share/locale/pt_BR/LC_MESSAGES
      ./share/locale/pt_BR/LC_MESSAGES/bash.mo
      ./share/locale/ja
      ./share/locale/ja/LC_MESSAGES
      ./share/locale/ja/LC_MESSAGES/bash.mo
      ./share/locale/el
      ./share/locale/el/LC_MESSAGES
      ./share/locale/el/LC_MESSAGES/bash.mo
      ./share/locale/it
      ./share/locale/it/LC_MESSAGES
      ./share/locale/it/LC_MESSAGES/bash.mo
      ./share/locale/ca
      ./share/locale/ca/LC_MESSAGES
      ./share/locale/ca/LC_MESSAGES/bash.mo
      ./share/locale/zh_TW
      ./share/locale/zh_TW/LC_MESSAGES
      ./share/locale/zh_TW/LC_MESSAGES/bash.mo
      ./share/locale/cs
      ./share/locale/cs/LC_MESSAGES
      ./share/locale/cs/LC_MESSAGES/bash.mo
      ./share/locale/ru
      ./share/locale/ru/LC_MESSAGES
      ./share/locale/ru/LC_MESSAGES/bash.mo
      ./share/locale/ro
      ./share/locale/ro/LC_MESSAGES
      ./share/locale/ro/LC_MESSAGES/bash.mo
      ./share/locale/zh_CN
      ./share/locale/zh_CN/LC_MESSAGES
      ./share/locale/zh_CN/LC_MESSAGES/bash.mo
      ./share/locale/pt
      ./share/locale/pt/LC_MESSAGES
      ./share/locale/pt/LC_MESSAGES/bash.mo
      ./share/locale/uk
      ./share/locale/uk/LC_MESSAGES
      ./share/locale/uk/LC_MESSAGES/bash.mo
      ./share/locale/sr
      ./share/locale/sr/LC_MESSAGES
      ./share/locale/sr/LC_MESSAGES/bash.mo
      ./share/locale/en@boldquot
      ./share/locale/en@boldquot/LC_MESSAGES
      ./share/locale/en@boldquot/LC_MESSAGES/bash.mo
      ./share/locale/gl
      ./share/locale/gl/LC_MESSAGES
      ./share/locale/gl/LC_MESSAGES/bash.mo
      ./share/locale/hr
      ./share/locale/hr/LC_MESSAGES
      ./share/locale/hr/LC_MESSAGES/bash.mo
      ./share/locale/hu
      ./share/locale/hu/LC_MESSAGES
      ./share/locale/hu/LC_MESSAGES/bash.mo
      ./share/locale/nl
      ./share/locale/nl/LC_MESSAGES
      ./share/locale/nl/LC_MESSAGES/bash.mo
      ./share/locale/bg
      ./share/locale/bg/LC_MESSAGES
      ./share/locale/bg/LC_MESSAGES/bash.mo
      ./share/locale/af
      ./share/locale/af/LC_MESSAGES
      ./share/locale/af/LC_MESSAGES/bash.mo
      ./share/locale/en@quot
      ./share/locale/en@quot/LC_MESSAGES
      ./share/locale/en@quot/LC_MESSAGES/bash.mo
      ./share/locale/nb
      ./share/locale/nb/LC_MESSAGES
      ./share/locale/nb/LC_MESSAGES/bash.mo
      ./share/locale/de
      ./share/locale/de/LC_MESSAGES
      ./share/locale/de/LC_MESSAGES/bash.mo
      ./share/locale/fi
      ./share/locale/fi/LC_MESSAGES
      ./share/locale/fi/LC_MESSAGES/bash.mo
      ./share/locale/eo
      ./share/locale/eo/LC_MESSAGES
      ./share/locale/eo/LC_MESSAGES/bash.mo
      ./share/locale/id
      ./share/locale/id/LC_MESSAGES
      ./share/locale/id/LC_MESSAGES/bash.mo
      ./share/locale/fr
      ./share/locale/fr/LC_MESSAGES
      ./share/locale/fr/LC_MESSAGES/bash.mo
      ./share/locale/es
      ./share/locale/es/LC_MESSAGES
      ./share/locale/es/LC_MESSAGES/bash.mo
      ./share/locale/et
      ./share/locale/et/LC_MESSAGES
      ./share/locale/et/LC_MESSAGES/bash.mo
      ./share/locale/lt
      ./share/locale/lt/LC_MESSAGES
      ./share/locale/lt/LC_MESSAGES/bash.mo
      ./share/locale/tr
      ./share/locale/tr/LC_MESSAGES
      ./share/locale/tr/LC_MESSAGES/bash.mo
      ./share/info
      ./share/info/dir
      ./share/info/bash.info
      ./share/doc
      ./share/doc/bash
      ./share/doc/bash/POSIX
      ./share/doc/bash/bash.html
      ./share/doc/bash/COMPAT
      ./share/doc/bash/INTRO
      ./share/doc/bash/FAQ
      ./share/doc/bash/CHANGES
      ./share/doc/bash/bashref.html
      ./share/doc/bash/README
      ./share/doc/bash/NEWS
      ./share/doc/bash/RBASH

      (Note: `.` in the list above refers to the install-location of the pkg which is `/usr/local`)

  5. Thanks! Very good instructions. I learned several things along the way. And I’m now running GNU bash, version 5.0.17(1)-release (x86_64-apple-darwin19.5.0).

    Wondering: under what conditions would it be good to update /etc/shells to include /usr/local/bin/bash:
    /bin/bash
    /bin/csh
    /bin/dash
    /bin/ksh
    /bin/sh
    /bin/tcsh
    /bin/zsh
    /usr/local/bin/bash

    ted

    1. There are a few tools that refer to this list, most importantly `chpass`, `chsh` and `chfn`. There are ways of changing the default shell without going through these tools (System Preferences, Directory Utility, `dscl`, Terminal). So, when you are the only user on your system, you don’t _need_ to update `/etc/shells`. But, you may end up in a confusing situation in the future where something might not work because `/usr/local/bin/bash` isn’t listed in that file, so it’s probably prudent.

      1. Great instructions. Thanks!

        Auto-complete (without a package manager) for Bash 5? Nice to have but not finding a clear win on how.

  6. I agree on installing it in its own location. I think the location of /usr/local/bin is good enough. I set the default to be the bash 5.0 and wrote a script to test all three bash. I tested all three of them and the 5.0 came on top. I will do further testing.

  7. I followed your instructions for installing bash 5.0 under macOS Catalina. (From your ebook as well as the blog.) The final step, sudo make install, generated error:
    ../../include/gettext.h:27:11: fatal error: ‘libintl.h’ file not found

    How fix this?

    1. I cannot recreate that problem. bash 5.0.18 works for me. I’d recommend deleting everything and starting over. You can also use the script I link to later in the post to create an installer pkg for you.

      1. For a workaround, I installed the MacPorts port of bash 5.0.17. And then in Terminal’s Preferences > General, for the “Shells open with” I clicked the “Command (complete path)” radio button and entered /opt/local/bin/bash for the path.

        One advantage of using bash from MacPorts is that it’s easy to update the port as part of my regular updating of all such ports.

  8. Tried to compile exactly as per yr instruction. make stops with fatal error too many errors emited. With “make CFLAGS=”-ferror-limit=0″ it throws 1 warning and 37 errors generated. make: *** [siglist.o] Eror 1

    No bash 5 on my macOS 10.15.7 now

Comments are closed.