2. Shell scripting foundations for CAE engineers

Crash CAE automation lives or dies by the shell. A typical LS-DYNA or Radioss workflow chains together pre-processing, solver invocation, and post-processing across dozens of directories, each with its own keyword decks, include files, and result databases. Before writing a single automation script, you need a firm grip on the execution model that Bash and POSIX sh share — and the places where they diverge. This section covers the interpreter landscape, how scripts actually run, how exit codes propagate, how quoting rules protect your data, and how to survive file paths that contain spaces or solver keywords.

Bash vs. POSIX sh: choosing your interpreter

Every script begins with a shebang line that names the interpreter. On most Linux clusters, /bin/sh is a symlink to dash or a POSIX-compliant mode of bash, while /bin/bash is the full-featured GNU shell. The distinction matters because CAE clusters are heterogeneous: a script that runs on your workstation may fail on a compute node where /bin/sh is dash.

POSIX sh guarantees a minimal feature set: parameter expansion, command substitution with backticks, basic arithmetic via $(( )), and the standard control structures. Bash adds arrays, associative arrays, [[ ]] conditional expressions, process substitution, brace expansion, and the local keyword. For CAE automation, the pragmatic rule is: write POSIX-compliant scripts when they must run on unknown cluster nodes, and use Bash explicitly (with #!/usr/bin/env bash) when you control the environment and need arrays to hold lists of load cases or result files.

If portability is uncertain, test with dash -n script.sh and shellcheck -s sh script.sh before deploying to a compute node.

How a script executes

There are three common ways to run a script, and they behave differently:

  • bash script.sh — launches a new Bash process, reads the file, and executes it. The shebang is ignored; the file does not need execute permission.
  • ./script.sh — the kernel reads the shebang, starts the named interpreter, and passes the file. The file must be executable (chmod +x).
  • source script.sh or . script.sh — executes in the current shell. Variables and functions persist after the script ends. This is how you load environment modules or define solver aliases.

For CAE batch jobs, ./script.sh is the norm because it respects the shebang and isolates the job. Use source only for environment setup files such as source /opt/lsdyna/env.sh.

Exit codes: the contract between processes

Every process returns an integer exit status in the range 0–255. Zero means success; anything else means failure. The shell stores the last status in $?. This is the backbone of automation: you check $? after each solver run to decide whether to proceed to post-processing or abort.

ls-dyna i=crash.k ncpu=8 memory=200m
status=$?
if [ "$status" -ne 0 ]; then
    echo "Solver failed with exit code $status" >&2
    exit "$status"
fi

Bash and POSIX sh both support set -e (exit on first error), set -u (error on unset variables), and set -o pipefail (a pipeline fails if any stage fails). Combining them as set -euo pipefail is standard practice in production CAE scripts. Note that pipefail is not POSIX; it is a Bash extension.

Be aware that some solvers return non-zero codes for warnings that are not fatal. LS-DYNA, for example, may return 1 for a completed run with warnings. Always consult the solver documentation before treating every non-zero code as a hard failure.

Quoting rules: the single most common source of bugs

Shell quoting determines whether the shell interprets special characters or passes them literally. The three quoting mechanisms are:

FormBehaviorExample result
'...'Literal; no expansion'$HOME' → $HOME
"..."Expands variables, command substitution, arithmetic"$HOME" → /home/engineer
\Escapes the next character\$HOME → $HOME

The golden rule for CAE automation: always double-quote variable expansions unless you specifically need word splitting. A path like /scratch/run 01/mesh.k contains a space; unquoted $file becomes two arguments and the solver receives a truncated path. Quoted "$file" stays intact.

deck="/scratch/crash run 01/main.k"
# Wrong: word splitting breaks the path
ls-dyna i=$deck
# Correct: quoted expansion preserves the space
ls-dyna i="$deck"

Handling paths with spaces and solver keywords

Crash CAE directories frequently contain spaces (project names, revision labels) and solver keywords such as *INCLUDE, *MAT, or *CONTROL that appear inside deck files. Two problems arise:

  1. Paths with spaces — always quote. When building paths, use "${dir}/${file}" rather than $dir/$file. When iterating over files, use find ... -print0 piped to xargs -0 or a while IFS= read -r loop to handle embedded spaces and newlines safely.
  2. Solver keywords in filenames or arguments — a file named *INCLUDE_notes.txt will be expanded by the shell if unquoted, because * is a glob. Quote it: "*INCLUDE_notes.txt". Similarly, a variable containing *MAT must be quoted to prevent glob expansion.
# Safe iteration over decks that may contain spaces
find ./decks -name '*.k' -print0 |
while IFS= read -r -d '' deck; do
    echo "Processing: $deck"
    ls-dyna i="$deck" ncpu=4
done
Never use for f in $(ls) in CAE scripts. It breaks on spaces, globs, and newlines. Use find -print0 with read -d '' or Bash arrays populated by mapfile -t.

Putting it together: a minimal safe runner

The following POSIX-compliant snippet demonstrates the foundations in one place: explicit interpreter, strict mode, quoted expansions, exit-code checking, and safe path handling.

#!/bin/sh
set -eu

run_dir="$1"
solver="${SOLVER:-ls-dyna}"

if [ ! -d "$run_dir" ]; then
    echo "Run directory not found: $run_dir" >&2
    exit 2
fi

find "$run_dir" -name '*.k' -print0 |
while IFS= read -r -d '' deck; do
    echo "Solving: $deck"
    "$solver" i="$deck" ncpu=8 memory=200m
    rc=$?
    if [ "$rc" -ne 0 ]; then
        echo "FAILED ($rc): $deck" >&2
        exit "$rc"
    fi
done

echo "All decks completed successfully."

Run it as ./run_all.sh "/scratch/crash run 01". The quoted argument preserves the space; the find -print0 loop preserves spaces inside deck paths; the exit-code check aborts on the first solver failure. This pattern scales from a single load case to hundreds of crash simulations.

Shell Execution & Quoting Decision Path for CAE Scripts Shebang line #!/bin/sh or bash Execution mode ./script.sh | source | bash Exit code check $? == 0 ? continue : abort Quoting decision: does the value contain spaces, globs, or solver keywords? YES → double-quote "$deck" "${dir}/${file}" find -print0 | read -r -d '' NO → unquoted is safe $(( )) arithmetic case patterns, [[ ]] tests Failure symptoms Path truncated at space Glob expands *INCLUDE to filenames Solver reads wrong deck or aborts Correct outcome Full path preserved to solver Keywords passed literally Exit code 0 → post-process

Practical checklist before deploying a CAE script

CheckCommand / ActionWhy it matters
Interpreter declaredFirst line is #!/bin/sh or #!/usr/bin/env bashEnsures correct shell on compute nodes
Strict modeset -eu (add -o pipefail in Bash)Catches unset vars and silent pipeline failures
All expansions quoted"$var", "${dir}/${file}"Survives spaces and globs in paths
Safe file iterationfind -print0 | while IFS= read -r -d ''Handles spaces, newlines, special chars
Exit codes checkedrc=$?; [ "$rc" -ne 0 ] && exit "$rc"Prevents post-processing on failed solves
ShellCheck cleanshellcheck -s sh script.shCatches quoting and portability bugs statically

These foundations — interpreter choice, execution semantics, exit-code discipline, and rigorous quoting — are not academic. In crash CAE automation, a single unquoted variable can silently send a solver to the wrong deck, producing results that look plausible but correspond to the wrong load case. Mastering these rules is what separates a fragile one-off script from a reliable simulation pipeline.