URL: <https://savannah.gnu.org/bugs/?67473>
Summary: ":|eval (exit 42)" does not return 42
Group: The GNU Bourne-Again SHell
Submitter: pmhahn
Submitted: Di 02 Sep 2025 16:38:44 GMT
Category: None
Severity: 3 - Normal
Item Group: None
Status: None
Privacy: Public
Assigned to: None
Open/Closed: Open
Discussion Lock: Any
_______________________________________________________
Follow-up Comments:
-------------------------------------------------------
Date: Di 02 Sep 2025 16:38:44 GMT By: Philipp Hahn <pmhahn>
The GitLab runner showed a strange behavior, where the exit code of a shell
command did not show up correctly and was always 1. GitLab fixed that by
changing their code how they call `bash`. Details at
https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27668
$ bash -e -c ': | bash -c "exit 42"'; echo $?
42
$ bash -e -c ': | eval "exit 42"'; echo $?
42
$ bash -e -c ' eval "bash -c \"exit 42\""'; echo $?
42
$ bash -e -c ' eval "(exit 42)" </dev/null'; echo $?
42
$ bash -e -c ': |(eval "(exit 42)" )'; echo $?
42
$ bash -e -c 'shopt -s lastpipe;: | eval "(exit 42)"';echo $?
42
$ bash -e -c ': | eval "bash -c \"exit 42\""'; echo $?
1
$ bash -e -c ': | eval "eval \"bash -c \\\"exit 42\\\"\""'; echo $?
1
$ bash -e -c ': | eval "(exit 42)"'; echo $?
1
Neither `dash` not `zsh` nor `ksh` show this behavior:
$ dash -ec ':|eval "(exit 42)"'; echo $?
42
$ zsh -ec ':|eval "(exit 42)"'; echo $?
42
$ ksh -e -c ': | eval "(exit 42)"'
42
= Variants =
* `errexit` is required to show the bug
* using a pipe is required to show the bug
* the `exit 42` must happen in another sub-process, either `(exit 42)` or `sh
-c 'exit 42'` but not simply `exit 42`
* `set -o pipefail` does not make any difference
* `shopt -s lastpipe` fixes it
= bisect =
I have testes old version of bash and the bug seems to start after 4.0-rc1:
$ podman run --rm docker.io/library/bash:3.0-alpine3.22 bash -e -c ': | eval
"(exit 42)"'; echo $?
42
$ podman run --rm docker.io/library/bash:3.1-alpine3.22 bash -e -c ': | eval
"(exit 42)"'; echo $?
42
$ podman run --rm docker.io/library/bash:3.2-alpine3.22 bash -e -c ': | eval
"(exit 42)"'; echo $?
42
$ podman run --rm docker.io/library/bash:4.0-alpine3.22 bash -e -c ': | eval
"(exit 42)"'; echo $?
1
$ podman run --rm docker.io/library/bash:5.0-alpine3.22 bash -e -c ': | eval
"(exit 42)"'; echo $?
1
== git bisect ==
I have run `git bisect` to further shrink the set of changes, which shows the
but to show up between `4.0-rc1` and `4.0`:
$ cat ../bash.bisect
#!/bin/sh
git clean -fdx || exit 2
[ -x ./configure ] || autoconf || exit 2
./configure || exit 125
make -j"$(nproc)" || exit 125
./bash -e -c ': | eval "(exit 42)"'
rv=$?
echo "*** rv=$rv ***"
git reset --hard
[ $rv -eq 42 ]
$ git bisect reset
$ git bisect start bash-4.0 bash-3.2-alpha
$ git bisect run ../bash.bisect
…
# bad: [89a92869e56aba4e4cab2d639c00a86f0545c862] Bash-4.0 patchlevel 38
# good: [e7f1978acfd2125b69bca36994882a1333607739] commit bash-20060706
snapshot
git bisect start 'bash-4.0' 'bash-3.2-alpha'
# skip: [7117c2d221b2aed4ede8600f6a36b7c1454b4f55] Imported from
../bash-2.05b.tar.gz.
git bisect skip 7117c2d221b2aed4ede8600f6a36b7c1454b4f55
# good: [0628567a28f3510f506ae46cb9b24b73a6d2dc5d] Imported from
../bash-3.2.tar.gz.
git bisect good 0628567a28f3510f506ae46cb9b24b73a6d2dc5d
# skip: [3185942a5234e26ab13fa02f9c51d340cec514f8] Imported from
../bash-4.0-rc1.tar.gz.
git bisect skip 3185942a5234e26ab13fa02f9c51d340cec514f8
# bad: [17345e5ad288f7543b77b23a25aa380eacc279f2] Imported from
../bash-4.0.tar.gz.
git bisect bad 17345e5ad288f7543b77b23a25aa380eacc279f2
# skip: [f1be666c7d78939ad775078d290bec2758fa29a2] Imported from
../bash-3.2.48.tar.gz.
git bisect skip f1be666c7d78939ad775078d290bec2758fa29a2
# only skipped commits left to test
# possible first bad commit: [17345e5ad288f7543b77b23a25aa380eacc279f2]
Imported from ../bash-4.0.tar.gz.
# possible first bad commit: [3185942a5234e26ab13fa02f9c51d340cec514f8]
Imported from ../bash-4.0-rc1.tar.gz.
# possible first bad commit: [f1be666c7d78939ad775078d290bec2758fa29a2]
Imported from ../bash-3.2.48.tar.gz.
# good: [3185942a5234e26ab13fa02f9c51d340cec514f8] Imported from
../bash-4.0-rc1.tar.gz.
git bisect good 3185942a5234e26ab13fa02f9c51d340cec514f8
# first bad commit: [17345e5ad288f7543b77b23a25aa380eacc279f2] Imported from
../bash-4.0.tar.gz.
== manual cherry-picking ==
I have then re-applied parts of 4.0 on top of 4.0-rc1 until I found the
relevant diff, which changes the behaviour of `execute_command_internal` and
introduces the bug. Removing the following lines from
`execute_command_internal()` in `execute_cmd.c` line 618-623 returns the old
"good" behavior:
- if (user_subshell && ignore_return == 0 && invert == 0 &&
exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
- {
- last_command_exit_value = exec_result;
- run_pending_traps ();
- jump_to_top_level (ERREXIT);
- }
I have also inserted some debug `fprintf()` to show the values at the point of
execution:
> 610> user_subshell=1 was_error_trap=0 ignore_return=0 invert=0
> exit_immediately_on_error=1 exec_result=42
> 2184> was_error_trap=0 ignore_return=0 invert=0 exit_immediately_on_error=1
> exec_result=42
_______________________________________________________
File Attachments:
Reverted hunk
Name: bash.diff Size: 2KiB
<https://file.savannah.gnu.org/file/bash.diff?file_id=57614>
AGPL NOTICE
These attachments are served by Savane. You can download the corresponding
source code of Savane at
https://savannah.gnu.org/source/savane-4b8598a5d17f272f0bb6fd029e74d5ef737ff864.tar.gz
_______________________________________________________
Reply to this item at:
<https://savannah.gnu.org/bugs/?67473>
_______________________________________________
Nachricht gesendet über Savannah
https://savannah.gnu.org/
signature.asc
Description: PGP signature
