Advice for when to use nix run versus nix-shell?

I mostly use nix-shell to load temporary environments with packages I need for development. I also noticed nix run does similar things and the documentation reads similarly. That reminded me of Eelco’s NixOS 2017 Conference talk on Nix 1.12 where he described CLI interface improvements including nix run.
All this leads to my actual question(s):

  1. are they the same command? or intended to become the same one?
  2. if different, when do you use one over the other?

nix run is a replacement for nix-shell -p, not for nix-shell in general.

nix run is a little easier to use for doing one-shot commands due to how it takes the command and arguments (as separate args to itself, whereas nix-shell -p requires you to cram them into a single argument). For example, with nix run:

> nix run nixpkgs.hello -c hello --greeting 'Hi everybody!'
Hi everybody!


> nix-shell -p hello --run "hello --greeting 'Hi everybody!'"
Hi everybody!

Though there are some more practical differences, like nix-shell -p evaluates the command with a shell whereas nix run apparently doesn’t (nix run nixpkgs.hello -c echo \$PATH just echoes $PATH whereas nix-shell -p hello --run 'echo $PATH' will print the environment variable). Also the two commands set up $PATH differently for some reason, e.g.

> nix run nixpkgs.hello -c env | grep '^PATH'
PATH=/nix/store/sshmh79fir83xlzq9pi7qydgmzm69xxb-hello-2.10/bin:<inherited path>
> nix-shell -p hello --run env | grep '^PATH'
PATH=/nix/store/9sjgkbxaq8sibpavaalkmm5scbgl60na-bash-interactive-4.4-p23/bin:/nix/store/qvagarl5ghs07v39zhszm5x9rn08k231-clang-wrapper-7.1.0/bin:/nix/store/i2nf6hslid0ak8fzgpj2m082sprihi92-clang-7.1.0/bin:/nix/store/6964byz5cbs03s8zckqn72i6zq12ipqv-coreutils-8.31/bin:/nix/store/nxw7kw4px494nxw0cz5f1zfw3rvm645s-cctools-binutils-darwin-wrapper/bin:/nix/store/2pj2hdfy8jqy41hbh5h6z7sqhmcpi7xy-cctools-binutils-darwin/bin:/nix/store/6964byz5cbs03s8zckqn72i6zq12ipqv-coreutils-8.31/bin:/nix/store/sshmh79fir83xlzq9pi7qydgmzm69xxb-hello-2.10/bin:/nix/store/6964byz5cbs03s8zckqn72i6zq12ipqv-coreutils-8.31/bin:/nix/store/zvb6vxqx5f3y89mgg4can85g5fwn8xjn-findutils-4.6.0/bin:/nix/store/fa4psi2mhr14kil59vbs5vx7b791x55x-diffutils-3.7/bin:/nix/store/1qkqjb02khxr13q8hhwicznz5zsvjvzv-gnused-4.7/bin:/nix/store/bzr287y1sy2qsrmywwkgxlkblz7vx61w-gnugrep-3.3/bin:/nix/store/4mjyhc65kdri28g59gpnjvimjkb44vpy-gawk-4.2.1/bin:/nix/store/c52xr0xha8m6752wr8s3h03ggjajwq5l-gnutar-1.32/bin:/nix/store/r1rvdsxgrmjpcf0k5fd9bsjg807m1grm-gzip-1.10/bin:/nix/store/aas637k5pwjp26mzm2la15n0dl11pl6j-bzip2-<inherited path>

Which is to say, nix-shell adds all of the dependencies to the path too, whereas nix run only adds the output package.


I’ve tried to make shellHook work for nix run. Here’s what I’ve got:

  mkShell2 = { shellHook, buildInputs ? [] }@args:
    let shell = pkgs.writeScriptBin "bash" ''
          exec ${pkgs.bashInteractive}/bin/bash --rcfile ${pkgs.writeText "shell-hook" ''
            export IN_NIX_SHELL=1
            export PS1="\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] "
            export PATH="${lib.makeBinPath buildInputs}:$PATH"
          ''} "$@"
    in (pkgs.buildEnv {
      name = "shell";
      ignoreCollisions = true;
      paths = [ shell ] ++ buildInputs;
      inherit buildInputs;
    }).overrideAttrs (_: {
      inherit shellHook;

It is kinda compatible with both nix run and nix-shell, in sense that for following file:

let pkgs = ...
    mkShell2 = ...
in {
  shell = mkShell2 {
    buildInputs = [ pkgs.fortune ];
    shellHook = ''
      echo IN HOOK

we get following results:

$ nix-shell shell.nix -A shell --command "fortune -s"
Old Mother Hubbard lived in a shoe,
She had so many children,
She didn't know what to do.
So she moved to Atlanta.

$ nix run -f shell.nix shell --command fortune -s
You have the power to influence all with whom you come in contact.

$ nix-shell shell.nix -A shell

[nix-shell:~]$ which fortune

[nix-shell:~]$ exit

$ nix run -f shell.nix shell

[nix-shell:~]$ which fortune

$ exit

Out of extra goodies, nix run version is faster than nix-shell:

$ time nix run -f shell.nix shell -c bash -ic ""

real    0m0,395s
user    0m0,303s
sys     0m0,058s

$ time nix-shell shell.nix -A shell --run 'exit'

real    0m0,593s
user    0m0,461s
sys     0m0,080s

but this may be also because my version is simplified a lot.

cc @zimbatm who added original mkShell


nix run evaluates nix expressions, then just populates $PATH (src: nix/
nix-shell evaluates nix expression, then launches bash shell that sources stdenv’s (src: is not a fast thing, thus nix run should be a little faster that nix-shell. On the other hand, is required to set up a lot of hooks and environment variables other than $PATH.

E.g. the following example relies on $PKG_CONFIG_PATH environment variable:

$ nix run nixpkgs.pkgconfig nixpkgs.gtkmm3 -c pkg-config --cflags --libs gtkmm-3.0
Package gtkmm-3.0 was not found in the pkg-config search path.

$ nix-shell -p pkgconfig gtkmm3 --run 'pkg-config --cflags --libs gtkmm-3.0'
-I/nix/store/h5vig4g60xwy553mpra16p8dnl95qv9b-gtkmm-3.24.2-dev/include/gtkmm-3.0 …

@danbst I have something a bit similar called “mkProfile”. It goes a bit further to allow to create a custom user shells for both interactive and non-interactive environments (and source from direnv). It’s still very much a work in progress and I am tweaking it as my usage evolves.


Does anyone know why nix run -c was removed/moved to nix shell?

It doesn’t seem logical that for a one-off command, I should use nix shell -c, in my opinion.

nix run knows which command to run for an application, nix shell -c can still be used to run arbitrary commands in the context of a throwaway environment.