sh: 1: Bad substitution means a bash-only expansion, here ${name,,} for lower case, was run by sh. On Ubuntu sh is dash, which does not support it. Run the line with bash, or use a form sh understands, such as tr '[:upper:]' '[:lower:]'.

This one stops the script. In the test the script had a third line, echo after; under sh it never printed, and the script exited with code 2. That is different from source or [[ under sh, where the script printed its error and carried on.

The error

docker exec fix10-lc-u3 sh -c 'name=MARIA; echo ${name,,}'
sh: 1: Bad substitution

The same line in a three-line script run with sh, then with bash:

docker exec fix10-lc-u3 bash -c 'cat /root/lower.sh; ls -l /bin/sh'
name=MARIA
echo ${name,,}
echo after
lrwxrwxrwx 1 root root 4 Mar 31  2024 /bin/sh -> dash

docker exec fix10-lc-u3 bash -c 'sh /root/lower.sh 2>&1; echo exit=$?'
/root/lower.sh: 2: Bad substitution
exit=2

docker exec fix10-lc-u3 bash -c 'bash /root/lower.sh 2>&1; echo exit=$?'
maria
after
exit=0

With sh -c the message names sh and line 1; in a script it names the file and the line.

Why it happens

${name,,} is a bash expansion. /bin/sh in the ubuntu:24.04 image is a link to dash, and dash refused it. Alpine's BusyBox sh refused the same script as well, with its own wording, and also stopped before echo after:

docker exec fix10-lc-al2 sh -c 'sh /root/lower.sh 2>&1; echo exit=$?'
/root/lower.sh: line 3: syntax error: bad substitution
exit=2

BusyBox named line 3 although the expansion is on line 2. A Dockerfile RUN line also runs under /bin/sh -c; this expansion was not built in a Dockerfile here, but the source: not found post linked below shows a RUN line failing the same way for another bash-only command.

The fix

Run the script with bash, as in the last command of the error section, which printed maria and after. Or keep sh and use tr, which worked in dash:

docker exec fix10-lc-u3 bash -c 'cat /root/tr.sh; sh /root/tr.sh'
name=MARIA
echo "$name" | tr '[:upper:]' '[:lower:]'
maria

For a script, bash script.sh is the smaller change. For a Dockerfile, the fix tested in the linked post for source is a SHELL ["/bin/bash", "-c"] line before the RUN lines. The tr form is the one to use when the script must stay portable to sh.

How it was reproduced

The captures were made in containers started with docker run -d --name fix10-lc-u3 ubuntu:24.04 sleep infinity (Ubuntu 24.04.5 LTS, bash 5.2.21, /bin/sh linked to dash) and docker run -d --name fix10-lc-al2 alpine sleep 3600 (Alpine 3.24.2) on Docker Engine 29.9.3. lower.sh and tr.sh were copied in with docker cp (for example docker cp l3\lower.sh fix10-lc-u3:/root/lower.sh). Every command was sent from Windows PowerShell 5.1 as shown, with the sh -c or bash -c text in single quotes so it reached the container unchanged. The container names are the test's own.

Frequently asked

What does Bad substitution mean in sh?
A bash-only expansion such as ${name,,} was run by sh. In the test dash printed Bad substitution, skipped the rest of the script and exited with code 2.
How do I convert a string to lower case in sh?
Pipe it through tr '[:upper:]' '[:lower:]'. In the test a two-line script doing that printed maria under dash.
Why does ${var,,} work in bash but not sh?
In the test bash printed maria for the same script, while Ubuntu's sh (dash) and Alpine's BusyBox sh both rejected it as a bad substitution.

More decoded errors in the Fixes category. Another bash command that sh rejects: /bin/sh: 1: source: not found.