I’m converting a shell script to a Windows batchfile and had to search up a few things to fill in the gaps. These are notes for me to be able to find these details again.
Comments
This is simple.
- Official way to have comments:
REM
- Also, supported way:
::
Echo a Blank Line
You use ECHO some text
to print a line on the console. I like to have a few blank lines between paragaphs in a wall of text. Doing just echo
does nothing useful in this case since it returns the status of whether echo-ing is turned on or off (e.g., ECHO is on
or off).
The way to handle this is echo.
this gives you a blank line.
If this is your script blank.cmd
:
@echo off
echo Line before blank
echo.
echo Line after blank
You get this output:
$ blank.cmd
Line before blank
Line after blank
Check if an environment variable is defined
In a shell script, you’re likely to see:
# Make sure SPECIAL_ENV_VAR is set
if [ -z "$SPECIAL_ENV_VAR" ]; then
echo "Error: 'SPECIAL_ENV_VAR' not set."
exit 1
fi
You can do this instead:
:: Make sure SPECIAL_ENV_VAR is set
if not defined SPECIAL_ENV_VAR (
echo SPECIAL_ENV_VAR not set. Cannot proceed.
)else (
echo SPECIAL_ENV_VAR set. Proceeding.
)
Be very careful about 2 things:
- If a
)
appears at the end of a line, it may be seen as the end of theif
segment. So, avoid statements likeecho (see readme.md)
- The
)
that closes theif
should always come after a line/ command that is not commented out
Echo multiple lines to a file
Sometimes, you want to able to output multiple lines to a file. The way to do this is to put the multiple echo
statements between parentheses as below.
@echo off
echo The rest of the lines below will be written to file.txt
(
echo The path currently is:
echo SYSTEMROOT=%SystemRoot%
echo.
echo That looks good.
) > file.txt
This is what file.txt
looks like when you run it:
$ type file.txt
The path currently is:
SYSTEMROOT=C:\WINDOWS
That looks good.
Of course, you can append into the file (instead of replacing its contents) by doing >> file.txt
instead of > file.txt
Exit the script, not CMD.exe
It’s common to have a exit
in the scripts. In Windows command files, this will exit the command shell that you’re running from. You can use exit /B <return-code>
instead to just exit the script not the shell.
exit /B 1
Standard Output and Standard Error Streams
You’ll often come across code that looks like 2>&1
which works the same in Windows. It will send the stderr (2) to the same place as stdout (1). Strictly speaking, it will send the data to a copy of the handle used by stdout.
Some common usage is as below:
- This will send the output to
file.txt
and will also redirect all errors (2) to a copy of stream 1, i.e., tofile.txt
.
$ my_command.exe > file.txt 2>&1
- You can explicitly set 1 and 2 in the same way:
$ my_command.exe 1> file.txt 2>&1
- You can send both to NUL (same as /dev/null) by doing:
$ my_command.exe > NUL 2>&1
Note that since 2>&1
asks to send it to a copy of the stream for 1, the position matters. If you do 2>&1 1>NUL
, it means that 2 will go the console and 1 will thereafter go to NUL. If you do 1>NUL 2>&1
it means that 1 will go to NUL and 2 will thereafter also go to 1, i.e., both go to NUL. There is more on this on stackoverflow
Find which executable(s) are on path
Linux/ Unix users will often tell you do which
to find the executable on the path. Windows uses where
instead and was the cause of my first patch to Rails. Here is the simple use of it.
$ where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
$ where ruby.exe
D:\Ruby33-x64\bin\ruby.exe
D:\Ruby32-x64\bin\ruby.exe
$ where where.exe
c:\Windows\System32\where.exe
< -- Let's nuke the path so that ruby is no longer on the path -- >
$ path=c:\windows\system32
$ where ruby.exe
INFO: Could not find files for the given pattern(s).
Couple of other helpful things
There are a couple of other things that I had previously documented:
- Navigating to the path of a script, and using pushd / popd
- Redirect all output to /dev/null on Windows Command
- Go home on Windows
Hope this helps. I expect that there will be a second part at some point if I need to do this more although I often just end up with a simple Ruby script instead of jumping through bash/ shell/ command scripts.