155 lines
5.7 KiB
YAML
155 lines
5.7 KiB
YAML
uuid:
|
|
- value: 3825516e-0d22-4770-9a2a-73dacd086d59
|
|
langcode:
|
|
- value: en
|
|
type:
|
|
- target_id: daily_email
|
|
target_type: node_type
|
|
target_uuid: 8bde1f2f-eef9-4f2d-ae9c-96921f8193d7
|
|
revision_timestamp:
|
|
- value: '2025-07-11T19:48:56+00:00'
|
|
revision_uid:
|
|
- target_type: user
|
|
target_uuid: b8966985-d4b2-42a7-a319-2e94ccfbb849
|
|
revision_log: { }
|
|
status:
|
|
- value: true
|
|
uid:
|
|
- target_type: user
|
|
target_uuid: b8966985-d4b2-42a7-a319-2e94ccfbb849
|
|
title:
|
|
- value: 'Writing robust bash scripts with Nix'
|
|
created:
|
|
- value: '2025-07-08T19:45:04+00:00'
|
|
changed:
|
|
- value: '2025-07-11T19:48:56+00:00'
|
|
promote:
|
|
- value: false
|
|
sticky:
|
|
- value: false
|
|
default_langcode:
|
|
- value: true
|
|
revision_translation_affected:
|
|
- value: true
|
|
path:
|
|
- alias: /daily/2025/07/08/writing-robust-bash-scripts-nix
|
|
langcode: en
|
|
body:
|
|
- value: |-
|
|
I have a lot of custom Bash scripts I use - some to perform simple commands like `git log` with some additional arguments, to more complex ones that mount and unmount USB devices plugged into my laptop.
|
|
|
|
A lot of people will post their scripts online along with their dotfiles for others to read and take inspiration from.
|
|
|
|
Mine are in my [nix config][0] directory and each script is added as a custom package I can install.
|
|
|
|
Here's an example of a script written as a Nix package:
|
|
|
|
```nix
|
|
{ pkgs }:
|
|
|
|
pkgs.writeShellApplication {
|
|
name = "get-tags";
|
|
|
|
runtimeInputs = with pkgs; [ git ];
|
|
|
|
text = ''
|
|
if [[ "$#" -gt 0 ]]; then
|
|
git tag | grep "$*"
|
|
exit 0
|
|
fi
|
|
|
|
git tag
|
|
'';
|
|
}
|
|
```
|
|
|
|
It gets a filtered list of Git tags within a repository using the `git tag` and `grep` commands.
|
|
|
|
Nix automatically adds the shebang line and sets some default shell options, so those aren't included within the script text.
|
|
|
|
It also automatically runs `shellcheck` to ensure the script is correct.
|
|
|
|
## Injecting Dependencies
|
|
|
|
This script depends on `git`. Without it, it would not run successfully.
|
|
|
|
Instead of assuming it is installed, `runtimeInputs` ensures any dependencies are present and the script can be executed, even if the dependencies aren't enabled globally.
|
|
|
|
Scripts can also rely on other scripts.
|
|
|
|
`get-tags` has a corresponding `count-tags` script that counts the returned tags:
|
|
|
|
```nix
|
|
pkgs.writeShellApplication {
|
|
name = "count-tags";
|
|
|
|
runtimeInputs = with pkgs; [
|
|
coreutils
|
|
get-tags
|
|
];
|
|
|
|
text = ''
|
|
get-tags "''${1:-}" | wc -l
|
|
'';
|
|
}
|
|
```
|
|
|
|
`get-tags` is declared as a dependency, as well as `coreutils` to add the `wc` command that counts the lines.
|
|
|
|
## Here's the thing
|
|
|
|
Using this approach gives me more robust scripts that are checked and verified, and will always have the required dependencies.
|
|
|
|
And, because they are added as custom Nix packages in my configuration, I am able to decide which scripts to install on which computer, giving me the most flexibility.
|
|
|
|
[0]: https://code.oliverdavies.uk/opdavies/nix-config
|
|
format: markdown
|
|
processed: |
|
|
<p>I have a lot of custom Bash scripts I use - some to perform simple commands like <code>git log</code> with some additional arguments, to more complex ones that mount and unmount USB devices plugged into my laptop.</p>
|
|
<p>A lot of people will post their scripts online along with their dotfiles for others to read and take inspiration from.</p>
|
|
<p>Mine are in my <a href="https://code.oliverdavies.uk/opdavies/nix-config">nix config</a> directory and each script is added as a custom package I can install.</p>
|
|
<p>Here's an example of a script written as a Nix package:</p>
|
|
<pre><code>{ pkgs }:
|
|
|
|
pkgs.writeShellApplication {
|
|
name = "get-tags";
|
|
|
|
runtimeInputs = with pkgs; [ git ];
|
|
|
|
text = ''
|
|
if [[ "$#" -gt 0 ]]; then
|
|
git tag | grep "$*"
|
|
exit 0
|
|
fi
|
|
|
|
git tag
|
|
'';
|
|
}
|
|
</code></pre><p>It gets a filtered list of Git tags within a repository using the <code>git tag</code> and <code>grep</code> commands.</p>
|
|
<p>Nix automatically adds the shebang line and sets some default shell options, so those aren't included within the script text.</p>
|
|
<p>It also automatically runs <code>shellcheck</code> to ensure the script is correct.</p>
|
|
<h2>Injecting Dependencies</h2>
|
|
<p>This script depends on <code>git</code>. Without it, it would not run successfully.</p>
|
|
<p>Instead of assuming it is installed, <code>runtimeInputs</code> ensures any dependencies are present and the script can be executed, even if the dependencies aren't enabled globally.</p>
|
|
<p>Scripts can also rely on other scripts.</p>
|
|
<p><code>get-tags</code> has a corresponding <code>count-tags</code> script that counts the returned tags:</p>
|
|
<pre><code>pkgs.writeShellApplication {
|
|
name = "count-tags";
|
|
|
|
runtimeInputs = with pkgs; [
|
|
coreutils
|
|
get-tags
|
|
];
|
|
|
|
text = ''
|
|
get-tags "''${1:-}" | wc -l
|
|
'';
|
|
}
|
|
</code></pre><p><code>get-tags</code> is declared as a dependency, as well as <code>coreutils</code> to add the <code>wc</code> command that counts the lines.</p>
|
|
<h2>Here's the thing</h2>
|
|
<p>Using this approach gives me more robust scripts that are checked and verified, and will always have the required dependencies.</p>
|
|
<p>And, because they are added as custom Nix packages in my configuration, I am able to decide which scripts to install on which computer, giving me the most flexibility.</p>
|
|
summary: ''
|
|
field_daily_email_cta:
|
|
- target_type: node
|
|
target_uuid: 20cde1b4-efdc-46a4-a6a4-4fd2264f617e
|