9

Background

Bash 4 installed successfully using homebrew and the additional steps required to make bash 4 your default shell. Many guides on the net with a simple google: Example Bash 4 upgrade guide for Mac

  • Bash 4.x (installed via brew) location: /usr/local/bin/bash
  • Bash 3.2 (Default OSX) location: /bin/bash

Problem

Scripts I can not control/modify the shebang, as it would affect other users. Unless I was to manually change and then revert before committing to source control. Another option is to type: bash some_script_with_bin_bash_shebang.shto force the use of the newly defaulted bash 4 shell.

I only ever need to do this for Bash 4+ functionality, which is very common now.

Question

Does anyone have a cool technique, workarounds to alias a shebang bash path like so: #!/bin/bash to the newer bash 4 shell at /usr/local/bin/bash?

Without obviously modifying the file! ^_^ (e.g. adding #!/usr/bin/env bash)

3 Answers 3

7

This is the main reason nowadays for using a different shebang:

#!/usr/bin/env bash

This will use the first bash in the PATH, which will most likely be the latest version. Since /bin is in the standard path (getconf PATH), this falls back gracefully for those without a custom bash.

4
  • 1
    I don't think you've actually read my question? I already know this, however this doesn't answer my question at all. Is there something I should do to make this clearer? What I don't know is the possibility of what I asked lol, I understand you're trying to get the easy points, but I did say without altering the file. To avoid these kinds of responses.
    – RST
    Jan 16, 2018 at 1:37
  • 1
    @RST I read your question but it seems like an XY problem and I was trying to steer it back on track. For your own scripts using ‘Bash 4+ functionality’, use env bash, and for scripts you haven't written, leave them alone! If I write a script for distributing to Mac and use /bin/bash, it's because I want to use the system bash, and using that shebang ensures that happens. If I want to use the latest version of bash available, then env bash is superior, but that doesn't mean I should force other scripts I haven't written to use what I think is best.
    – grg
    Jan 16, 2018 at 10:08
  • 1
    Simply as some users are on Linux and others on Mac, with 99% of the Linux users having latest bash for years and having included the env based approach. Essentially, yes the best case would be to go and change them all, that's not a possibility right now. That's is why I said once again without modifying the file out of interest. I don't necessarily agree with everything you're saying, but as is life hah.
    – RST
    Jan 16, 2018 at 10:54
  • @RST Taken literally, your question is "why do scripts that are told to open the file /bin/bash opening the file /bin/bash and not a completely different file /usr/local/bin/bash? The logically correct answer is "because that's what the script was told to do, you should edit the script if you want it to do something else." Mar 20, 2021 at 4:10
4

Firstly, you could consider simply replacing /bin/bash with a more uptodate version.

Secondly, if you're not comfortable voiding your warranty doing that, and where you already have a problem with an existing script, bypass its #! line by invoking it as:

/path/to/bash4/path/to/script.bashargs...

(You can just use bash rather than its full path if your $PATH is in the right order.)

Thirdly, if this script is specific to Apple devices and must have Bash version 4, then always set it explicitly to #!/path/to/bash4, as there's no benefit to setting it to anything else. (This does not mean changing it in your source repo, but rather choosing it at installation time; see below.)

Fourthly, consider using https://gist.github.com/kurahaupo/8130030 - note that you need the "source" version of the script to be outside the intended bin directory; eg, leave it in your source repo, or in a temporary directory (which can be deleted after this "installation").

Fifthly, if you really don't want to touch the original script, make a stub script that invokes it:

#!/bin/sh
exec /path/to/bash4 /path/where/you/stashed/the/original/script "$@"

and install that in a suitable bin directory.

Source vs Deploy

Your query about setting the #! line in the source repo highlights an extremely prevalent misapprehension about scripts: the idea that a script, simply because it is "text", should not need to be "prepared for use" in any sense.

For other types of programs it's obvious: they have to be compiled. And if they're large python or perl scripts, they need a package or installer that will include any module dependencies.

But even a shell script needs, at minimum, to have its ownership and permissions set, and to be placed into a bin directory. Typically this would be either when a package is built for a class of device, or when the script is installed on a target device. This is also the appropriate time to set the #! line to match the deployment environment.

I do not recommend #!/usr/bin/env bash

The widespread promotion of #!/usr/bin/env slightly weakens overall security, but in a way that's not likely to bite the people who write it, only the users somewhere down the track. Therefore I do not recommend it; indeed I predict that eventually it's going to come back and bite your clients, or your clients' clients, or your clients' clients' ... clients.

It's promoted as "portable", but that's not actually true: Android and OpenWRT devices do not even have /usr much less /usr/bin/env, so it's quite useless there. So in practice the main beneficiaries are Apple users.

If the script requires a new version of bash, then the system version simply won't do, so it's pointless using it as a fallback. But if you don't need the latest version, then having it fixed as /bin/bash is fine.

In short, if you're deploying a script to Apple that requires Bash version 4, then you should use #!/path/to/bash4, either when you build the script into an installable package, or when the script is installed on a particular device.

EDIT

I replaced /usr/local/bin/bash with /path/to/bash4 because that was causing some confusion. The point isn't that /usr/local/bin/bash must be used, but rather that the full path to bash4, whatever it is, should be specified explicitly. If you don't know it before installation, looking in $PATH then is a reasonable thing to do, but you'd want to run a short test like

bash_path=$( type -p bash )
if ! "$bash_path" -c '[[ $BASH_VERSION > 4.2 ]]'
then
    echo "ERROR: $bash_path isn't new enough"
    exit 1
fi
{
  printf '#!%s\n' "$bash_path"
  tail -n+2 "$downloaded_file"
} > "$installpath"
chmod a=rx "$installpath"
3
  • However bash is not always installed in /usr/local/bin it depends on your packaging system - which is why the env way is useful
    – mmmmmm
    Mar 13, 2018 at 9:43
  • It seems that replacing /bin/bash is explicitly blocked by the OS: sudo mv -vf /bin/bash /bin/bash_old results in mv: cannot move '/bin/bash' to '/bin/bash_old': Operation not permitted Feb 8, 2019 at 13:57
  • +1 for pointing out that using shell scripts on platforms with different standards requires deployment as any other application.
    – nohillside
    Nov 15, 2020 at 10:21
0

My workaround is simply to call the interpreter directly.

So for example, instead of calling something like:

./foo.sh

I instead call it with:

bash ./foo.sh

The first form (calling './foo.sh') exercises the interpreter defined in the shebang line, giving you (unwanted) bash 3.x on OSX (at least as of OSX Big Sur). However, the second form ignores the shebang line, calling the version of bash you have in your PATH. If your PATH has '/usr/local/bin' (or wherever you install with brew) occurring before '/bin', you'll execute your script with the desired bash version regardless of what's on the shebang line. Thus, you can run your script with goodies needing bash 4.x+ (like associative arrays).

This achieves the goals of not having to change the script at all, while still executing with the version of bash you want. As of this writing you can get bash 5.x with brew, whereas OSX 11.2 "Big Sur" still ships with /bin/bash being v3.x.

This methodology fits best when you develop for platforms where '/bin/bash' is reliably a version you want, but you develop on OSX where '/bin/bash' is 3.x. That way you can develop and test with the intended target version of bash without modifying the script's shebang line in ways that make it less reliable on target platforms. For bash in particular, I also prefer not using the #!/usr/bin/env trick, as that added complexity for users to deal with and can add complexity to supporting it.

I understand the point raised about Source vs. Deployment, and that there is no absolute requirement not to allow deployment changes to shell scripts. However, being "allowed" to change the shebang lines in version control and having that cause required changes in deployment processes can be a big deal, and might not be something you have any control over. Having shell scripts that require a deployment process that modifies the checksum of the original code reduces one of the core "simplicity" advantages of using bash shell scripts. If scripts are deployed as part of some larger product with ability to ensure complex installation steps are handled reliably, then I wouldn't object to it. But if scripts are the product, there's much benefit in limiting deployment complexity to "untar from a tarball" (with a prescribed command that handles the chmod +x and placement/deployment location).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .