Windows shell scripting is similar to Linux shell scripting in many ways.
However, since Windows and Linux (and other Unix-like operating systems) have different technical heritages, some of the syntax and approaches are very different.
We've looked at scripting on Linux systems using the bash shell – which is the most widely-deployed shell on Linux and other Unix-like systems. There are many other shells on similar systems, including:
Many of these have a syntax based on and similar to the original Bourne shell, which is standardized in the POSIX.1 standard (or IEEE Standard 1003.1), and these shells have a lot in common.
On Windows, there are two main shells:
Due to PowerShell scripting being disabled by default, and because object oriented programming is not taught in the first semester programming courses, we will focus on scripting using the traditional Windows CMD shell.
There are several possible approaches to writing scripts that will work on both Linux and Windows systems, as well as other common operating systems such as Mac OS:
In addition to shell scripting languages, there are other interpreted languages that are well suited for cross-platform development, including Python and Perl.
When should you use a shell scripting langauge?
DOS and early Windows systems were inherently interactive in nature, and early scripts were called “batch files” because they were viewed as similar to non-interactive batch processing on mainframe systems.
This terminology has stuck, and Windows shell scripts are still often called “batch files” (hence the occasional use of the .bat extension instead of .cmd).
The concept of a “batch file” and a “shell script” are roughly equivalent.
Remember these requirements? They apply to Windows scripts too:
1. Create a text file containing shell commands. Use any text editor, such as Notepad, to create the file.
2. Tell the operating system which shell to use to execute the commands. In Windows, the filename extension is used to associate a file with a program, and this mechanism is used to associate a script with a command interpreter.
3. Ensure that the script file has the appropriate permissions. On Windows, the ability to read the script file is sufficient (and this is the default permission, so no change is usually required for scripts that you create for your own use; the situation may be different for scripts that are shared to other machines over the network or to other users on your system).
Windows defaults to displaying each command in a script before executing it (the opposite of the default in the bash shell). If you do not want each command to be displayed, you can:
Usually, you'll combine these in a script, using this as one of the first lines:
@echo off
Here is a simple example script using two commands, echo
and date
:
@echo off echo The current date is: date /t
If this is save into the file named “now”, the permission could be set with this command:
$ chmod u+rx now
The script can then be executed. Normally, the current working directory is not searched, so to run the a script in the current directory, you will need to explicitly specify the directory name like this:
$ ./now The current date is: 2024-12-11
To set a variable, use the set
keyword with a variable name, an equal sign, and the variable value:
set A=5 set B=World
If the variable does not exist, it will be created. If it does exist, the previous value will be discarded.
Variable names may contain letters, digits, or underscores, but must not start with a digit. Case does not matter! The variable names Number
, number
, and NUMBER
all refer to the same variable.
Do not put spaces on either side of the equal sign.
Variables are not typed – they may be used as strings, integers, or decimal values.
To access a variable, place a percent sign [%] on either side of it, and use it in a command as an argument (or as a command name):
> SET B=World > echo %B% World > echo Hello $B Hello World
Quoting in the Windows shell is very different from Bash!
Using single or double quotes causes the quotes themselves to be included as part of the string or argument in most cases, but not when dealing with a filename:
> ECHO "Hello" "Hello"
> ECHO test > "test file"
Quoting is not required when assigning a string value which contains spaces to a variable:
> SET A=One Two Three > ECHO %A% One Two Three
Escaping characters to remove their special meaning is performed using the carat [^] symbol in Windows. In this example, the ampersand [&] symbol would normally cause an error, but it can be treated as a regular character by escaping it with a carat:
> echo Lost ^& Found Lost & Found
When piping, a CMD subshell is started for each command in the pipeline, and it is necessary to use triple carat symbols ^^^ to escape characters:
> echo Lost ^^^& Found | find "Lost" Lost & Found
By default, all variables are environment variables, inherited by child processes.
Environment variables are commonly used to pass configuration information to programs and to configure how programs operate.
You can view all of the current variables with the set
command; you'll probably want to pipe the output through more
.
Environment variables are used by all processes, not just the shell!
Environment Variable | Purpose | Examples |
---|---|---|
CD | Current directory | ECHO %CD% |
TIME | Current time (HH:MM:SS) | ECHO %TIME% |
DATE | Current date in local format | ECHO %DATE% |
ERRORLEVEL | The error code / exit status of the last command executed (note that some commands or programs do not set this variable as expected). | DIR \FileThatDoesNotExist ECHO %ERRORLEVEL% |
PATH | A semicolon [;] separated list of directories that will be searched when looking for a command | |
PROMPT | The prompt presented by the shell. | SET PROMPT=Enter a command: SET PROMPT=$P$G |
RANDOM | A random integer (0-32767) | ECHO %RANDOM% |
Note that the prompt
and path
programs may also be used to adjust the PROMPT and PATH environment variables, respectively.
You can read values from standard input (stdin) and assign them to a variable with the set command using the /p option (aka switch). When you do this, the value specified on the right-hand side of the equal sign is used as the prompt presented to the user:
> set /p NAME=Enter your name: Enter your name: J. Doe > echo %NAME% J. Doe
Here is a script which uses a couple of SET /P
statements:
@echo off set /p NAME=Please enter your name: echo Please to meet you, %NAME% set /p FILE=Please enter a filename: echo Saving your name into the file... echo NAME=%NAME% >> %FILE% echo Done.
There is no direct equivalient to command capture, but it is possible to use SET /P
with redirection from standard input using the less-than [<] symbol to capture one line of text:
> DATE /T >X > SET /P D= <X > ECHO %D% 2024-12-11
CMD can perform integer arithmetic.
To evaluate an arithmetic expressions and store the results in a variable, use the SET
command with the /A
(arithmetic) option. (Note: when used interactively the result of the expressions evaluation will be output to stdout; this does not happen inside scripts).
> SET A=100 > SET B=12 > SET /A X=A*B 1200 <--- This does not get printed inside a script > ECHO %X% 1200 > SET /A A+=1 > NUL: > ECHO %A% 101 > SET /A C=A*B*2 > NUL: > ECHO The answer is %C% The answer is 2424
Notes:
The IF
command takes a test, and uses the result of the test to control the execution of one or more commands. An ELSE
clause is optional; if included, the first conditional commands should be placed in parenthesis.
> set A=Blue > set B=Orange > set C=Blue > if %A%==%C% echo Strings A and C match Strings A and C match > if %A%==%B% (echo Same!) else echo Different! Different!
There are four main types of tests available:
Tests that a filename exists (regardless of the entry type: file or directory):
EXIST filename
Test for string equality:
''string1''==''string2''
These tests accept two string arguments, both strings or both integers, which are compared. Adding the /i switch will make string comparisons case-insensitive (UPPER/lowercase).
value1 EQU value2 True if the values are equal value1 NEQ value2 True if the values are not equal value1 LSS value2 True if the value1 less than value2 value1 LEQ value2 True if the value1 less/equal to value2 value1 GTR value2 True if the value1 less/equal to value2 value1 GEQ value2 True if the value1 less/equal to value2
To force a string comparison, enclose value1
and value2
in quotes. Otherwise, the shell will determine if the variables appear to contain integer values and compare them as integers, or otherwise compare them as strings.
Test to see if a variable is defined:
DEFINED variable True if variable is defined
Test to see if the ERRORLEVEL is above a threshold:
ERRORLEVEL value True if ERORRLEVEL>=value
Although it's probably better to just an integer comparison such as: %ERRORLEVEL% GEQ value
IF
commandGOTO
and a label:IF test GOTO :skip
... :skip
Note that using a GOTO in a loop will make the shell forget about the loop, regardless of where the label is located!
You can negate (invert) a test with the NOT operator:
IF NOT EXIST %N% ECHO The file %N% does not exist.
Note that you cannot combine tests - there is no AND
or OR
operator.
Arguments to a script are called parameters. You can access the parameters using the special variables %0
, %1
, %2
, and so forth. %0
contains the name of the script, %1
contains the first parameter, %2
contains the second parameter, and so forth. Note that there is no second percent sign after the parameter number.
The shift command gets rid of the first parameter and shifts every parameter to a lower number.
Examples:
> type params.cmd @echo off ECHO PARAM 0: %0 ECHO PARAM 1: %1 ECHO PARAM 2: %2 > params red green blue PARAM 0: params PARAM 1: red PARAM 2: green > type params-shift.cmd @echo off ECHO List of all arguments: %* :start IF "%1"=="" GOTO :done ECHO %1 SHIFT GOTO :start :done > params-shift yellow orange red List of all arguments: yellow orange red yellow orange red
The special variable %*
returns all of the parameters.
On the command line, parameters may be separated by:
In the Windows CMD shell, looping is performed with the FOR
statement.
Loops are controlled by an iterator variable, which has special rules:
When the body of a FOR
loop is executed, the variables are expanded (replaced by their values) before the loop begins. That means that any variables that are contained in the loop have their values locked-in and they can't be changed while the loop is executing.
To allow updated variable values to be accessed within a loop:
1, Set the EnableDelayedExpansion option:
SETLOCAL EnableDelayedExpansion
2. Change any variables which will be updated during the execution of the loop by replacing the percent-signs ( % ) with exclaimation-marks ( ! ):
%ERRORLEVEL% -> !ERRORLEVEL!
To loop through a list of values such as filenames, use:
FOR %variable IN (files) DO list
The variable will be sequentially set to each of the given files values, and the loop body (list) will be executed once for each value.
The files value may be:
FOR %F IN (file1.txt file2.pdf file3.c) DO ECHO %F
FOR %F IN (*.pdf) DO ECHO %F
FOR %C IN (Red Green Blue) DO ECHO %C
FOR %P IN (%*) DO ECHO %P
The /D
option can be used along with a filename pattern to match only directories:
FOR /D %variable IN (files) DO list
@echo off SETLOCAL EnableDelayedExpansion FOR F IN (*) DO ( rem The CHOICE command presents a Y/N choice and sets %ERRORLEVEL% rem to 1 if the user selected Y and 2 if the user selected N CHOICE /M "DELETE F“
IF !ERRORLEVEL!==1 ( ECHO ...Deleting %%F DEL %%F ) ELSE ( ECHO ...Skipping %%F )
)
FOR /L (start, step, end) DO list
This type of loop counts forward or backwards from start
to end by a given step.
Example:
@echo off rem Count from 0 to 5 in increments of 1 FOR /L %%I IN (0, 1, 5) DO ECHO ... %%I ... rem Count from 4 to 0 in increments of -1 FOR /L %%I IN (4, -1, 0) DO ECHO ... %%I ...
Output:
0 1 2 3 4 5 <- This is the end of output from the first loop, having gone from 0 to 5 4 <- The second loop starts output here, going from 4 to 0 3 2 1 0
@echo off rem intcmp.cmd - compare as integers SET /A A=11,B=2 IF %A% GTR %B% ( ECHO %A% is greater than %B% ) ELSE ECHO %A% is less than or equal to %B%
@echo off rem strcmp.cmd - compare as strings SET /A A=11,B=2 IF "%A%" GTR "%B%" ( ECHO %A% is greater than %B% ) ELSE ECHO %A% is less than or equal to %B%
@echo off SET /A COIN=%RANDOM% %% 2 IF %COIN% EQU 0 (ECHO Heads!) ELSE ECHO Tails