Batch Script, mint teljesértékű nyelv

Mivel Windowson mindig is volt parancssor, nevezhetjük a legkézenfekvőbb programozási nyelvnek. – írta: bebe93, 11 éve

Előszó

Mivel Windowson mindig is volt parancssor, nevezhetjük a legkézenfekvőbb programozási nyelvnek. Általában aki használta, azt se tudta, hogy használta. cd, dir, del. Ismerősek? Sokan nem is tudják, de képesek vagyunk scripteket írni ezen parancsok felhasználásával anélkül, hogy bármivel lefordíttatnánk, vagy értelmeztetnénk (utalok itt a visual basic scriptre, amit szintén elég futtatnunk, ám az mégsem a shell része).

És hogy miért írtam meg ezt a cikket? A Batch elsőre egy rendkívül egyszerű nyelv, alig pár paranccsal.
A szintaxisa azonban annyira rosszul lett megírva, hogy képesek vagyunk kibújni a nyelv szabályai alól
és saját kedvünk szerint alakíthatjuk azt. A cím is ezt érzékelteti. Nagyon gusztustalan, de érdekes és tanulságos segédfüggvényeket írni hozzá, hogy szép lassan teljes értékű programozási nyelvként működhessen.

Technikai előszó

Ezen a nyelven látszik a legjobban az interpreter működése. Bizonyos eredményt pl. csak akkor érhetünk el, ha a változók behelyettesítése után megkérjük, hogy még egyszer menjen át az interpreter a scriptünkön. Ekkor a jelzéssel ellátott változókat bontja ki.

Hatalmas előny, hogy legalább egész számokra lehet műveleteket végezni, így akár komolyabb matematikai függvényeknek is előkészítve a helyet.

A nyelv egyes dolgokban nagyon szigorú, míg pl. egy függvény argumentumot használhatunk változónévként.

Szerszámok

* Egész típusú változók (-2147483647,2147483647)
* Stringek
* Feltételkezelés
* Ciklusok
* Exceptionök
* Goto
* Külső scriptek behívása
* változónévben szerepelhet: #$'()*+,-.?@[]_`{}~

Referencia:

Egész típusú változók, értékadás

Sztringek, értékadás, sztring változtatása

Fontosabb konstansok

Feltételkezelés

A feltételkezelésre a már jól bevált ifet használjuk. Kicsit bonyolultabb, mint más nyelvekben, sajnos több típusa van.
A már eddig is ismert módon így járhatunk el:

if condition (
command
) else (
command
)

Opcionális a zárójel, ha csak egy parancsot adunk ki vagy összefűzzük őket & jellel.

if 1 == 1 echo 1 and 1 are equal! & echo i'm alive

(Az else is opcionális, ám csak a zárójel után, vagy az iffel egy sorba rakhatjuk, mivel nem külön utasítás)

Mint láthattuk, az egyenlőségjel egyenlőséget tesztel, ám minden feltételre nem használhatjuk az általános matematikai jeleket, épp ezért javasolt a == elvetése.
Helyette a Batch három betűs rövidítéseket használ:

Egyszeri használatra

set /a computer=10
set /p user=Give me a number between 0 and 20:
if %computer% gtr %user% (
echo computer won
)

Logikai ÉS a gyakorlatban

set /a computer_a=10
set /a computer_b=15
set /p user=Give me a number between 0 and 20:
if %computer_a% gtr %user% (
if %computer_b% gtr %user% (
echo one of the computers won
)
)

Logikai VAGY a gyakorlatban

set /a computer_a=10
set /a computer_b=15
set /p user=Give me a number between 0 and 20:
if %computer_a% gtr %user% (
echo one of the computers won
) else (
if %computer_b% gtr %user% (
echo one of the computers won
)
)

A goto

A Batch-ben olyan gyakran kell használni a gotot mint máshol függvényhívást. Két részből áll. Maga a goto, és a label.

goto :label

:label
commands

Ha a kódunk így néz ki:

:label
commands

goto :label

Egy végtelen ciklust kapunk, amit nem akarunk, ezért egy konstans `:eof` hívással érhetünk azonnal a fájl végére. Így csak egyszer fut le.

:label
commands
goto :eof

goto :label

Lássuk, hogyan használhatjuk ezt a tudást a gyakorlatban.

@echo off
set /p choice=Do you like cookies? [yes/no]
if %choice% equ yes (
goto :likes
)
goto :eof

:likes
echo Me too!

Ciklusok

A ciklusok Batch-ben szintén sokfélék lehetnek, ahogy az if is. (ám nem kerülnek említésre, mert inkább fájlműveletekhez használatosak) Ebből is minket csak kettő érdekel, mégpedig azok, amelyek a programozáshoz szükségesek. (A for ciklusban is lehagyható a zárójel, ám úgy használatos, ahogy eddig tettük)

for /l %%iterator in (implicit_from,step,implicit_to) do (
command
)

Egy kis gyakorlat. Adjuk össze a számokat 1-től 100-ig!

set /a sum=0
for /l %%i in (1,1,100) do (
set /a sum+=%%i
)

Az előbb megtanult goto/label kombinációval hozhatjuk létre a jól ismert while ciklust.

:loop
commands
goto :loop

Számoljunk el végtelenig 1 másodperces szünetekkel

set counter=0
:loop
ping -n 2 127.0.0.1 >&0
set /a counter+=1
echo %counter%
goto :loop

Függvények ...helyett goto és subroutine

Áttekintés

Függvények nem léteznek a nyelvben, de bármikor hívhatunk egy címkézett fájlrészt, ami úgy fog viselkedni, mintha csak a konzolból hívnánk parancsot. Elláthatjuk argumentumokkal, végiglépkedhetünk rajtuk, returnölhetünk akár több változót is. (Ha a gyakorlatban akarjuk használni, érdemes a script testének végét egy goto :eof paranccsal lezárni, hogy ne fusson keresztül az alatta definiált függvényeken.)
De hogy is néz ki egy ilyen függvény?

:function_name
setlocal
set input1=%1
set input2=%2
...
set output=something
...
endlocal & set return=%output%
goto :eof

Nézzük meg mi mit jelent ebben a rendetlenségben.
:function_name Aki látott már c nyelveket, tudhatja, hogy ez egy label. Hivatkozhatunk rá, akárhonnan.

setlocal Ez a parancs eredményezi azt, hogy ne a bat fájl törzseként kezelje a következő kódot, hanem úgy, mintha konzolból hívtunk volna meg egy másik parancsot.
%0-N A függvényünk argumentumai, pontosabban:
* %0 a függvény neve
* %1 %2 %3... argumentumok

endlocal Lezárjuk függvényt, a változók elvesznek
& return=%output% Trükk. Ha az endlocal és return=%output% parancsokat egy sorban hajtjuk végre, az output változóra még hivatkozhatunk és globális térbe kerül return néven.

Egy kis kitérés: a call
A goto csak ugrik. A call ezzel a bizonyos setlocal-lal kooperál, hiszen meghívja, mint egy másik függvényt. Két fontos funkciója van a callnak. Egyrészt külső scriptet tud behívni, és azok függvényeit futtatni, másrészt a lokális scriptben lévő labelök alatt található függvényeket hívja meg.

call :some_function argmuments

Az említett külső scriptből való függvényhívást már nem lehet ilyen egyszerűen megejteni.
Fel kell rá készíteni a fogadó könyvtárat, headert.

main.bat

call outer :outer_function2 argument
echo %return%

outer.bat

call %1 %2
goto :eof

:outer_function
setlocal
endlocal & set return=outer function call reached
goto :eof
:outer_function2
setlocal
endlocal & set return=outer2 function call reached
goto :eof

Mint látható, az outer.bat fájlban szintén a call utasítással hívjuk meg az első argumentumot
a második argumentum paraméterrel. Így az interpreter pontosan azt teszi amit kell, kérésünket átkötötte. A globális térbe kerül a returnölt változó.

Vissza a függvényekhez, gyakorlati példa

Írjunk egy fibonacci számgenerátort!

@echo off
:main
call :fibonacci 8
set fib_result=%return%
echo %fib_result%
goto :eof

:fibonacci
setlocal
set counter=%1
set a=0
set b=1
set out=1
:fibonacci_loop
if %counter% gtr 1 (
set /a out=a+b
set /a a=b
set /a b=out
set /a counter-=1
goto :fibonacci_loop
)
endlocal & set return=%out%

Látható, hogy ha a belépési részét is kvázi függvényként kezeljük a scriptünknek, sokkal átláthatóbb lesz. Ha egy függvénynek van visszatérési értéke, azonnal változóba mentjük, hogy ne vesszen el, így teljesen biztonságos, ha minden visszatérésnél következetesen a return változót használjuk.

Tömbök

Batch-ben nincs tömb. Viszont a szerszámok paragrafus alatt megemlítettem, hogy mennyi karaktert elfogad ez a hanyag nyelv. Úgyhogy essünk is neki. Ennek nincs különösebb struktúrája, egyszerűen csak rá lehet erőltetni a c stílusú tömb értékadást. Így rögtön a gyakorlattal kezdem. Inicializáljunk egy 10 elemű tömböt, majd töltsük fel számjegyekkel.

set /a array=10-1
for /l %%i in (0,1,%array%) do (
set /a array[%%i]=%%i*%%i
)

Láthatjuk, hogy array[0],array[1],array[2] nevű változók generálódnak, míg a tömb nagysága az array változóban *pihen*. A kiíratáshoz már kell egy trükk. Az a bizonyos második átfutás a scripten. Ezt úgy érhetjük el, hogy a `setlocal` parancsot a `EnableDelayedExpansion` argumentummal használjuk.
Lássuk, hogyan.

setlocal EnableDelayedExpansion
set /a array=10-1
for /l %%i in (0,1,%array%) do (
set /a array[%%i]=%%i*%%i
)
for /l %%i in (0,1,%array%) do (
echo !array[%%i]!
)
endlocal

az !érték! jelen esetben azt jelzi az interpreternek, hogy miután átfutott a belső részen,
egy következő scanneléssel értékelje ki. Ha nem lenne erre lehetőség, a ciklus array[0],array[1]..-ként iratná ki.

Batch Overtuned
Mivel egy pár napos kalandról beszéltem és nem kívánok agyrákot kapni e nyelv lehetőségeinek kiaknázása miatt, csak megemlítem, mi az amit biztos lehet implementálni:

Ha ebben kéne programoznom életem végéig (azon kívül hogy idő előtt halnék meg):

strlen
Definíció:

:strlen
setlocal EnableDelayedExpansion
set temp=%~1~
set counter=0
:strlen_loop
if !temp:~%counter%! neq ~ (
set /a counter+=1
goto :strlen_loop
)
endlocal & set return=%counter%
goto :eof

Használat:

call :strlen string
set /a size_of_string=%return%

substr
Definíció:

:substr
setlocal EnableDelayedExpansion
set string=%~1
set from=%2
set to=%3

call :strlen "%string%"
set /a length=%return%
set /a to -= from

set output=!string:~%from%,%to%!

endlocal & set return=%output%
goto :eof

Használat:

call :substr string 0 %i%
set stripped_string=%return%

indexof
Definíció:

:indexof
setlocal EnableDelayedExpansion

set string=%~1
call :strlen "%string%"
set /a string_length=%return%

set search=%~2
call :strlen "%search%"
set /a search_length=%return%

set /a i_length=%string_length%-%search_length%

for /l %%i in (0,1,%i_length%) do (
if "!string:~%%i,%search_length%!" equ "%search%" (
endlocal & set return=%%i
goto :eof
)
)
endlocal & set return=-1
goto :eof

Használat:

call :indexof "LOREM IPSUM DOLOR SIT AMET" SUM
set position_of_sum=%return%

join
Definíció:

:join
setlocal
set array_name=%1
set /a array_size=!%1!
set separator=%~2
set output=
set /a i=0
:join_loop
if %i% leq %array_size% (
set output=%output%!%array_name%[%i%]!%separator%
set /a i+=1
goto :join_loop
)
endlocal & set return=%output:~0,-1%
goto :eof

Használat:

call :join array ","
set string_of_array=%return%

Juhász Bálint

___

(az eredeti cikk itt olvasható)

Forrás, linkek:

ss64.com/nt/
en.wikibooks.org/wiki/Windows_Batch_Scripting
csie.ntu.edu.tw/~r92092/ref/win32/win32scripting
stackoverflow.com/questions/10166386/
dostips.com/DtTipsStringManipulation

Azóta történt