2. Batch script fundamentals for CAE

In the previous chapter we established that a CAE automation script is essentially a deterministic pipeline: it locates a solver, feeds it a deck, and collects results. That pipeline only becomes reusable when the paths, filenames, and solver options inside it are data rather than hard-coded text. This chapter covers the three mechanisms that make this possible in Windows batch: ordinary variables, environment variables, and parameter expansion. Together they let one script drive dozens of load cases, mesh variants, or solver versions without editing a single line of the script body.

Ordinary variables and the delayed-expansion trap

A batch variable is created with set and referenced with %NAME%. The critical subtlety is that %NAME% is expanded when the parser reads a line, not when the line executes. Inside a parenthesized block or a for loop, this means a variable you just assigned is still invisible on the next line of the same block. The standard fix is setlocal enabledelayedexpansion plus !NAME!:

@echo off
setlocal enabledelayedexpansion

set "CASE=LC01"
set "MESH=coarse"
set "WORKDIR=D:\cae\projects\bracket\%CASE%_%MESH%"

if not exist "%WORKDIR%" mkdir "%WORKDIR%"

for %%F in (deck_*.inp) do (
    set "DECK=%%~nF"
    echo Running !DECK! in %WORKDIR%
    call run_solver.bat "%%F" "!WORKDIR!"
)
endlocal

Note the quoting convention set "NAME=value". It is not cosmetic: it prevents trailing spaces from becoming part of the value, which is the single most common cause of "file not found" errors when a path is assembled from several variables. A path like D:\cae\projects\bracket\LC01_coarse (with a trailing space) will fail silently in some solvers and produce a confusing error in others.

Environment variables that matter to a solver run

Environment variables are variables inherited by every child process, including the solver executable. For CAE work, four categories are worth knowing:

  • System paths: PATH determines whether abaqus, ansys, ccx, or nastran can be invoked by name. Prepending a version-specific directory lets you pin a solver version per script.
  • Scratch and temp: TEMP and TMP control where the solver writes intermediate files. Large implicit jobs can exhaust the system drive if these point to C:\Users\...\AppData\Local\Temp.
  • License and vendor settings: variables such as LM_LICENSE_FILE, ANSYSLMD_LICENSE_FILE, or ABAQUSLM_LICENSE_FILE select the license server. Setting them inside the script isolates a run from a misconfigured machine.
  • Parallelism: vendor-specific variables (for example OMP_NUM_THREADS for OpenMP-enabled solvers) control thread count and therefore memory footprint.

You can set these for the duration of a script with set, and they will be inherited by the solver process launched afterwards. To keep the change local, wrap the script in setlocal/endlocal; to make it persistent for the user account, use setx — but be aware that setx does not affect the current console session.

Practical rule: never rely on a machine-wide PATH to select a solver version. Resolve the executable explicitly at the top of the script and store the full path in a variable. This makes the script portable across workstations and CI runners.

Parameter expansion: the workhorse of path construction

Batch supports a compact set of modifiers applied to %VAR% or to a for variable. They are the difference between a script that only works on one machine and one that works anywhere.

ModifierMeaningCAE use case
%~d1Drive letterDetect whether the deck is on a network share
%~p1Path without driveRebuild an output path relative to the deck
%~n1File name without extensionDerive the job name from bracket_v3.inp
%~x1ExtensionBranch on .inp vs .cdb vs .dat
%~nx1Name with extensionCopy the deck into the run directory unchanged
%~dp0Drive and path of the script itselfLocate sibling folders such as templates\ or tools\
%VAR:old=new%Substring replacementSwap \mesh\ for \results\ in a path
%VAR:~start,len%Substring extractionExtract a load-case ID from a long filename

The %~dp0 modifier deserves special attention. It expands to the directory containing the running script, always ending with a backslash. This is the anchor that makes a script relocatable: instead of hard-coding D:\cae\tools\, you write set "TOOLS=%~dp0tools" and the script works from any checkout location.

Building a solver input deck path from parameters

Consider a script that must run a solver on a deck whose name encodes the load case and mesh density, and must place results in a mirrored directory tree. The following pattern is typical:

@echo off
setlocal enabledelayedexpansion

set "ROOT=%~dp0"
set "DECK=%~1"
if "%DECK%"=="" (
    echo Usage: run_case.bat ^<deck^>
    exit /b 1
)

rem Derive job name and output directory from the deck path
set "JOB=%~n1"
set "OUTDIR=%ROOT%runs\%JOB%"

rem Replace the input tree marker with the output tree marker
set "OUTDIR=%OUTDIR:\decks\=\runs\%"

if not exist "%OUTDIR%" mkdir "%OUTDIR%"

echo Deck      : %DECK%
echo Job name  : %JOB%
echo Output dir: %OUTDIR%

rem Launch the solver with the deck and output directory
solver.exe -input "%DECK%" -output "%OUTDIR%\%JOB%.dat"

Two details are worth noting. First, the substring replacement %OUTDIR:\decks\=\runs\% is applied to the already-assembled path, so the script does not need to know the absolute location of the project. Second, the output directory is created before the solver is launched, because most solvers will not create missing directories themselves and will instead abort with a generic I/O error.

Visualising the parameter flow

The diagram below shows how a single deck path is decomposed by parameter expansion and reassembled into a solver invocation. The left column is the raw input; the middle column shows the modifiers applied; the right column is the concrete value passed to the solver.

Parameter expansion pipeline for a solver run Raw input %~1 = D:\proj\decks\LC01.inp %~dp0 = D:\proj\tools\ Modifiers applied %~n1 → LC01 %~x1 → .inp %~p1 → \proj\decks\ %~dp0 → D:\proj\tools\ Assembled values JOB = LC01 OUTDIR = D:\proj\runs\LC01 DECK = %~1 Solver invocation solver.exe -input "D:\proj\decks\LC01.inp" -output "D:\proj\runs\LC01\LC01.dat" Inherited environment (set before launch) PATH += D:\solvers\v2023\bin TEMP = D:\scratch OMP_NUM_THREADS = 8 LM_LICENSE_FILE = 27000@srv-lic

Environment variables versus script variables: choosing the right scope

A frequent design question is whether a value should be a script variable or an environment variable. The distinction is not stylistic; it affects process isolation and reproducibility.

  • Use script variables for anything that is internal to the automation logic: job names, output directories, iteration counters, log file paths. These should not leak into the solver's environment.
  • Use environment variables for anything the solver executable reads directly: license servers, thread counts, scratch directories, plugin paths. These must be visible to the child process.
  • Use setlocal around the whole script so that any environment changes are discarded when the script exits. This prevents a failed run from leaving a corrupted PATH or license setting behind for the next job.
Common failure: a script sets PATH to a solver version, runs successfully, and then a later script on the same console picks up the modified PATH because setlocal was omitted. The symptom is a solver version mismatch that appears only when scripts are run in sequence. Always pair setlocal with endlocal, or rely on the implicit endlocal at script exit.

Verifying the assembled paths before launching the solver

Because a wrong path usually surfaces as an opaque solver error, it is worth adding a dry-run mode. A minimal pattern is to echo the resolved variables and exit before invoking the solver when a --dry-run flag is present:

if /i "%~2"=="--dry-run" (
    echo [dry-run] DECK   = %DECK%
    echo [dry-run] JOB    = %JOB%
    echo [dry-run] OUTDIR = %OUTDIR%
    echo [dry-run] SOLVER = %SOLVER%
    exit /b 0
)

Running the script with --dry-run should print paths that match the actual directory tree. If OUTDIR contains a doubled backslash, a trailing space, or an unresolved %VAR%, the problem is in the assembly logic, not in the solver. This single check eliminates the majority of "solver cannot find input" and "cannot write output" incidents before they consume license time.

With variables, environment variables, and parameter expansion in place, the script can now be driven entirely by its arguments and its surrounding environment. The next chapter builds on this foundation by introducing control flow — conditionals and loops — that turn a single-case script into a batch runner over many decks.