CMD ja BAT - nipid ja trikid

Allikas: Hinnavaatlus.ee Wiki
Mine navigeerimisribaleMine otsikasti


CMD.EXE ja COMMAND.COM interpretaatoritele skriptimise nippe ja trikke

"Lasciate ogni speranza, voi ch'entrate!" ("Kõik lootus jätke, astudes siit sisse!")
Dante Alighieri, "Jumalik komöödia"

Sissejuhatus ja tavapärane disklaimer

  • Käesolev juhend ei pretendeeri vähimalgi määral olema skriptima õppimise juhend algajatele, kuigi sissejuhatuses on toodud mõned selgitavad märkused. Eelkõige peaks see olema kokaraamat, mida algaja skriptija saab mõne keerukama tüüpülesande jaoks kasutada lihtsalt copy-paste abil.
  • CMD ja COMMAND süntaks on erakordselt jube ja kultiveerib erakordselt halbu programeerimisharjumusi (eeldefineerimata muutujate kasutamine, tüübistamata muutujad, GOTO kasutamine jne.) Teid on hoiatatud!
  • Mõlemad keeled on üpris primitiivsed imperatiivsed keeled ja nendes lahenduse realiseerimise keerukus kasvab oluliselt kiiremini, kui ülesande keerukus. See tähendab, et probleemide arenedes keerustumise suunas, tuleb paratamatult kätte piir, kus on mõistlik senine skript maha jätta ja probleemi tuum realiseerida mõnes 'päris' keeles. Ära kunagi karda seda sammu!
  • koodinäite ette paigutatud COMMAND tähendab, et antud skript on ühilduv command.com ja cmd.exe interpretaatoritega. Märge CMD tähistab ainult cmd.exe ühilduvat koodi. Märge META tähistab metasüntaksis koodi. Näidetes leiduvad foo ja bar on üldlevinud metasüntaktilised muutujad. Eesti keeli: neid võib kasutada vabalt valitavate nimega failide, muutujate, alamprotseduuride jms. tähistamiseks või metasüntaksis kirjutatud näidetes.


COMMAND.COM

MS-DOS operatsioonisüsteemi kestaks (shell) on käsuinterpretaator command.com. Command.com suhtleb kasutajaga dialoogrežiimis (peale sisestusklahvi ENTER vajutamist täidetakse koheselt antud käsk ja väljastatakse tulemus). Command.com on UNIXist tuntud kestprogrammidega (bash, tcsh, sh, korn jne) võrreldes üsna primitiivne samuti, on tema utiliitide (aka väliskäskude) komplekt kehvapoolne (puuduvad näiteks mõistlikud stringitöötluse vahendid, MS-DOS'i ainutegumilise iseloomu tõttu pole standardsete sisend-väljundvoogude realisatsioon kuigi mõistlik jne.) Siiski on command.com abil võimalik ühte-teist korda saata. Abiks on siinkohal command.com oskus töödelda käske nn. "batch" rezhiimis. Command.com interpretaatorisse ehitatud sisekäsud (näiteks DIR, COPY, DEL jne.) ja eraldi programmidena realiseeritud väliskäsud (MODE, APPEND jne.) moodustavad DOSi käsustiku. Kõiki DOSi sise ja väliskäske, samuti suvalisi käivitusfaile saab panna spetsiaalsesse skriptifaili (batch-faili), mida command.com ridahaaval interpreteerib. Sellel rezhiimil täidetakse kõik käsud järjest, nii nagu nad failis on, ja väljatatakse tulemused. Faili laiend peab olema .BAT, sest mingil veidral põhjuselt tavatsevad kõik Microsofti operatsioonisüsteemid otsustada faili sisu üle tema laiendi alusel. Command.com on olemas ka Windows 9x seeria operatsioonisüsteemides. Command.com on 16-bitine käsuinterpretaator mis töötab real-modes v. Windowsi keskonnast käivitades virtual-real modes.

CMD.EXE

Koos täielikult 32bitise operatsioonisüsteemiga (NT 3.51) laskis Microsoft välja ka uue - 32bitise käsuinterpretaatori CMD. Kahjuks puudub mul täpsem informatsioon NT 3.51 eripärade kohta, seetõttu kehtib kõik järgnev NT4 ja uuemate (2K, XP, 2K3) kohta. CMD käsustik on command.com käskude ülemhulk. Eesti keeli: CMD oskab kõike seda mis COMMAND ja veel palju rohkem. Vahel nimetatakse CMD kasutamist ekslikult "DOSi käskudeks". See on vale, sest CMD on täisvereline 32bitine kaitstud rezhiimi kestprogramm - tõsi küll, harjumuspärastest Windowsi programmidest eristab teda graafilise liidese puudumine. Windows NT seeria koosseisus on olemas ka vana command.com mida jooksutatakse MS-DOSi emuleerimise keskkonnas, aga ilma erivajaduseta pole põhjust seda kasutada. Ka CMD oskab käsujadasid täita "batch" režiimis. CMD käsuskripti laiendina on soovitav kasutada .CMD, siis teab süsteem, et skripti interpreteerimiseks tuleks kindlasti kasutada CMD.EXE interpretaatorit. CMD laiendatud võimalused, olgugi napid, annavad juba lihtsama interpreteeriva programeerimiskeele võimekuse välja.

Millal on otstarbekas käsuskripte kasutada?

Ilmselt siis kui me soovime automatiseerida mõnda lihtsamat korduvat tegevust, mille jaoks pole mõtet hakata 'päris' programmi kirjutama. Või soovime korduvalt teostada mõnda ülesannet mille tarbeks operatsioonisüsteemis on utiliit juba olemas, lihtsalt tema töö tuleks automatiseerida. Näiteks: mingi hosti korduv pingimine ja võrgukatkestuse korral häire saatmine. Failide massiline kopeerimine -- lihtsamat sorti varundus, suure hulga failide ümber nimetamine kindlate reeglite alusel vms. Samuti on CMD skript abiks Windows Active Directory administreerimisel. Microsoft ise soovitab selleks otstarbeks kasutada Windows Scripting Host interpretaatorile kirjutatud VBScripti, kuid siin on yks pisikene "AGA". Vahel on tarvis kõikides domeeni kuuluvates masinates jooksutada mingeid käske kellegi kõvema kui kasutaja õigustes. Selle jaoks pakub AD võimalust jooksutada skripte arvuti käivitamisel lokaalse kasutaja SYSTEM õigustes. Paraku - startupi ajal pole Windows Scripting Host veel laetud ja meil puudub keskkond/interpretaator VBScritpi täitmiseks. Kasutada saame CMD skripte. CMD/BAT skripti on ka lihtne muuta. Teda ei kompileerita kunagi - igal täitmisel loeb interpretaator skritpi rea kaupa, tõlgib rea "masinakeelde", täidab ja väljastab tulemuse.

Miks on nendel juhtudel otstarbekas käsuskripte kasutada?

Peamised asjaolud, mis käsuskriptid kasulikuks teeb, on:

  • nende interpretaator tuleb Microsofti operatsioonisüsteemiga kaasa;
  • nende keel on sarnane kõnealuse operatsioonisüsteemi tavalisele käsureale.

Esimene nendest asjaoludest annab käsuskriptidele teiste keelte ees teatava logistilise eelise, teine jälle mõtteilma-eelise -- inimesel, kes oskab käsuridu efektiivselt kasutada, on käsuskriptikeele õppimine veidi lihtsam, kui mõne teise keele õppimine. Aga paljudes olukordades ei rakendu kumbki nendest eelistest ja skripte võib olla kasulik kirjutada Perlis, VBScriptis või Bashis.

Mõned rehad millega skriptima asumisel tuleks arvestada

CMD/COMMAND väliskäskude (utiliitide) väljund on tihitpeale inkonsistentne ja pole ilma eelneva parsimiseta torudes (pipe) kasutuskõlbulik.

Näide:

date /T
R 28.04.2006

Niisugust kuupäeva väljastuse vormingut ei ole Windowsi seadetes määratud! Seal on kuupäeva lühikese esituse vormiks 28.04.2006 ja pikaks esituseks 28. aprill 2006. a. Paraku lisab DATE utiliit väljundisse "omaenese tarkusest" nädalapäeva.

Sissejuhatuse lõpetuseks klassikaline näiteprogramm


COMMAND

@ECHO OFF
ECHO Hello World

Esimene rida seab interpretaatori töörežiimile kus väljastatakse ainult see mida käskudel öelda on (vaikimisi väljastab interpretaator ka käsud päras muutujate laiendamist -- see on silumisel väga abiks). Teine rida väljastab teksti "Hello World". Asudes tõsisemalt skriptima, on @ECHO OFF mõtekas esialgu välja kommmenteerida, et tõrgete tekkimisel näha, millise rea täitmisel viga tekkis. Keerukamate skriptide puhul tasub ka kajamise ajutist sisselülitamist kahtlase skriptiosa ümber -- käsu "@ECHO OFF" paariline on "@ECHO ON".
COMMAND

REM @ECHO OFF
ECHO Hello World

REM on lühend inglise keelsest sõnast REMARK ja tähistab kommentaari.

Nipid ja Trikid

Kuidas tekitada tühja faili

Enamasti kasutatakse selleks umbes niisugust rida:
COMMAND

echo. > foo.bar

Tegelikult annab see rida väära st. ülesande tingimustega kokkusobimatu tulemuse, kuna silma järgi tyhjas failis on tegelikult yks reavahetus 0x0D 0x0A. Õige tulemuse annab niisugune kood:
COMMAND

copy NUL foo.bar

Nüüd saame me tõesti tühja faili. Paar sõna selgituseks: NUL on spetsiaalfail, mis oma käitub samamoodi nagu *NIX /dev/null. Tegemist on failiga kuhu võib lõpmatult andmeid saata, see fail ei saa iialgi täis, samuti ei saa sealt midagi tagasi. Omamoodi arvuti "must auk". Erinevalt looduslikust "mustast august", kust tõesti midaga tagasi ei saa on NUL natukene viisakam. Igale katsele temast midagi lugeda vastatakse viisakalt faililõpusümboliga. Ehk NUL ütleb meile: "mittemidagi, mida ma sulle ei anna, sai siinkohas otsa, seega paneme faili nüüd viisakalt kinni". Õnneks see ongi täpselt see mida meil vaja on.

Kuidas tekitada ajutist faili

Tihtipeale on keerukamate ülesannete implmenteerimiseks CMD skripti abil vaja kasutada ajutisi faile. Kuidas seesuguseid viisakalt tekitada.
CMD

:DO
SET myrndtmp=%TEMP%\%RANDOM%.tmp
IF EXIST %myrndtmp% GOTO DO 
COPY NUL %myrndtmp%
IF ERRORLEVEL 1 EXIT /B

:: Teeme midagi kasulikku 
ECHO foo > %myrndtmp%
TYPE %myrndtmp%

DEL /Y %myrndtmp%

Mis meil siin siis toimub? Esiteks seatakse ajutise faili täisnimi (faili nimi koos täieliku teega). Selleks kysitakse süsteemilt ajutiste failide hoidmiseks määratud kataloogi %TEMP% ning juhuslikku arvu vahemikus 0..32767 (%RANDOM%). Kontrollitakse ega niisugune fail juba ei eksisteeri. Kui juhtub, et niisuguse nimega fail on juba olemas küsitakse uus juhuslik failinimi. Tegevust korratakse seni kuni leitakse "vaba" nimi. Vaba nimi käes, kasutatakse eelmises näites toodud võtet tühja faili loomiseks. Lõpuks kontrollitakse kas faili loomine oli edukas. Kui COPY käsu täitmine mingil põhjusel ebaõnnestus (ketas täis, puudub kirjutusõigus, vigaselt määratud keskkond jms.) katkestatakse koodi täitmine (EXIT /B). Kui töö tehtud, siis on viisakas ajutused failid enda järelt kustutada. Paneme tähele, et me ei tea, MIS nimega meile ajutine fail luuakse ja KUHU see täpselt pannakse, aga meie seisukohast pole see absoluutselt oluline, kuna meie jaoks on faili täisnimi alati saadavalt muutujas %myrndtmp% ja sellisel kujul kasutatav.

Tühikute eemaldamine sõne algusest ja lõpust

Tihtipeale tekib olukord, kus FOR tsükli abil pikemast tekstist parsitud sõnedele jäävad algusesse ja/või lõppu soovimatud tühikud. Üks võimalus nende eemaldamiseks oleks kasutada CMD alamsõnede eemaldamise funktsionaalsust. Vaatleme järgmist koodi:

CMD

SET test = proov 
SET test=%test: =%

Selle koodi täitmisel saab muutuja test väärtusest "[tühik]proov[tühik]" uueks väärtuseks "proov". Kõik oleks ilus, aga kui meil oleks näiteks niisugune sõne " see on proov ", siis uus väärtus "seeonproov" meid vaevalt rahuldab. Ainult sõne algusest ja lõpust tühikute eemaldamiseks sobiks paremini niisugune kood:
CMD

SET b= see on test 
IF "%b:~0,1%"==" " SET b=%b:~1%
IF "%b:~-1%"==" " SET b=%b:~0,-1%

Kood loeb sõnest esimese märgi ja kui tegu on tühikuga, siis eemaldab selle (sõne uueks väärtuseks omistatakse kõik märgid peale esimese), kui tegu ei ole tühikuga, siis antakse täitmine edasi järgmisele käsule. See toimib analoogselt, lugedes sõnest viimase märgi.

Kataloog ehk appi ma olen eksinud

Jooksvat kataloogi säilitatakse muutujas %cd%
CMD

echo %cd%

Seame töökataloogiks oma valitud kataloogi ja pärast skripti töö lõppu pöördume tagasi jooksvasse kataloogi
CMD

PUSHD %SYSTEMROOT%
ECHO %CD% 
POPD

Antud näites kasutame keskkonnamuutujat %SYSTEMROOT% ehk kataloogi kuhu Windows on paigaldatud. Kasutada võib mistahes olemasolevat kataloogi C:\foo\bar. Eriti kasulikuks teeb selle käsu asjaolu, et kasutada on võimalik ka UNC kujul võrguteesid \\foo\bar\fubar mille kasutamist skriptides interpretaator ei luba (PUSHD otsib automaatselt vaba kettatähise ja monteerib selle ajutiselt külge, et skript saaks kasutada "klassikalist" teed. POPD vabastab monteeringu).
CMD

PUSHD \\foo\bar
ECHO %CD%
POPD

Rekursiivne DIR

Soovime nimekirja kõigist failidest mis asuvad puus jooksvas kataloogist allapoole (ehk kõigi failide ja alamkataloogide failide listing). Siin on võimalik ära kasutada rekursiivse kopeerimisutiliidi, xcopy, võtit /L mis faile ei kopeeri vaid väljastab ainult nimekirja failides mida ta kopeeriks.
CMD

FOR /F "tokens=1 delims=->" %A IN ('xcopy /L /F /E /S /H /R /C /Y *.* %TEMP%') do echo %A 

For tsükkel on vajalik selleks, et xcopy väljundist kujul source -> destination eemadalda meie jaos mittevajalik "destination" osa. Huvitavaid lisavõimalusi pakub xcopy võti /D millega saab otsingule seada alampiiri, millest ajaliselt vanemaid faile ignoreeritakse. Xcopy käsu "destinationiks" sobib MISTAHES relaaselt eksisteeriv kataloog.

Muutujate hilistatud väärtustamine

CMD interpretaatoril on jabur komme asendada muutjad nende väärtustega mitte siis, kui koodirida täidetakse vaid siis, kui rida interpreteerimiseks ja täitmiseks mällu loetakse. Tegelikult on võimalik sundida CMD-d käituma "päris" interpretaatorite moodi - muutujad asendadatakse nende väärtustega interpreteerimise käigus. Paraku ei ole Microsofti programeerijad suutnud vastavat algoritmi korralikult implementeerida ja "hilistatud väärtustamise" režiimis on koodi täitmine väga aeglane. Seetõttu on see võimalus vaikimisi välja lülitatud. Sisse on seda võimalik lülitada, kutsudes CMD välja võtmega /V

CMD /V:ON

Jäigalt saab hilistatud väärtustamise sisse lülitada muutes registrivõtmete

HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\DelayedExpansion

(süsteemselt) või

HKEY_CURRENT_USER\Software\Microsoft\Command Processor\DelayedExpansion

(kasutajapõhiselt) väärtust 0x0 (disabled) või 0x1 (enabled) Viimane tegevus pole siiski soovitav, sest nagu öeldud - skripti interpreteerimine muutub KORDADES aeglasemaks. Hilistatud väärtustamise kasutamiseks on mõttekas võtmega /V:ON välja kutusuda alamshell ja tulemused tagastada failis (alamshell ei päranda muutujaid vanemale!).

Vaatleme niisugust näidet:
CMD

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

Tundub, et meie muutuja %LIST% väärtuseks peaks saama jooksvas kataloogis asuvate failide nimekiri. Ometi näeme muutja %LIST% väärtusena ainult viimast leitud faili. Miks? Sest CMD väärtustab %LIST% FOR-tsükli lugemisel (sel hetkel sisaldab see tühja väärtust), mitte täitmisel. Meil oleks tarvis, et %LIST% saaks väärtuse FOR-tsükli IGAL täitmisel. Siin tulebki appi hilistatud väärtustamise režiim:
CMD /V:ON

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

See skript annab juba oodatud tulemuse. Paneme tähele, et lisaks sellele, et meil on interpretaator vaja välja kutsuda õige võtmega, tuleb ka "hilistatud" muutuja välja kutsuda teist moodi kui "tavaline" muutuja -- !LIST! Hilistatud muutujaid kasutatakse siis kui IF-(foo) või FOR-DO skoobis on tarvilik väärtustada muutuja ja muutuja väärtust samas skoobis uuesti kasutada.

Nüüd aga üks praktiline rakendus. Varem või hiljem õnnestub meil sattuda mõne arhiivi peale, mille on teinud keegi maniakk, kes on kõik kataloogid ühekaupa rar'inud või zip'inud. Järgnev skript võtab jooksvast kataloogist kõik rar failid, teeb igaühele sama nimega kataloogi, lõigates arhhivi lõpust maha laiendi ja pakib iga arhiivi oma kataloogi lahti.

:ECHO OFF
FOR /F %%A IN ('DIR /b *.rar') DO (
SET B=%%A
SET B=!B:~0,-4!
mkdir !B!
"C:\Program Files\WinRAR\rar" e %%A !B!
)

Kuidas ma saan muutujasse oma IP aadressi

Selle skripti kasutamise eelduseks on teada, millise võrguliidese aadressi soovitakse küsida.

@ECHO OFF
SET INTEF=Local Area Connection
FOR /F "usebackq tokens=3" %%A IN (`"netsh interface ip show address name="%INTEF%" | FIND "IP Address"" `) do SET IP=%%A
ECHO %IP%

Märkused: usebackq on vajalik selleks, et käsu sees jutumärke kasutada. Käsk ise tuleb panna tagurpidi ülakomade vahele ja siis veel omakorda jutumärkide vahele. Viimased on olulised selleks, et CMD DO käsku parsides sealt võrdusmärki ära ei kaotaks. Käsk töötab ainult staatiliselt määratud ja VPN IP aadresside korral.