writeShellApplication not running bash script as you would expect

I’m trying to include all/most of my scripts(bash, python) in my nix config. During the conversion of the second script I’m running into a weird situation.
The script runs fine using ./genpasswd.sh

11
22
Ngs?6</sPtG<%oLC(R2XU7V!LXK^EsEA2b5(!AN1D%Q4?pYks<c|c2VBWO!IyJ8AQ#j*fl_*#gCNz{P^RdB[3%LHC-,ow(^*-Igd

It doesn’t give the output I expect when running through nix using genpasswd

11

I have no idea why this happens. It passes shellcheck, and it builds through nix

bash script genpasswd.sh :

#!/usr/bin/env bash

DEFAULT_LENGTH=100
DEFAULT_ALLOWED_CHARACTERS="A-Za-z0-9#$%^_(){}[]<>|&\-*+/=.,:!?"

genpasswd() {
  local l=${1:-$DEFAULT_LENGTH}
  characters=${2:-$DEFAULT_ALLOWED_CHARACTERS}
  echo 11
  pass="$(tr -dc "$characters" < /dev/urandom | head -c "$l" | xargs)"
  echo 22
  echo "$pass"  # print
  echo "$pass" | wl-copy --paste-once --trim-newline  # clipboard
  return 0
}

_main() {
  genpasswd "${@}"
}

_main "${@}"
environment.systemPackages = with pkgs; [
    (pkgs.writeShellApplication { 
      name = "genpasswd";
      runtimeInputs = with pkgs; [ coreutils wl-clipboard findutils ];
      text = builtins.readFile ../../scripts/genpasswd.sh;
    })
];

So if you take a look at the script created by writeShellApplication, it uses:

set -o errexit
set -o nounset
set -o pipefail

So the script will exit if any pipe fails. Since head closes the pipe before tr could read everything from /dev/urandom, tr will return exit status 141 and the script exits.

You can either disable pipefail for that line (set +o pipefail) or add a || true.
I’d probably write it like this:

pass="$(tr -dc "$characters" < /dev/urandom | head -c "$l" || true; echo)"
2 Likes