Using custom version of Bash inside nix-shell

I want to use a specific version of Bash in my shell.nix to aid in consistent shell environment to execute some Bash scripts. But using, say, bashInteractive_5 in my shell.nix’s buildInputs[] does not help.

It appears that nix-shell does install the requested version of package, but once inside the nix-shell, I still get Bash version 4, which is installed in my profile.

Is there an easy way to achieve this? Eventually I want to share the shell.nix with others in my team, so that their work also depends on the packages in the shell.nix.

Here’s my shell.nix:

with import <nixpkgs> {};

stdenv.mkDerivation {
  name = "some_name";
  buildInputs = [
    coreutils
    awscli
    bashInteractive_5
  ];
}
1 Like

It rather seems that something in the shell also adds bash interactive 4.4 rather than your system bash is promoted.

$ nix-shell ./shell.nix --run 'whereis bash'
bash: /nix/store/bfaqivmkqdiaaswa7lpgfjsf62cjkkb3-bash-interactive-4.4-p23/bin/bash /nix/store/1b6d7pijyc4majc3v1b4h7rd6av03ixs-bash-interactive-5.1-p4/bin/bash /nix/store/dskh7v2h3ly3kdkfk3xmjlqql1zr0hnw-bash-4.4-p23/bin/bash /nix/store/4dbjr2w5bj3wm6svsvd3d7ib2fxasclg-user-environment/bin/bash /nix/store/fjq3djxpm41rnank6wq6v5zp403n3iqs-system-path/bin/bash

@Mic92, sorry I’m not sure what you’re implying! Here’s the output of that command in my macOS environment.

$ nix-shell ./shell.nix --run 'whereis bash'
/bin/bash

Mhm. Not sure what happening on your machine. This is unexpected.

Thanks for pitching in to help!

Not sure what happening on your machine.

This is ironic! :slight_smile: This is the exact problem Nix is supposed to be solving. “Works on my machine” should not be in the vocabulary of the Nix users. From NixOS explore website:

You will no longer have to hear: But it works on my machine!

I guess nix-shell is an oddity rather than the norm.

I also find it troubling that nix-shell sources user’s ~/.bashrc and host machine’s /etc/bashrc, even in --pure mode. from nix-shell --help:

–pure … Note that ~/.bashrc and (depending on your Bash installation) /etc/bashrc are still sourced, so any variables set there will affect the interactive shell.

This behaviour is sure to cause “works on my machine” syndrome despite using Nix package manager.

I guess nix-shell is the wrong tool to do what I am trying to do. I would love to hear if there’s an alternative that works better than nix-shell, with respect to providing a clean, consistent, and reproducible development environment.

1 Like

What does your PATH contains in your case?
I rarely have reproducibility issues with it and I still think it is still the best tool for this job.

From https://nixos.org/manual/nix/stable/#environment-variables

Environment variables

NIX_BUILD_SHELL

Shell used to start the interactive environment. Defaults to the bash found in PATH .

@gurjeet You are probably overriding PATH in your bashrc (or other environment file).

@Mic92 If you want to use Bash 5 you have to run it like this

$ nix-shell ./shell.nix --run "which bash"
/nix/store/68sm67lcd4pnmyhijpyh134a7ykgyjhq-bash-interactive-4.4-p23/bin/bash
$ NIX_BUILD_SHELL=bash nix-shell ./shell.nix --run "which bash"
/nix/store/12a1inlsm4hn5yjx815ajdwfvhp179b5-bash-interactive-5.0-p17/bin/bash

because if NIX_BUILD_SHELL is unset, Nix will load the bashInteractive derivation for you and use it as the shell.

4 Likes

As I understand it, nix-shell is a tool built for working on Nix packages–not a tool built to create a hermetic bash5 execution environment for your scripts. For the same reasons it is useful for working on packages, it happens to be decent at building dev environments.

Because the tool is built to provide an interactive environment for humans to work on Nix packages (and not for you to run your bash5 scripts), it has helpful defaults like:

  • using the same bash that builds will use
  • providing a human-friendly version of the build environment
  • continuity with the user’s local development environment

It may or may not be a helpful way to run your scripts–but, like most tools, you may have to do a little extra work to use them for things they weren’t built for.

There isn’t enough detail here to give you strong recommendations, but some approaches that you might find fruitful:

  • If you really want newer bash everywhere, you can override it. I have something like bashInteractive = super.bashInteractive_5; and it hasn’t caused me much trouble that I can recall.
  • Using nix-shell as a shebang enables you to specify an interpreter and packages, but keep the script itself somewhere in ~/
  • Use shell.nix as you’re doing, but use the env to inject the bash you want, and the shellHook to assert whatever control you need over the environment inside.
  • “build” the scripts as a package and put them in the store, with the specific bash you want as their shebang, and either add them to your system/user packages, or add them to your shell.nix.
2 Likes

With flakes-enabled Nix you can get something that almost rembles a hermetic bash5 execution environment

[user@nixos]$ nix shell --ignore-environment --store /tmp/nix nixpkgs#hello nixpkgs#bashInteractive_5 -c bash --norc --noprofile
bash-5.0$ hello
Hello, world

Whether the resulting shell is useful is another question.

2 Likes

By the way GitHub - numtide/devshell: Per project developer environments is solving your problem:

[devshell]$ cat devshell.toml
[devshell]
packages = [
  "bash_5"
]
4 Likes

So I looked at the code of nix-shell and tried to reverse-engineer it and see how I could make it behave as I intend to use it. And the following is what I came up with. The hack uses nix’s feature shellHook and an additional file named rm; both listed below.

$ echo $BASH_VERSION
4.4.23(1)-release

$ RMPATH="$(dirname "$(realpath "$(which rm)")")" \
  PATH=  PS1= "$(which nix-shell)"

$ echo $BASH_VERSION
5.1.4(1)-release

$ which bash
/nix/store/v6cfvgrrw37hq0hbqjfk8ndn3ngig3nh-bash-interactive-5.1-p4/bin/bash
# cat rm
"$RMPATH/rm" "$@"
# cat shell.nix
with import <nixpkgs> {};

stdenv.mkDerivation {
  name = "some_name";
  buildInputs = [
    which
    bashInteractive_5
  ];

  shellHook = ''
# Strip the first element from $PATH. This component was prepended by nix-shell
# to make sure the bash that it prefers gets used inside nix-shell environment
# as well.
export PATH="$(echo "$PATH" | cut -d : -f 2-)"

# Now invoke first bash found in $PATH. Use --norc to prevent invoking user's ~/.bashrc
exec env PS1='$ ' bash --norc

# Any code after the exec command above will not be executed
  '';
}
2 Likes

This doesn’t seem to be the case anymore (basing this on commit 65f6d5d “Don’t source bashrc in pure mode” on 10/9/2019) but it seems that the manual hasn’t caught up yet…

(I would gladly open an issue and eventually doing PR but the NixOS/nix repo already has 1300+ open issues and not sure if I’m correct.)

@toraritte, I seem to be using the latest version of nix-shell, but my ~/.bashrc is still being invoked. So I did some digging and found that it’s because /etc/bashrc is being invoked, and that in turn, at least on my macOS, invokes my user-bashrc.

$ nix-shell --version
nix-shell (Nix) 2.3.11

$ nix-shell --pure
Inside /etc/bashrc
Inside /Users/gurjeet/.bashrc
Inside /etc/bashrc

I presume nix-shell uses --norc flag on the bash command to ensure purity, but that does not prevent it from invoking system-wide bashrc located at /etc.

Note that this bahavior of Bash is expected, since the man page of bash says this.

       --norc Do not read and execute the personal initialization file ~/.bashrc if the shell is interactive.  This option is on
              by default if the shell is invoked as sh.
1 Like

@toraritte, I dug a bit further and it seems like that that commit you mention has made it to the master branch, but has never been released to public; none of the Git tags in the Nix repo. contain that commit.

$ git tag --contains 65f6d5db6f5ef2724a3dc03e1773c510123287f1
<empty response>

The last release from master, 2.3, was released on Sept 4, 2019 in 22d4ea7a989d26b86fc27706dfea0abd2fb52c52. Since we’re still in the 2.3.x series, there’s quite a lot of unreleased work in master.

You may be able to use the nixUnstable instead (assuming nothing else it includes is unacceptable…)

@abathur, the nix-shell I’m using is version 2.3.11, tagged on 2021/05/14, so I guess I’m using unstable already!? If that’s not the case, can you please explain how one would go about using nixUnstable; I’m using Nix on macOS.

$ nix-shell --quiet -p nix-info --run "nix-info -m"
 - system: `"x86_64-darwin"`
 - host os: `Darwin 18.7.0, macOS 10.14.6`
 - multi-user?: `yes`
 - sandbox: `no`
 - version: `nix-env (Nix) 2.3.11`
 - channels(gurjeet): `""`
 - channels(root): `"nixpkgs-21.11pre299841.357d2c8f608"`
 - nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixpkgs`

Note: I’m speaking generally and am not intimately familiar with Nix’s maintenance/release practices.

No, you’re using a maintenance release in the 2.3.x (stable) release series, which branched off from master in Sept. 2019. The 2.3.x series continues to get some fixes while work towards the next stable release series continues in master. (2.3.x may continue to receive fixes even after the next stable release branches off from master).

It is grossly-simplified, but maybe a graph makes this clearer:

In nixpkgs, the nix attr is ~roughly the latest stable Nix release, but there is also a nixUnstable attr which is occasionally updated to a recent unreleased commit in the master branch.

I’m not really sure (I haven’t looked into doing this myself) but it’ll depend on how you’re managing your user/system packages in the first place (i.e., invoking nix-env, or nix-darwin/home-manager). It’s probably discussed in most getting-started-with-nix-flakes docs/tutorials. You may also be able to find good examples with a github code search.