How to create a script with dependencies?

I know I can create a script using writeShellScript or writeShellScriptBin and if I inline it then I can also include it’s dependencies, for example:

my-script = writeShellScriptBin "my-cool-script" ''
  ${pkgs.some-tool} --some-flag
'';

But my script has grown quite big, so I’d like to move it to it’s own separate file, like so:

my-script = ./my-script.sh;

But the problem with that is, that it cannot inline my dependency on pkgs.some-tool.

How can I create a script from a separate file and include its dependencies? Something like:

my-script = writeShellScriptWithDeps {
  name = "my-cool-script";
  inputs = [ pkgs.some-tool ];
  script = ./my-script.sh;
}
3 Likes

If you can live with basically “breaking” the script for non-Nix use (i.e., all you want here is to get the script out of your .nix file), the traditional way to get this is to use the @variable@ syntax supported by the substituteAll shell functions.

Edit: resholve (described below) is now in nixpkgs. Provisional docs for the Nix API are at https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/misc/resholve/README.md.

If the breaking tokens for substitution aren’t acceptable, I’m also working on a project, resholve, that can do this without them. A few caveats:

  • It isn’t in nixpkgs yet. Personally, I add in with an overlay and use it as if it were.
  • Learning is all still very self-serve from tests/demos rather than documentation.
  • I’m not sure the API is stable yet; if you keep using it, there’s a decent chance you’ll have to rename some options at some point.
  • It attempts to resolve what we could call everyday bash–there’s certainly valid bash that it can’t handle. If your script does anything wild, it may not… :slight_smile:

I have an example of how to use the Nix API in https://github.com/abathur/resholved/blob/7b9d5021e64a051cc6b43f95f6e6bd25cfec071b/ci.nix where it composes some interdependent shell scripts together. There’s also an attempt to explain the demo in the README.

1 Like

Here’s an example of using substituteInPlace (similar to substituteAll) if you’re curious:

nix expression: https://github.com/jerith666/elbum/blob/8bf53d2538f829617f53c50052f568a456de8384/default.nix
shell script: https://github.com/jerith666/elbum/blob/8bf53d2538f829617f53c50052f568a456de8384/src/elbum/elbum

1 Like

The substituteInPlace approach is interesting. I bet this could be generalized so you wouldn’t have to specify each variable by hand. Thanks @Jerith, I’ll see if I can make somthing more generic out of that.

@abathur that looks like way overkill for me. But interesting non-the-less.

substituteAll and substituteAllInPlace both already do this.

1 Like

I recently wrote a wrapper to package shell scripts like this screenshot taker. My objective is to be able to use the script both directly executing it (since it uses nix-shell as shebang) and from a proper Nix derivation, that will be faster.

Here is how I packaged it:

with import <nixpkgs> { };
let
  mkScript = { name, version, file, env ? [ ] }:
    writeTextFile {
      name = "${name}-${version}";
      executable = true;
      destination = "/bin/${name}";
      text = ''
        for i in ${lib.concatStringsSep " " env}; do
          export PATH="$i/bin:$PATH"
        done

        exec ${bash}/bin/bash ${file} $@
      '';
    };

in mkScript {
  name = "screenshot";
  version = "0.1";
  file = ~/bin/screenshot.sh;
  env = with self; [ bash maim xdotool dmenu xclip ];
}

The mkScript function builds a wapper script that adds the indicated dependencies to the PATH, and then calls the original script. This way you don’t need to use substituteInPlace or similar hacks. You just execute the commands as you would do in any other distro.

This should work for your script too just by changing the dependencies and the path to the script.

3 Likes

@abathur Oh, you’re right, as the docs state:

Replaces every occurrence of @varName @, where varName is any environment variable, in infile , writing the result to outfile .

Neat. Thanks for pointing that out.

@cript0nauta this is exactly the kind of thing I had in mind. A simple way to make a script that works without having to include things like @varName that would break the script normally. Thanks!

1 Like

Actually, there’s an even simpler solution to this, which is the substituteAll package:

Which can be seen used here:

EDIT: One small note, this method doesn’t work with names that contains a dash(-).

2 Likes