Course outline

Jenkins / Execution and source foundations / Lesson 6

Shell steps, quoting, and exit status

Preserve command failures and keep data outside Groovy-generated shell source.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain sh, bat, and PowerShell step assumptions.
  • Handle returnStatus and returnStdout without neutralizing required checks.

Choose the step for the actual agent

Jenkins shell-related steps execute on an allocated node with platform and interpreter prerequisites. sh is for a suitable Unix-like shell environment; Windows batch and PowerShell steps have different syntax and status rules. A familiar step name does not install the interpreter or make a script portable. Parcel’s examples assume a separately provided complete check_fixture.py when discussing a command. This course executes no Jenkins shell step. Identify working directory, selected interpreter, toolchain, and command before interpreting an error as an application defect.

A nonzero result is meaningful evidence

By default, a failing sh command raises a Pipeline failure under its documented step behavior. returnStatus instead returns the exit code so the Pipeline can decide what to do. That is useful only if the code is actually evaluated. Ignoring it converts a required failed check into continued execution. returnStdout captures text and changes how output is consumed; it does not make arbitrary output trustworthy or authorize downstream decisions. Keep command status, diagnostic text, and release eligibility as separate facts.

Evaluate an explicitly captured status

The fragment below captures a check’s status and stops through error when it is nonzero. It requires a Unix-like allocated workspace, Python, and a complete fixture script that are not supplied by the fragment. It is never run here. A real Pipeline may instead preserve a failed stage while collecting reports through a deliberate result-handling pattern, but it must retain the original check outcome. A successful echo after the check is not a substitute for checking the returned integer.

groovy A captured-status decision fragment
script {
    int status = sh(script: "python check_fixture.py", returnStatus: true)
    if (status != 0) {
        error("Fixture check did not pass")
    }
}

This is illustrative Declarative script content. It neither creates the fixture nor proves Jenkins execution. Exact stderr and timing depend on the real command and agent, so none are fabricated.

Groovy and the shell parse at different times

A double-quoted Groovy string can interpolate values before the shell receives the script. Inserting untrusted parameters or credentials there can expose them or turn data into executable syntax. Prefer a fixed script and an appropriate environment or structured argument channel, then quote according to the selected shell. A safely quoted value can still be unsafe as a command name, option, path, or expression in another program. Avoid eval-like reparsing. Secret masking is not a reason to build shell source containing credentials or contributor-controlled text.

Observe a local exit code without a CI engine

The complete Python program launches a local child interpreter that exits with status three, captures the result, and reports blocked. It performs no network, file mutation, or Jenkins operation. This demonstrates that a process can return a status without the caller raising automatically when configured that way. It is not an emulator for Jenkins interruption, shell pipeline semantics, or Windows exit behavior. A real wrapper must preserve failures through pipes and report collection, and distinguish an assertion failure from cancellation or missing execution capacity.

python Capture a declared local process status
import subprocess, sys
r = subprocess.run([sys.executable, "-I", "-c", "raise SystemExit(3)"], capture_output=True, text=True)
print(r.returncode)
print("ready" if r.returncode == 0 else "blocked")
Expected output
3
blocked

Only a local Python child is started. No shell is invoked, and status three is deliberately declared by the child rather than an observed Jenkins failure.

Common errors and fixes

returnStatus is enabled but the returned integer is ignored.

Why it happens
Failure handling was disabled without a replacement decision.
How to fix it
Evaluate the code and preserve the required check’s failure.
Prevent it next time
Review negative command outcomes in wrapper logic.

A Bash expression is sent to a Windows batch step.

Why it happens
The script assumes an interpreter different from the selected step.
How to fix it
Use the correct supported interpreter and rewrite its quoting and status handling.
Prevent it next time
Declare platform prerequisites and test script portability separately.

A secret or free-text parameter is interpolated into Groovy shell source.

Why it happens
Data crosses two interpretation layers without a safe boundary.
How to fix it
Use a fixed script with appropriate scoped data input and downstream validation.
Prevent it next time
Trace Groovy, shell, and helper-program interpretation independently.

Check your understanding

1. A shell check returns status two, but a later report publishes successfully. Is the check now successful?

Show worked solution

No. Report publication and the check are distinct outcomes. Retain the failing status and apply the required gate while preserving useful diagnostics. Do not allow a final successful command or ignored return value to erase the original contract failure.

2. Does returnStdout turn a command’s text into a typed boolean?

Show worked solution

No. It captures text under the step contract. Parse and validate the intended format and inspect command success independently. A string such as false or an empty result must not be treated as universal release authorization.

Key takeaways

  • A captured exit code needs an explicit decision.
  • Separate Groovy interpolation, shell parsing, and data validation.

Official references