Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions cronic
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@
# The options and the treatment of execution traces are documented in function
# `cronic_help`; run `cronic --help`.

# The caller's options, as commands that restore them. This script restores
# the caller's options while it runs the wrapped command. Otherwise, any bash
# script that the wrapped command runs, such as a `#!/bin/bash` script, would
# get this script's `set -eu` rather than the caller's options and, for example,
# fail on its first unset variable.
# Limitation: if the wrapped command is a builtin that runs code in this
# script's shell, such as `source` or `eval`, that code also runs with the
# caller's options, even if the caller does not export SHELLOPTS. So if the
# caller does not use `set -u`, that code silently expands an unset variable
# to the empty string.
# Not `$(set +o)`, because a command substitution, unlike a process
# substitution, does not inherit `set -e`. `read` exits with status 1 at the
# end of its input.
read -r -d '' cronic_caller_opts < <(set +o) || true

# Exit if a command exits with a non-zero status, and treat unset variables
# as an error.
set -eu
Expand Down Expand Up @@ -158,11 +173,17 @@ if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1)));
export BASH_XTRACEFD=$cronic_xtrace_fd
fi

# The wrapped command runs in a subshell with the caller's options. It is not
# the operand of `||` or the like, because then bash would ignore `set -e` and
# would remove "errexit" from the SHELLOPTS that the wrapped command inherits.
set +e
# `command` because the wrapped command runs in this shell, where a function of
# this script, such as `cronic_usage`, would otherwise run in place of a
# program of the same name.
command -- "$@" > "$cronic_out" 2> "$cronic_trace"
(
eval "$cronic_caller_opts"
# `command` because the wrapped command runs in a subshell of this shell,
# where a function of this script, such as `cronic_usage`, would otherwise
# run in place of a program of the same name.
command -- "$@"
) > "$cronic_out" 2> "$cronic_trace"
cronic_result=$?
set -e

Expand Down
26 changes: 26 additions & 0 deletions tests/cronic-test/cronic-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,30 @@ check "PS4 with a newline, error line that contains its second line" 0 \
"report:^error: bad b input$" "$work/stderr-lines" "error: bad b input"
unset PS4

# A command that expands an unset variable.
cat > "$work/unset-variable" << 'EOF'
#!/bin/bash
echo "${cronic_test_unset_variable}"
EOF
chmod +x "$work/unset-variable"

# A caller that sets the option given as its first argument, such as `-u` or
# `+u`, and exports SHELLOPTS. When the caller exports SHELLOPTS, the wrapped
# command gets the caller's `set -u` setting, not `cronic`'s.
cat > "$work/caller" << EOF
#!/bin/bash
set "\$1"
shift
export SHELLOPTS
exec "$CRONIC" "\$@"
EOF
chmod +x "$work/caller"
saved_cronic="$CRONIC"
CRONIC="$work/caller"
check "exported SHELLOPTS without nounset, unset variable" 0 silent \
+u "$work/unset-variable"
check "exported SHELLOPTS with nounset, unset variable" 1 \
"report:unbound variable" -u "$work/unset-variable"
CRONIC="$saved_cronic"

exit "$status"
Loading