Hello i am at the moment stuck on how to escape an array variable "${STATIC_ENV_VARS[@]}" in the following code:
installPhase = ''
...
mapfile -t STATIC_ENV_VARS < <(${yq-go}/bin/yq '.env[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$Static_LAUNCHER_YML" 2>/dev/null)
for env_item in "${STATIC_ENV_VARS[@]}"; do
if [ -n "\$env_item" ]; then
export "\$env_item"
fi
done
...
'';
What i tried:
"''\${STATIC_ENV_VARS[@]}" → ""
"''\$''\{STATIC_ENV_VARS[@]''\}" → ""
"\${STATIC_ENV_VARS[@]}" → error
"\$\{STATIC_ENV_VARS[@]}" → "$\{DYNAMIC_ENV_VARS[@]}"
"$''\{STATIC_ENV_VARS[@]}" → ""
"$\{STATIC_ENV_VARS[@]}" → "$\{DYNAMIC_ENV_VARS[@]}"
I don’t know what i do wrong…
NobbZ
May 22, 2026, 9:00pm
2
In intended strings you escape the ${ by using ''${.
installPhase = ''
...
mapfile -t STATIC_ENV_VARS < <(${yq-go}/bin/yq '.env[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$Static_LAUNCHER_YML" 2>/dev/null)
-for env_item in "${STATIC_ENV_VARS[@]}"; do
+for env_item in "''${STATIC_ENV_VARS[@]}"; do
if [ -n "\$env_item" ]; then
export "\$env_item"
fi
done
...
'';
thanks for the fast reply.
It didn’t work for me:
for env_item in "''${STATIC_ENV_VARS[@]}"; do
if [ -n "\$env_item" ]; then
export "\$env_item"
fi
done
"''${STATIC_ENV_VARS[@]}" → ""
NobbZ
May 22, 2026, 9:05pm
4
What does this mean? Do you see "" in the rendered script? Or is the STATIC_ENV_VARS just empty?
As a matter of debugging, you might want to add a simple echo $STATIC_ENV_VARS between mapfile and the loop.
I see "" in the rendered script.
NobbZ
May 22, 2026, 9:07pm
6
Can you please share a full reproducer?
Sure.
I try to build it with nix-build and it doesn’t work.
The code is in package.nix line 125 or 131.
default.nix
let
pkgs = import <nixpkgs> {};
in
pkgs.callPackage ./package.nix {}
package.nix
{ stdenv
, fetchurl
, makeDesktopItem
, copyDesktopItems
, autoPatchelfHook
, unzip
, zlib
, glibc
, fontconfig
, alsa-lib
, xorg
, wayland
, gcc13
, libzen
, libmediainfo
, zenity
, lib
, ffmpeg
, yt-dlp
, deno
, openjdk21
, yq-go
}:
let
version = "5.2.12";
# Define sources for different architectures
sources = {
"x86_64-linux" = {
arch = "amd64";
sha256 = "sha256-0v5zSUmY2JuCiE0VPaL0iayOm0RIlcbc3CYP9vaUxPM=";
};
"aarch64-linux" = {
arch = "arm64";
sha256 = "sha256-rXT3HKczUtvoebjmCxm0S/l1dIRkolk40qdL/FVvrK8=";
};
};
# Select the source based on the current system
sysSrc = sources.${stdenv.hostPlatform.system}
or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
jdk = openjdk21;
desktopItem = makeDesktopItem {
name = "tinyMediaManager";
exec = "tinyMediaManager";
icon = "tinyMediaManager";
comment = "A media management tool";
desktopName = "tinyMediaManager";
genericName = "Media Manager";
categories = [ "Video" "AudioVideo" ];
terminal = false;
};
in
stdenv.mkDerivation {
pname = "tinyMediaManager";
inherit version;
src = fetchurl {
url = "https://archive.tinymediamanager.org/v${version}/tinyMediaManager-${version}-linux-${sysSrc.arch}.tar.xz";
sha256 = sysSrc.sha256;
};
nativeBuildInputs = [
autoPatchelfHook
unzip
copyDesktopItems
];
buildInputs = [
zlib
glibc
fontconfig
alsa-lib
xorg.libX11
xorg.libXext
xorg.libXrender
xorg.libXtst
xorg.libXi
wayland
gcc13.cc.lib
libzen
libmediainfo
zenity
];
strictDeps = true;
__structuredAttrs = true;
desktopItems = [ desktopItem ];
installPhase = ''
runHook preInstall
# Create destination directory
mkdir -p $out/opt/tinyMediaManager $out/bin
cp -r * $out/opt/tinyMediaManager
cat > $out/bin/tinyMediaManager << EOF
#!/usr/bin/env bash
export LD_LIBRARY_PATH=${libzen}/lib:${libmediainfo}/lib:\$LD_LIBRARY_PATH
export PATH=${zenity}/bin:${yt-dlp}/bin:${ffmpeg}/bin:${deno}/bin:\$PATH
Static_LAUNCHER_YML="$out/opt/tinyMediaManager/launcher.yml"
# 1. Parse JVM Options
Static_OPTS=\$(${yq-go}/bin/yq '.jvmOpts[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$Static_LAUNCHER_YML" | xargs)
# 2. Parse Classpath
Static_CLASSPATH=\$(${yq-go}/bin/yq '.classpath[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "") | sub("^CONTENT_DIR", "~/.local/share/tinyMediaManager") | sub("^/", "")' "\$Static_LAUNCHER_YML" | paste -sd ":" -)
# 3. Parse env
mapfile -t STATIC_ENV_VARS < <(${yq-go}/bin/yq '.env[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$Static_LAUNCHER_YML" 2>/dev/null)
LAUNCHER_YML="\$HOME/.local/share/tinyMediaManager/launcher-extra.yml"
DYNAMIC_OPTS=""
# Check if launcher-extra.yml exists
if [ -f "\$LAUNCHER_YML" ]; then
DYNAMIC_OPTS=\$(${yq-go}/bin/yq '.jvmOpts[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$LAUNCHER_YML" | xargs)
DYNAMIC_CLASSPATH=\$(${yq-go}/bin/yq '.classpath[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "") | sub("^CONTENT_DIR", "~/.local/share/tinyMediaManager") | sub("^/", "")' "\$LAUNCHER_YML" | paste -sd ":" -)
mapfile -t DYNAMIC_ENV_VARS < <(${yq-go}/bin/yq '.env[] | select(test("^\\[macos\\]") == false) | sub("^\\[linux\\]\\s*", "")' "\$Static_LAUNCHER_YML" 2>/dev/null)
fi
for env_item in "''${STATIC_ENV_VARS[@]}"; do
if [ -n "\$env_item" ]; then
export "\$env_item"
fi
done
for env_item in "''${STATIC_ENV_VARS[@]}"; do
if [ -n "\$env_item" ]; then
export "\$env_item"
fi
done
export JDK_JAVA_OPTIONS="-Djava.library.path=./native \$Static_OPTS \$DYNAMIC_OPTS"
cd $out/opt/tinyMediaManager
exec ${jdk}/bin/java -cp "\$Static_CLASSPATH:\$DYNAMIC_CLASSPATH" org.tinymediamanager.TinyMediaManager "\$@"
EOF
chmod +x $out/bin/tinyMediaManager
# Handle the icon
mkdir -p $out/share/pixmaps
if [ -f "$out/opt/tinyMediaManager/tmm.png" ]; then
ln -s $out/opt/tinyMediaManager/tmm.png $out/share/pixmaps/tinyMediaManager.png
fi
runHook postInstall
substituteInPlace $out/opt/tinyMediaManager/launcher.yml \
--replace 'jvmOpts:' "jvmOpts:
- '-Dtmm.noupdate=true'
- '-Dtmm.usepath=true'"
substituteInPlace $out/opt/tinyMediaManager/launcher.yml \
--replace 'env:' "env:
- \"_JAVA_AWT_WM_NONREPARENTING=1\""
'';
meta = with lib; {
description = "A media management tool";
homepage = "https://www.tinymediamanager.org/";
changelog = "https://gitlab.com/tinyMediaManager/tinyMediaManager/-/releases/tinyMediaManager-${finalAttrs.version}#changelog";
sourceProvenance = [ lib.sourceTypes.binaryNativeCode ];
license = licenses.asl20;
maintainers = with lib.maintainers; [ gamebeaker ];
platforms = [
"x86_64-linux"
"aarch64-linux"
];
mainProgram = "tinyMediaManager";
};
}
NobbZ
May 22, 2026, 9:20pm
8
echo -n $(nix-instantiate --eval --expr '((import <nixpkgs> {}).callPackage ./package.nix {}).installPhase')
shows this:
for env_item in \"\${STATIC_ENV_VARS[@]}\"; do
if [ -n \"\$env_item\" ]; then
export \"\$env_item\"
fi
done
for env_item in \"\${STATIC_ENV_VARS[@]}\"; do
if [ -n \"\$env_item\" ]; then
export \"\$env_item\"
fi
done
So the envvar is there correctly. Bash sees this:
for env_item in "${STATIC_ENV_VARS[@]}"; do
if [ -n "$env_item" ]; then
export "$env_item"
fi
done
for env_item in "${STATIC_ENV_VARS[@]}"; do
if [ -n "$env_item" ]; then
export "$env_item"
fi
done
So for me, everythhing in the generated script looks fine.
I uploaded a video: 2026-05-22 23-23-47.mp4 - Storage Share
I thought nixos would prevent “it works on my machine” xD.
NobbZ
May 22, 2026, 9:31pm
10
I have no clue what file you looked at in that video, though it might have been through additional stages. I just realized that you have a <<EOF…EOF slapped around the area.
So maybe you are missing some bash escaping on top, making it "\''${…}" should break that level.
1 Like
That was it. Huge thanks <3
"\''${DYNAMIC_ENV_VARS[@]}" → "${STATIC_ENV_VARS[@]}"
Should i use something else?
NobbZ
May 22, 2026, 9:42pm
12
No, you just have to properly be aware of the various escaping levels you go through, when you embed languages in languages, here bash in bash in nix.
1 Like
TLATER
May 24, 2026, 12:24pm
13
Or maaaaybe avoid doing that in the first place. I don’t think there is ever a legitimate reason to doubly-nest bash in ways that would cause quoting issues; use subshells or functions instead, or pkgs.writeText (or pkgs.writers) if you want to create files.
Haven’t seen the code, of course, this just sounds like a wrong choice somewhere a bit earlier.
2 Likes