Comment by pxc

Comment by pxc 2 days ago

0 replies

> Same w/ the "pipe to read" (although it doesn't seem to work right with OSX's bash-3.2). I found this gizmo somewhere and it's worth sharing as well...

Yes, you need a recent Bash for this (and lots of other nice things like 'associative arrays' (maps)). To free myself up to write more concise and legible scripts, I lean into bashisms and make use of external programs without restriction, regardless of what's shipped by default on any system. To recover portability where I need it, I use Nix to fix my Bash interpreter versions as well as the versions of external tools.

When my scripts are 'packaged' by Nix, their bodies are written as multiline strings in Nix. In that case, Nix takes care of setting the shebang to a fixed version of Bash, and I interpolate external commands in like this:

  "${pkgs.coreutils}/bin/realpath" .      # these do
  "${pkgs.coreutils}/bin/pwd" --physical  # the same thing
and in that way my scripts use only their fixed dependencies at fixed versions without modifying the environment at all. This also works nicely across platforms, so when these scripts run on macOS they get the same versions of the GNU coreutils as they do when they run on Linux, just compiled for the appropriate platform and architecture. Same goes for different architectures. So this way your script runs 'natively' (no virtualization) but you still pin all its dependencies.

In other contexts (e.g., cloud-init), I use bash a bit more restrictively depending on what versions I'm targeting. But I still use Nix to provide dependencies so that my scripts use the same versions of external tools regardless of distro or platform:

  nix shell nixpkgs#toybox --command which which # these do
  nix run nixpkgs#which -- which                 # the same thing
`nix run` and `nix shell` both behave as desired in pipelines and subshells and all that. (To get the same level of determinism as with the method of use outlined earlier, you'd want to either pin the `nixpkgs` ref in your local flake registry or replace it with a reference that pinned the ref down to a commit hash.)

There are is a really cool tool[1] by the Nixer abathur for automagically converting naively-written scripts to ones that pin their deps via Nix, as in the first example. I'm not using it yet but I likely will if the scripts I use for our development environments at work get much bigger-- that way I can store them as normal scripts without any special escaping/templating and linters will know how to read them and all that.

Anyhow, it's totally safe to install a newer Bash on macOS, and I recommend doing it for personal interactive use and private scripts. Pkgsrc and MacPorts both have Bash 5.x, if you don't have or want Nix.

> I need to write up some thoughts on bash being effectively a lisp if you stare at it the right way.

You should! It's totally true, since external commands and shell builtins are in a prefix syntax just like Lisp function calls. Some shells accentuate this a bit, like Fish, where subshells are just parens with no dollar signs so nested subcommands look very Lisp-y, and Elvish, whose author (xiaq) has tried to lean into that syntactic convergence designing a new shell (drawing inspiration from Scheme in various places).

> I found this gizmo somewhere and it's worth sharing as well...

    # MAGIC DEBUGGING LINE
    #trap '(read -p "[$BASH_SOURCE:$LINENO] $BASH_COMMAND? ")' DEBUG
Okay that looks really nifty. I will definitely find a use for that literally tomorrow, if not today.

--

1: https://github.com/abathur/resholve