Skip to content
daniel@cosenza:~/blog
FreeDOSDeep Dive February 19, 2026 3 min read

Writing Batch Files on FreeDOS

A practical tour of FreeDOS batch scripting: variables, control flow, argument handling, and the quirks that differ from a modern shell.

Batch files are DOS’s native scripting mechanism, interpreted directly by COMMAND.COM — no separate scripting engine, no separate interpreter to invoke, just a .BAT file containing the same commands you’d type interactively, plus a small set of batch-specific control-flow directives.

The basics: a script is just commands

Any .BAT file is executed line by line, exactly as if you’d typed each line at the prompt yourself:

@ECHO OFF
ECHO Starting backup...
COPY C:\DATA\*.TXT A:\BACKUP\
ECHO Done.

@ECHO OFF suppresses DOS’s default behavior of echoing every command back to the screen before running it — the @ prefix on that specific line also suppresses the ECHO OFF command itself from being echoed, a small but universally-used idiom.

Variables and the environment

Batch files read and write environment variables — the same flat, inherited key-value store every DOS process has access to — using SET and %VARNAME% substitution:

SET NAME=daniel
ECHO Hello, %NAME%!
SET /A COUNT=COUNT+1

SET /A performs integer arithmetic, since batch variables are otherwise just text — there’s no native concept of a numeric type without that flag.

Command-line arguments: %1 through %9

A batch file’s own arguments are available as %1 through %9 (with %0 being the batch file’s own name), and SHIFT moves the argument window forward to handle more than nine:

@ECHO OFF
ECHO First argument: %1
ECHO Second argument: %2
:LOOP
IF "%1"=="" GOTO END
ECHO Processing: %1
SHIFT
GOTO LOOP
:END

Control flow: IF and GOTO, not real loops

Batch has no native for/while loop construct in the way a modern language does — repetition is built entirely from IF plus GOTO jumping to a :LABEL, as the argument-processing example above shows. IF supports string comparison, numeric comparison (with /I for case-insensitive), and existence checks:

IF EXIST C:\CONFIG.SYS ECHO Config found
IF NOT EXIST C:\TEMP MKDIR C:\TEMP
IF %ERRORLEVEL% NEQ 0 GOTO ERROR_HANDLER

ERRORLEVEL is DOS’s version of a shell exit status — every program that exits sets it, and IF ERRORLEVEL n tests for a value greater than or equal to n, which trips up people expecting exact-match semantics.

FOR: DOS’s actual iteration construct

DOS does have a genuine iteration command, just spelled differently than a modern language’s loop — FOR iterates over a set of files or a literal list:

FOR %%F IN (*.TXT) DO COPY %%F A:\BACKUP\
FOR %%N IN (1 2 3 4 5) DO ECHO Number: %%N

Note the doubled %% — inside a batch file, a single % in a FOR variable would be interpreted as the start of a variable substitution, so the loop variable is escaped by doubling it (this differs from using FOR interactively at the prompt, where a single % is correct).

Subroutines: CALL and labels

CALL invokes another label within the same batch file (or another batch file entirely) as a subroutine, returning control afterward — the closest batch equivalent to a function call:

@ECHO OFF
CALL :GREET
CALL :GREET
GOTO END

:GREET
ECHO Hello!
GOTO :EOF

:END

GOTO :EOF is the idiomatic way to “return” from a subroutine — jumping to the (virtual) end of the file rather than falling through into whatever code happens to follow.

Where batch scripting genuinely differs from a modern shell

Batch has no real data structures, no functions with parameters and return values (only global-environment-variable-based communication and ERRORLEVEL), and string manipulation is famously awkward — substring extraction and manipulation rely on obscure variable-expansion syntax (%VAR:~0,5% for a substring) rather than anything resembling a modern string library. That said, for its actual job — sequencing DOS commands, basic conditional logic, simple file operations — batch scripting is entirely sufficient, and its simplicity (interpreted directly by the shell already running, no separate runtime) is exactly why it’s remained usable, unchanged in spirit, for over four decades.