· 6 years ago · Oct 15, 2019, 07:24 PM
1# LEARN HOW TO BATCH
2
3# Table of contents
4
5> * [What you have to know before we start](#what-you-have-to-know-before-we-start)
6> - [Change your mindset & be patient - Easy is gonna become hard](#change-your-mindset-&-be-patient---easy-is-gonna-become-hard)
7> - [The .bat extension](#the-bat-extension)
8> - [There are only 2 types: String and Number](#there-are-only-2-types-string-and-number)
9> - [Avoid spaces](#avoid-spaces)
10> - [Use `setlocal enabledelayedexpansion`](#use-setlocal-enabledelayedexpansion)
11> - [Script returned value - 0 vs 1](#script-returned-value---0-vs-1)
12> - [Avoid blocks as much as possible](#avoid-blocks-as-much-as-possible)
13> - [You should organise your script using subroutines](#you-should-organise-your-script-using-subroutines)
14> * [Getting started](#getting-started)
15> - [Basic script example](#basic-script-example)
16> - [Subroutines](#subroutines)
17> - [Basic](#basic)
18> - [Intermediate](#intermediate)
19> - [Advanced - Returning multiple values](#advanced---returning-multiple-values)
20> - [String manipulation](#string-manipulation)
21> * [How to](#how-to)
22> - [How to set a variable from a command output?](#how-to-set-a-variable-from-a-command-output)
23> * [Annex](#annex)
24> - [Special variables](#special-variables)
25
26# What you have to know before we start
27## Change your mindset & be patient - Easy is gonna become hard
28
29You're dealing with some super primitive shit here. The cmd.exe compiler is extremely primitive and what is trivial to do in other language is now gone. This is why I'm writing this document. My mission is to help you swear less at your machine than what I did when I had to learn this horrible piece of shit crap from Microsoft.
30
31## The .bat extension
32
33To run a batch script, simply enter the name of your `.bat` file in the terminal:
34
35```
36> hello.bat
37```
38This executes the scripts defined in the `hello.bat` file.
39
40## There are only 2 types: String and Number
41Forget about booleans, objects, ... The following does not set the `var_01` to a truthy boolean. Instead, it sets the string value `true`.
42
43```
44set var_01=true
45```
46
47## Avoid spaces
48
49Batch scripts have a really fucked up unpredictable way of dealing with spaces, so do your best to not use them:
50
51```bat
52:: Don't do this
53set hello = World
54
55:: Instead, use this
56set hello=World
57```
58
59## Use `setlocal enabledelayedexpansion`
60
61> I MEAN IT, JUST FREAKING USE THIS. Trust me, use this, otherwise, your code will start generating really randomly.
62
63### TL;DR
64
65If your script mutates variable, and if those variables are used in IFs, then if you don't use this set up, the variables will not be set sequentially based on your code execution. You'll be scratching your heads for hours wondering why your code is not working and you'll see weird stuff when you echo your variables.
66
67__*Before delayed expansion*__
68
69```bat
70@echo off
71set hello=world
72echo %hello%
73```
74
75__*After delayed expansion*__
76
77```bat
78@echo off
79setlocal enabledelayedexpansion
80
81set hello=world
82echo !hello!
83
84endlocal
85```
86
87### Explanation
88
89Delayed Expansion will cause variables within a batch file to be expanded at execution time rather than at parse time, this option is turned on with the SETLOCAL EnableDelayedExpansion command.
90
91Variable expansion means replacing a variable (e.g. %windir%) with its value C:\WINDOWS.
92
93
94## Script returned value - 0 vs 1
95
96A successful script returns 0 while an unsuccessful one returns 1. This value is stored inside the global `ERRORLEVEL` variable, so you can check it after the script execution to determine whether the script was successful or not.
97
98## Avoid blocks as much as possible
99
100The cmd.exe compiler likes reading commands that start and end on the same line. If you think that your code inside an if block is only executed if the condition is true, then be ready for a big surprise. The following will most probably (most probably because is not that predictable, it may actually work) make your code exit:
101
102```bat
103set do_not_do_this=true
104if do_not_do_this == false (
105 echo Mate, do not do this!
106 exit /b 1
107)
108
109echo Great mate! You did the right thing
110```
111
112This because the compiler will reads each line inside the block. The following 2 options are more predictable:
113
114```bat
115set do_not_do_this=true
116if do_not_do_this == false echo Mate, do not do this!
117if do_not_do_this == false exit /b 1
118
119echo Great mate! You did the right thing
120```
121
122or
123
124```bat
125set do_not_do_this=true
126if do_not_do_this == false (goto :ok) else (goto :notok)
127
128:ok
129echo Great mate! You did the right thing
130goto :EOF
131
132:notok
133echo Mate, do not do this!
134exit /b 1
135```
136
137## You should organise your script using subroutines
138
139Batch is so primitive that you will eventually need to use subroutines, so better learn how to structure your script from the beginning of your learning journey. To see how to get started with subroutines, please refer to the [Subrountines](#subroutines) section.
140
141# Getting started
142## Basic script example
143
144```bat
145:: This is a comment.
146
147:: This is how you prevent the terminal to print the entire set of commands below.
148@echo off
149setlocal enabledelayedexpansion
150
151:: This is how you get an array of all the arguments.
152echo %*
153
154:: This is how you access any arguments passed to the script.
155:: A new argument occurs each time there is a new space.
156echo %1
157echo %2
158
159:: Prints the name of the .bat file
160echo %0
161
162:: This is how to define a variable
163:: WARNING: Though it looks like we're setting a boolean, that's not the case. Remember, the only 2
164:: supported types in batch are numbers and string. The line below assigns the string 'false' to the
165:: variable hello.
166set hello=false
167
168:: This is how you store arithmetic values. If you don't use the /a flag, the
169:: arithmetic ops is interpreted as a string.
170set /a numerator=1+2
171
172:: Reference a expandded variable
173echo !hello!
174:: Reference a non-expanded variable
175echo %hello%
176
177:: This is how to do an IF. Notice you need to wrap the %1 between double quotes
178:: to be able to compare against an empty string to check wether it exists or not.
179:: This is to cover the use case where %1 does not exist. If we don't do this and %1
180:: does not exist, an error similar to: Incorrect syntax if neq "".
181:: Also, notice the "neq" operator. Something like != does not exist :(
182if "%1" neq "" (
183 echo Hoorayyyyy, we have a value.
184) else (
185 echo Oh shit, no value
186)
187
188:: This is how you do a logical AND. The "&&" or "&" do not exist in batch.
189:: NOTICE:
190:: - The use the "==" logical equivalence. You could also use "equ" instead.
191:: - 'exit /b 1' is how you properly exit the execution. The 1 is optional but recommended.
192:: This allows other scripts to determine whether this script was successful or not.
193:: (successful scripts return 0)
194:: - The pattern <command> & exit /b 1. DO NOT use block with 2 lines (one for the
195:: echo and one for the exit)instead, as this can have unpredictable results.
196:: Remember, in batch, coding in one line rules.
197if "%1" == "" if "%2" == "" echo ERROR: Missing required argument --name. & exit /b 1
198
199:: This is how you do a logical OR. The "||" or "|" do not exist in batch.
200set or_val=false
201if "%1" neq "" set or_val=true
202if "%2" neq "" set or_val=true
203
204:: Get into the habit to write if else on a single line. The compiler seems to like this better.
205if or_val equ true (echo OR is ok) else (echo OR is NOT ok)
206
207:: This is how you do a 'for' loop.
208:: NOTICE:
209:: - With an the output of a command, wrap it between single quotes (e.g., list of files: dir /b /a-d).
210:: - The /f option must be used to store items from an array (technically, it stands for files).
211for /f %%x in ('dir /b /a-d') do echo %%x
212
213endlocal
214exit /b 0
215```
216
217## Subroutines
218### Basic
219Batch is so primitive that you will eventually need to use subroutines, so better learn how to structure your script from the beginning of your learning journey.
220
221The following script outputs something similar to this:
222
223```
224> test.bat hello world
225CORE ARGS: hello world
226MAIN ARGS: world hello
227```
228
229_test.bat:_
230
231```bat
232@echo off
233
234echo CORE ARGS: %1 %2
235
236:: Notice how you must end with '& exit /b' to make sure the rest of the script is not executed.
237call :main %2 %1 & exit /b
238
239:main
240:: Notice how you still use the %1 convention to get params
241echo MAIN ARGS: %1 %2
242exit /b
243```
244
245### Intermediate
246
247```bat
248@echo off
249
250echo CORE ARGS: %1 %2
251
252call :main %2 %1 & exit /b
253
254:main
255setlocal enabledelayedexpansion
256echo MAIN ARGS: %1 %2
257
258:: Succeeding routine
259call :divide 6 2 result
260if !errorlevel! neq 0 exit /b !errorlevel!
261echo 6 divided by 2 is equal to %result%
262
263:: Failing subroutine
264call :divide 6 0 result
265if !errorlevel! neq 0 exit /b !errorlevel!
266echo 6 divided by 1 is equal to %result%
267
268endlocal
269exit /b
270
271
272:divide
273setlocal enabledelayedexpansion
274
275set /a numerator=%1
276set /a denominator=%2
277
278if "!denominator!" == "0" echo ERROR - Can't devide by 0 & exit /b 1
279
280set /a result=!numerator!/!denominator!
281
282:: NOTICE:
283:: - The pattern used to set the returning result: set %3=%result%
284:: Notice that we did not use set %3=!result!. I have no clue why, but you can
285:: only use %...% when setting the return value. Setting up %3 is not the best
286:: practice though. Refer to the next section for a more advanced example.
287:: - The pattern used to maintain the result value from the setlocal scope while
288:: at the same time setting it to %3. If you had seperated the 'endlocal' and
289:: 'endlocal' in 2 lines, the value stored in result would had been lost.
290endlocal & set %3=%result%
291exit /b
292```
293
294### Advanced - Returning multiple values
295
296The previous example demonstrated how to return a value by explicitly setting `%3`. However, this can create problem. Take for example the following sample:
297
298```bat
299call :my_routine %1 %2 result
300echo %result%
301exit /b
302```
303
304Setting `%3` in `:my_routine` only works if it is 100% garanteed that both `%1` and `%2` exist, because if, for example, `%2` is not set, then as far as `:my_routine` is concerned, `result` is similar to `%2`, not `%3`. In that case, when `:my_routine` sets `%3`, it does not set `result`.
305
306A better solution is to return an explicit variable as follow:
307
308```bat
309call :my_routine %1 %2
310echo %result%
311exit /b
312
313:my_routine
314setlocal enabledelayedexpansion
315:: do stuff
316set example=hello world
317endlocal & set result=%example%
318exit /b
319```
320
321It is also trivial to return many results:
322
323```bat
324call :my_routine %1 %2
325echo %result_01%
326echo %result_02%
327exit /b
328
329:my_routine
330setlocal enabledelayedexpansion
331:: do stuff
332set example_01=hello world
333endlocal & set result_01=%example_01% & set result_02=What's up
334exit /b
335```
336
337## String manipulation
338
339```bat
340set sample=hello world
341
342:: Getting a sub-string
343:: this outputs hell
344echo %sample:~0,4%
345:: this outputs world
346echo %sample:~6,200%
347:: this outputs hello wor
348echo %sample:~0,-2%
349```
350
351# How to
352## How to set a variable from a command output?
353
354```bat
355setlocal enabledelayedexpansion
356
357:: This exe is IIS 7.0
358set appCmd="!WINDIR!\System32\inetsrv\appcmd.exe"
359set website_name=demo_01
360
361set website_status=Unknown
362for /f %%x in ('!appCmd! list site !website_name! /text:state') do set website_status=%%x
363
364echo The status of the website !website_name! is: !website_status!
365
366endlocal
367```
368
369# Annex
370## Special variables
371
372| Var | Description |
373|:--------------|:----------------------------------------------------------------------------------------------|
374| `cd` | Current directory where the .bat file is being called (not the directory where the file is). |
375| `WINDIR` | Location of the Windows folder with all its libraries and utils. |