A shell script and a Nix expression in the same file

let _=''1

# shell script starts here

echo "I am a shell script"
echo "followed by a Nix expression."

exit
''; in

# nix expression starts here

{
	This = "is a Nix expression";
	after = "a shell script.";
}
$ nix eval --file default.nix
{ This = "is a Nix expression"; after = "a shell script."; }
$ bash default.nix
I am a shell script
followed by a Nix expression.

One caveat is that it would be painful if you want to use ${...} in the script though. If the string interpolation ${...} in bash does not have special characters, you can use unquoted ''${...}.


Update: With this version, you can have variables that are both accessible from Bash and from Nix, and there is a variable that you can refer to in Bash to get the contents of the Nix expression, and also a variable that you can refer to in Nix to get the contents of the Bash script.

#!/usr/bin/env bash
let _=0;

commonVariable="I can be accessed in both the shell script and the Nix expression";

__='';nixExpression="$(cat <<'NIX_EXPRESSION' | sed '1d;$d'
'';finalAttrs=

{
	This = "is a Nix expression";
	inherit commonVariable bashScript;
}

;___=''
NIX_EXPRESSION
)";_='';bashScript=''

echo "I am a shell script followed by a Nix expression."
echo "The common variable is: $commonVariable"
echo "The Nix expression is: $nixExpression"

exit
'';in finalAttrs
$ ./default.nix
I am a shell script followed by a Nix expression.
The common variable is: I can be accessed in both the shell script and the Nix expression
The Nix expression is: 
{
        This = "is a Nix expression";
        inherit commonVariable bashScript;
}
$ nix eval --file default.nix --json | jq
{
  "This": "is a Nix expression",
  "bashScript": "\necho \"I am a shell script followed by a Nix expression.\"\necho \"The common variable is: $commonVariable\"\necho \"The Nix expression is: $nixExpression\"\n\nexit\n",
  "commonVariable": "I can be accessed in both the shell script and the Nix expression"
}
5 Likes

Cool! Would even work with a #!/usr/bin/env bash or other suitable shebang at the beginning, with +x.

Here’s a version that fixes the ${…} issue:

#!/usr/bin/env bash
let _=1;
__=/*
echo "hello, ${USER}"
exit
*/_;
in
{ nix = "cool"; }

This construction also lets you have the script in scope, if you want, but you have to be a bit careful about ${…} (it’ll parameter expand if it contains spaces).

#!/usr/bin/env bash
let _=1;
script=''
echo hello, ''${USER}
exit
'';
in
{ nix = "cool"; inherit script; }
1 Like

Nice! I initially wanted to use the multiline comment but did not come up with something like this.

1 Like

I did something similar a while back this way:

if false && /*;then :;fi # */ false then null else /*

echo Bash script goes here

: <<'EONix' # */

{ foo = "Nix expression goes here"; }

/*
EONix
# */

The only real caveat is that you have to be careful of putting */ into the bash section.

Here’s a flake:

#!/usr/bin/env bash
{# 2>/dev/null
  outputs=attrs@{...}:let
  _=/*
  echo "hello, ${USER}"
  exit
  */_;
  in
  { nix = "cool"; };
}
1 Like