How to import a local script in $PATH when using nix-shell

Hi! Let me share with you a simple use case that got me headaches.

I have a project with a bin/ subdirectory in which there are some shell scripts, the project has a shell.nix that loads many packages and I wanted to add bin/myscript to the $PATH so users could run myscript from your shell while working on the project.

Here is my shell.nix file stripped down with the necessary bits remaining:

{ pkgs ? import <nixpkgs> {} }:

let
  myscript-local = pkgs.writeShellScriptBin "myscript" (builtins.readFile ./bin/myscript);
in
with pkgs;
mkShell {
  packages = [ myscript-local ];
}

As the script has many lines of code, I didn’t want to write it verbatim in shell.nix because it’s not practical for editing and testing. I needed to import the local file into writeShellScriptBin, this was possible thanks to builtins.readFile.

Note the ./ in ./bin/myscript path, this is a syntax to turn the relative path into an absolute path, required for nix-shell, using bin/myscript would return an error like “nix-shell needs absolute paths”. There was another trap here, paths are not string, so they shouldn’t be quoted.

7 Likes

More ideas for similar circumstances:

  • If you have multiple scripts and you know all of their dependencies will be on the PATH in the shell, you can use the shellHook to append $PWD/bin to the PATH.
  • If you don’t already know all of the dependencies in the scripts (perhaps they depend on utilities that aren’t otherwise project dependencies), resholve (nixpkgs README, project) can help ensure they’re all present.
2 Likes

Just yesterday learned that in flake.nix, local repo files such as this can be referenced by absolute path as "${self}/bin/myscript}". Here ${self} is replaced with the relevant store path.

1 Like