- Using ANSI Colors in echo
- Variables
- Code snippets
- Functions
- License
You can combine colors like [30m[43m
where 30
is the font-color (black) and 43
the background-color (yellow).
@ECHO off
cls
ECHO.
ECHO [30m[43mSomething written in BLACK (with a YELLOW background for visibility)[0m
ECHO [31mSomething written in RED[0m
ECHO [32mSomething written in GREEN[0m
ECHO [33mSomething written in YELLOW[0m
ECHO [34mSomething written in BLUE[0m
ECHO [35mSomething written in PURPLE[0m
ECHO [36mSomething written in CYAN[0m
ECHO [37mSomething written in WHITE[0m
ECHO.
ECHO [40mSomething with a BLACK background[0m
ECHO [41mSomething with a RED background[0m
ECHO [42mSomething with a GREEN background[0m
ECHO [43mSomething with a YELLOW background[0m
ECHO [44mSomething with a BLUE background[0m
ECHO [45mSomething with a PURPLE background[0m
ECHO [46mSomething with a CYAN background[0m
ECHO [47m[31mSomething with a WHITE background (written in RED for visibility)[0m
See also https://gist.github.com/mlocati/fdabcaeb8071d5c75a2d51712db24011 for a few more like bold, underline, ...
Variable | Description |
---|---|
%~0 |
In a function, display the name of the function |
%~dfp0 |
Return the full name of the running script (return f.i. c:\temp\a.cmd ) |
%~n0%~x0 |
Return the base name of the running script (return f.i. a.cmd ) |
%~dp0 |
Return the parent folder name of the running script (return c:\temp\ when the executed script is c:\temp\a.cmd ). This is different to the current directory. |
%cd% |
Return the current directory |
%CMDCMDLINE% |
Allow to determine if the script has been fired from a DOS prompt ("C:\WINDOWS\system32\cmd.exe" ) or with a double-click from f.i. the file explorer (C:\WINDOWS\system32\cmd.exe /c ""C:\temp\script.bat" " ) |
%DATE% |
The system date |
%ERRORLEVEL% |
The error level returned by the last executed command, or by the last called batch script |
%RANDOM% |
A generated pseudo-random number between 0 and 32767 |
%TIME% |
The system time in HH:MM:SS.mm format |
This is useful for, f.i., debugging purpose (saying which function is responsible for which action/ECHO);
@echo off
cls
call :showhelp :show
exit/b
:showhelp
ECHO You are in function %~0
GOTO:EOF
Suppose your script is started with a parameter like this: run.cmd "C:\Program Files\Windows Photo Viewer\readme.txt"
.
The parameter needs double-quotes here since the full name contains spaces.
The solution is to use %THE_VARIABLE:"=%
i.e. the name of the variable followed by a :
, the character to trim "
and ended by =
.
@ECHO off
cls
SET INPUT="C:\Program Files\Windows Photo Viewer\readme.txt"
ECHO Not filtered: %INPUT%
SET FILTERED=%INPUT:"=%
ECHO Filtered : %FILTERED%
Count the number of arguments passed to the script.
This code can't be put in a function since, then, we need to pass arguments to the function and we don't know how many arguments are there.
set argcount=0
for %%i in (%*) do set /a argcount+=1
ECHO The number of arguments is %argcount%
choice
allow to prompt user input. When the choice is a list of options (yes/no or like below 1/2/3/0), the selected option index can be retrieved by reading the %ERRORLEVEL%
variable.
@ECHO OFF
CHOICE /C 1230 /M "Press 1 ... or 2 ... or 3 ... or 0 to quit"
IF "%ERRORLEVEL%"=="1" SET ANSWER=You've choosen for 1
IF "%ERRORLEVEL%"=="2" SET ANSWER=You've choosen for 2
IF "%ERRORLEVEL%"=="3" SET ANSWER=You've choosen for 3
IF "%ERRORLEVEL%"=="4" SET ANSWER=You've choosen to cancel
ECHO Your choice: %ANSWER%
When you DOS script makes some echos like showing an error message, if the user has double-click on the script through f.i. Windows Explorer, he'll not be able to see the message: the DOS window is closed automatically at the end of the execution.
The snippet below will add a pause
to the script in that situation.
Just add this code at the end of your batch.
REM Try to detect if the script was started by double-clicking on it from, f.i., Windows explorer.
REM This is the case when the intern variable %cmdcmdline% contains the "/c" parameter.
REM Note: %cmdcmdline% is empty if the script has been fired from DOS otherwise, looks like this:
REM C:\WINDOWS\system32\cmd.exe /c ""C:\...\docker-up.bat" "
REM When "/c" is found, ERRORLEVEL is set to 0 so, it's Windows.
REM https://stackoverflow.com/a/12036163/1065340
echo %cmdcmdline% | find /i "/c" >nul 2>&1
if %errorlevel% EQU 0 (
echo.
pause
)
Get the list of files in the current folder (in the example) and process files one by one.
@ECHO off
cls
for %%f in (*.*) do (
ECHO %%f
)
How to extract the xxx first characters of a string.
Imagine a directory structure where each your coding projects are stored in their own folder and you're using a php_
prefix to sort projects based on the most used language:
@echo off
SET folder=C:\Folder\php_MyProject
SET PREFIX=%folder:~0,4%%
IF %PREFIX% EQU "php_" (
ECHO "That folder is a PHP project"
)
See https://en.wikibooks.org/wiki/Windows_Batch_Scripting#FOR for much more way to make a loop
Simple loop, from 1 till 5
@ECHO off
cls
for %%i in (1,2,3,4,5) do (
ECHO %%i
)
Loop from 1 till 20, only odd numbers
@ECHO off
cls
for /l %%i in (1,2,20) do (
ECHO %%i
)
With the for /f
construct like below, we can process a file line by line, like below:
@ECHO off
cls
for /f "tokens=*" %%l in (readme.md) do (
ECHO %%l
)
How to extract the xxx last characters of a string.
@echo off
SET folder=C:\Folder\SubFolder.wiki
SET SUFFIX=%folder:~-5%
IF %SUFFIX% EQU ".wiki" (
ECHO "The foldername ends with .wiki"
)
Let's take a real life example: your batch will run an external program and redirect the output to a text file so, just before leaving the batch, you can open the file with Notepad++ but ... how can you make your code generic and open notepad.exe if notepad++ isn't installed?
Here is the solution:
First make sure that the folder where Notepad++ is well mentioned in your system environment variables (i.e. in PATH
).
Then check the code below; mainly the IF EXISTS
block. The FOR /F ...
line is quite strange but allow to run the where notepad++.exe
command and redirect the output into the notepad
variable.
- If notepad++ is found in the
PATH
,%%g
will be initialized to the full path of the program; - If notepad++ is not found, the code won't reset the notepad variable and thus keep the default
notepad.exe
value.
@ECHO OFF
SETLOCAL EnableDelayedExpansion
SET outputFile=%tmp%\test.log
REM Any statements... the objective here is just to create a log file
DIR *.* > %outputFile%
REM Open the log
IF EXIST %outputFile% (
SET notepad=notepad.exe
WHERE "notepad++.exe" /Q
IF !errorlevel!==0 (
FOR /F "tokens=*" %%g IN ("where notepad++.exe") do (
SET notepad=%%g
)
)
REM Start notepad++.exe is present otherwise start notepad.exe
START "!notepad!" %outputFile%
)
ENDLOCAL
For this purpose, just use the %errorlevel%
internal value.
Below an example, idea is to validate a list of mandatory parameters. If the check is successful, return 0, if anything goes wrong, return -1 (or any code in fact).
REM Check parameters and make sure mandatory parameters have been set
call :checkParams %1 %2 %3
if %errorlevel% == 0 (
ECHO "Great, parameters have been set"
)
::--------------------------------------------------------
::-- checkParams - Make sure this script is called with the
:: required parameters. In case of errors, the help is
:: displayed and the script will be ended
:: return 0 when success, -1 otherwise
::--------------------------------------------------------
:checkParams
SET bContinue=0
IF "%1"=="" (
ECHO "Please specify a value for all required parameters."
SET bContinue=-1
) ELSE (
IF "%2"=="" (
ECHO "You have mention only the first parameter. Please also do this for the second and third one."
SET bContinue=-1
) ELSE (
IF "%3"=="" (
ECHO "You forget to set the third parameter."
SET bContinue=-1
)
)
)
exit /b %bContinue%
A function parameter is retrieved by using the %~
syntax; followed by a number to get the first parameter, the second one, ...
For instance (partial example; not executable as is)
@ECHO off
REM Call doIit with two parameters, we can imagine that the first
REM parameter can be any extension and the second is an action
REM like open/copy/print/...
CALL :doIt "PDF" "OPEN"
EXIT/B
:doIt
ECHO Generate %~1 files and, when done, %~2 them
GOTO:EOF
Create a symbolic link only if the file doesn't exists yet or is different.
The example below will use the file c:\master\git_check_status.cmd
as the master one.
If the file git_check_status.cmd
didn't exists yet in the current folder or if the content of that file is different, the file will be (re) created: a symbolic link will be made to the master one.
@ECHO off cls
call :fnFileCreateLink "git_check_status.cmd" "c:\master\git_check_status.cmd"
GOTO END:
::-------------------------------------------------------- ::-- fnFileCompare: Compare two files :: %1 = Full name of the first file :: f.i. "C:\Christophe\Repository\push_wiki.cmd" :: %2 = Full name of the second file :: f.i. "C:\Christophe\Repository\master\push_wiki.cmd" :: :: Return "0" when files are identical; "1" otherwise ::-------------------------------------------------------- :fnFileCompare
SET current=%1
SET master=%2
SET FileCompare="0"
REM /C - case insensitive
REM /L - compare as ASCII text
REM /W - compress whitespace and tabs for comparison
fc /C /L /W "%current%" "%master%" > nul
REM 0 = files are identical
REM 1 = files are different
REM 2 = at least one file didn't exists
IF %ERRORLEVEL% NEQ 0 SET FileCompare="1"
GOTO:EOF
::-------------------------------------------------------- ::-- fnFileCreateLink: Create a file that is a symbolic :: link to another one :: :: %1 = Full name of the file to create :: f.i. "C:\Christophe\Repository\push_wiki.cmd" :: %2 = Full name of the master file :: f.i. "C:\Christophe\Repository\master\push_wiki.cmd" :: :: Note: if the file is already present, a file compare will be :: made so the file will be "recreated" only if there is :: a difference with the master one. ::-------------------------------------------------------- :fnFileCreateLink
SET current=%1
SET master=%2
SET CreateFile="Y"
IF EXIST "%current%" (
REM The file already exists, check if different of the master
call :fnFileCompare "%current%" "%master%"
IF !FileCompare! EQU "0" (
REM Files are identical, nothing to dy
SET CreateFile="N"
)
)
IF %CreateFile% EQU "Y" (
IF EXIST "%current%" DEL "%current%"
MKLINK "%current%" "%master%"
)
GOTO:EOF
:END
Compare two files, return 0
when files are identical, 1
when there is at least one difference.
Get the absolute path from a relative file.
@ECHO off
cls
CALL :getAbsolutePath %~dp0..\..\..\..\..\..\autoexec.bat
REM Display C:\autoexec.bat
ECHO %AbsolutePath%
GOTO END:
::--------------------------------------------------------
::-- getAbsolutePath - Make a path absolute, like reapath() does
::-- %1 A filename
::--
::-- Return "C:\Temp\test.cmd" when %1 is "C:\Folder\..\Temp\test.cmd"
::--------------------------------------------------------
:getAbsolutePath
SET AbsolutePath=%~dpfn1
GOTO:EOF
:END
Remove any folder in a variable filename and return only the filename but without the extension.
@ECHO off
cls
CALL :getBaseNameWithoutExtension C:\Folder\SubFolder\test.txt
REM Display test
ECHO Basename without extension is "%BaseNameWithoutExtension%"
GOTO END:
::--------------------------------------------------------
::-- getBaseNameWithoutExtension - Get basename of a file w/ extension
::-- %1 A filename
::--
::-- Return "test" when %1 is "C:\Folder\SubFolder\test.txt"
::--------------------------------------------------------
:getBaseNameWithoutExtension
SET BaseNameWithoutExtension=%~n1
GOTO:EOF
:END
Remove any folder in a variable filename and return only the filename (with extension).
@ECHO off
cls
CALL :getBaseNameWithExtension C:\Folder\SubFolder\test.txt
REM Display test.txt
ECHO Basename with extension is "%BaseNameWithExtension%"
GOTO END:
::--------------------------------------------------------
::-- getBaseNameWithExtension - Get basename + ext of a file
::-- %1 A filename
::--
::-- Return "test.txt" when %1 is "C:\Folder\SubFolder\test.txt"
::--------------------------------------------------------
:getBaseNameWithExtension
SET BaseNameWithExtension=%~nx1
GOTO:EOF
:END
Get the full name of a file that is stored in one of the folder mentioned in the %PATH%
.
If the file can't be found in the %PATH%
, return an empty string.
@ECHO off
cls
CALL :getFileNameFromPATH notepad++.exe
REM If Notepad++.exe is in the PATH, return the full name
REM of the file f.i. C:\Program Files\Notepad++\notepad++.exe
ECHO File name is %FileNameFromPATH%
GOTO END:
::--------------------------------------------------------
::-- getFileNameFromPATH - Get the full name of a file
::-- that is present in the PATH. Return the first occurrence
::-- If the file isn't found, returns an empty string
::--
::-- %1 A filename
::--
::-- Return a number, the size in bytes
::--------------------------------------------------------
:getFileNameFromPATH
SET FileNameFromPATH=%~$PATH:1
GOTO:EOF
:END
Get the file size in bytes.
@ECHO off
cls
CALL :getFileSize C:\Temp\test.bat
REM Display the filesize
ECHO File size is %FileSize%
GOTO END:
::--------------------------------------------------------
::-- getFileSize - Get the file size in bytes
::-- %1 A filename
::--
::-- Return a number, the size in bytes
::--------------------------------------------------------
:getFileSize
SET FileSize=%~z1
GOTO:EOF
:END
Return the file's extension.
Note: when the file has multiple extensions like .xlsx.bak
, only the last extension is returned.
@ECHO off
cls
CALL :getFileExtension C:\file.xlsx
REM Display xlsx
ECHO File extension is "%FileExtension%"
GOTO END:
::--------------------------------------------------------
::-- getFileExtension - Get the file's extension
::-- %1 A filename
::--
::-- Return "xlsx" when %1 is "C:\file.xlsx"
::--------------------------------------------------------
:getFileExtension
SET FileExtension=%~x1
GOTO:EOF
:END
Return the folder of a file / sub-folder
@ECHO off
cls
CALL :getFolderName C:\Folder\SubFolder\test.txt
REM Display C:\Folder\SubFolder\
ECHO %FolderName%
GOTO END:
::--------------------------------------------------------
::-- getFolderName - Get the foldername of a file
::-- %1 A filename
::--
::-- Return "C:\Folder\SubFolder" when %1 is "C:\Folder\SubFolder\test.txt"
::--------------------------------------------------------
:getFolderName
SET FolderName=%~dp1
GOTO:EOF
:END
Return the parent folder of a folder
@ECHO off
cls
CALL :getParentFolderName C:\Folder\SubFolder\
REM Display C:\Folder\
ECHO %ParentFolderName%
GOTO END:
::--------------------------------------------------------
::-- getParentFolderName - Get the parent foldername of a folder
::-- %1 A foldername
::--
::-- Return "C:\Folder\" when %1 is "C:\Folder\SubFolder\"
::--------------------------------------------------------
:getParentFolderName
SET Folder=%1
FOR %%a IN ("%Folder:~0,-1%") DO SET ParentFolderName=%%~dpa
GOTO:EOF
END:
Consider the following situation:
- You've a generic script called
C:\Folder\my_script.cmd
- You've create a symbolic link
C:\Folder\SubFolder\test.cmd
to that file (called thetarget path
).
By running C:\Folder\SubFolder\test.cmd
and displaying the script fullname (ECHO %~dfp0
), you'll obtain C:\Folder\SubFolder\test.cmd
which is the symbolic link.
How can you retrieve the target file? The following function will return that info.
@ECHO off
cls
REM %~dfp0 is the current script filename and thus C:\Folder\SubFolder\test.cmd
CALL :getSymLinkTargetPath %~dfp0
REM Display C:\Folder\my_script.cmd
ECHO %SymLinkTargetPath%
GOTO END:
::--------------------------------------------------------
::-- getSymLinkTargetPath - When a file is a symlink, return the
::-- target path i.e. the original path of the file
::--
::-- %1 A filename that is a symlink to another file
::--
::-- Return "C:\Christophe\...\generate.cmd" f.i. when %1 is
::-- is a symbolic link to that file, no matter where the file is located
::--------------------------------------------------------
:getSymLinkTargetPath
SET FILE=%1
SET TargetPath=""
SET fileDirInfo=""
IF EXIST %tmp%\symlinks.tmp (
DEL %tmp%\symlinks.tmp
)
REM When using "DIR filename", we get something like below when
REM the file is a symlink.
REM 16-10-19 13:18 <SYMLINK> phan.bat [C:\Christophe\phan.bat]
REM
REM Using the findstr pipe will allow us to check if the file is a
REM symlink
REM Output that line in the .tmp file so we can read the file after
REM and process the string as a DOS string variable
dir %FILE% | findstr "<SYMLINK>" > %tmp%\symlinks.tmp
IF EXIST %tmp%\symlinks.tmp (
SET /p fileDirInfo=<%tmp%\symlinks.tmp
REM Here, fileDirInfo, contains the following:
REM 16-10-19 13:18 <SYMLINK> phan.bat [C:\Christophe\phan.bat]
REM Extract here the portion between brackets => we'll extract part2
for /f "useback tokens=1,2,3 delims=[]" %%a in ('!fileDirInfo!') do (
set "TargetPath=%%b"
)
)
REM Done, we've our original file (C:\Christophe\phan.bat)
SET SymLinkTargetPath=%TargetPath%
GOTO:EOF
:END
Detect if the script has been executed from a DOS Prompt fired under admin privileges.
call :fnIsAdminMode
IF %isAdmin% EQU "0" (
END "Please start this script with admin privileges"
GOTO END
)
REM ... Ok, the script can continue
GOTO END
::--------------------------------------------------------
::-- fnIsAdminMode: Detect if the CMD prompt has been started
:: with "Run as admin" or not
:: Return "1" when admin mode set; "0" otherwise
::--------------------------------------------------------
:fnIsAdminMode
SET isAdmin="0"
openfiles >nul 2>&1
REM If not equal to 0, command prompt not run under admin privileges
IF %ERRORLEVEL% EQU 0 SET isAdmin="1"
GOTO:EOF
END:
Detect is a folder is empty or not.
@ECHO off
cls
CALL :fnIsEmptyFolder C:\Folder
IF "%isEmpty%" EQU "1" (
ECHO "The folder is empty"
) ELSE (
ECHO "There is at least one file in the folder"
)
GOTO END:
::--------------------------------------------------------
::-- fnIsEmptyFolder: Check if a folder is empty or not
:: Return 1 when empty, 0 if there is at least one file
::--------------------------------------------------------
:fnIsEmptyFolder
SET isEmpty=1
for /f "delims=" %%a in ("dir /b %1") do (
REM At least one file
SET isEmpty=0
)
GOTO:EOF
:END