Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding tutorial 'Moving a bash script into its own file' #317

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ building-bootable-iso-image.rst
deploying-nixos-using-terraform.rst
installing-nixos-on-a-raspberry-pi.rst
integration-testing-using-virtual-machines.rst
wrap-and-call-a-bash-script.rst
cross-compilation.rst
contributing.rst
```
109 changes: 109 additions & 0 deletions source/tutorials/wrap-and-call-a-bash-script.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
(wrap-and-call-bash-script)=

# Wrapping a bash script and making it executable

It is not uncommon to consume bash scripts inline. If long enough, however, one may have to consume it from an outside file.

This tutorial goes through the steps required to wrap a bash script, making it executable, and ultimately run it from a derivation or developer shell.

## The problem

Let's say we have a bash script called `countdown.sh` that prints natural numbers from 10 down to 0 before running the `hello` package:

```bash
#!/bin/bash

valid=true
count=10
while [ $valid ]
do
sleep 1
echo $count
if [ $count -eq 0 ];
then
hello
break
fi
((count--))
done
```

## Read it, shebang out, wrap it

Create an input called `wrap-script`, adding the builtin function `readFile` and a `patchShebangs` hook. We will also need a [`writeShellScriptBin`](https://nixos.org/manual/nixpkgs/stable/#trivial-builder-writeText) trivial builder, passing the arguments `name` and `text`:

```nix
read = rec {
name = "script";
text = builtins.readFile ./countdown.sh;
script = (pkgs.writeShellScriptBin name text).overrideAttrs(old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
};
```

## Symlinking it

Next thing, create a derivation that we can call elsewhere. We will do that with [`symlinkJoin`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/trivial-builders.nix#L388). All we need to do is to add the script wrapper declared above to the `paths` argument. We will also add the `hello` package:

```nix
symlink = with pkgs; pkgs.symlinkJoin {
name = "script";
paths = [
read.script
pkgs.hello
];
};
```

## Call the executable from a developer shell

In a developer shell definition, export the path to the `hello` package and call the executable inside a `shellHook`:

```nix
devShells.x86_64-linux = {
default = pkgs.mkShell {
buildInputs = with pkgs; [
wrapped
];
shellHook = ''
PATH=${pkgs.hello}/bin:$PATH
${symlink}/bin/script
'';
};
};
```

## Call the executable from a derivation

Again, export the path to the `hello` package and call the executable with a `writeShellScriptBin` trivial builder:

```nix
wrapped = pkgs.writeShellScriptBin "script" ''
PATH=${pkgs.hello}/bin:$PATH
exec ${symlink}/bin/script
'';
```

Finally, call it from the default derivation:

```nix
packages.x86_64-linux = {
default = wrapped;
};
```

## Simplifying it

Since the `writeShellApplication` trivial builder can write our script to the store making it executable and do the symlinking for us, all in one stroke, we can get rid of `writeShellScriptBin` and `symlinkJoin`. [`writeShellApplication`](https://nixos.org/manual/nixpkgs/stable/#trivial-builder-writeShellApplication) accepts three arguments, `name`, `text`, and `runtimeInputs`. The `text` argument will accept our bash script through `readFile`. We can drop the `patchShebangs` hook too since `writeShellApplication` will remove the shebangs. And since we want to run the package `hello` after the countdown script, all we have to do is plug it in the `runtimeInputs` argument. It will export the path for us, which is quite convenient. Here is the full expression:

```nix
wrapped = pkgs.writeShellApplication {
name = "wrapped-script";
runtimeInputs = with pkgs; [
hello
];
text = builtins.readFile ./count-five.sh;
};
```
The full flake with this example can be found [here](https://github.com/nrdsp/nix-examples/tree/main/hello-example).