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.