Dzisiejszy post jest w sumie fragmentem mojego wykładu o skryptach .BAT (tego z wyklady.net), więc jeżeli ktoś był obecny na owym wykładzie, to raczej niczego nowego się nie dowie.
Skrypty .BAT (lub batch, jak kto woli) są stare jak DOS - pierwszy raz spotkałem się z nimi na starym 286, i były w powszechnym użytku wtedy. Chciałeś uruchomić grę na Herkulesie (czarno biała karta graficzna o dość niestandardowej rozdzielczości 720 x 348)? To musiałeś naklepać skrypcik odpalający emulator CGA (większość gier chodziło w tym trybie), potem gierkę, a potem wyłączający emulację. Skrypcik wyglądał mniej więcej tak:
color 5
gra.exe
color 7
Potem nadeszły czasy walki o każdy dodatkowy kilobajt pamięci operacyjnej, XMSa i EMSa, czasy w których każdy miał po kilka zestawów skryptu startowego autoexec.bat i config.sys (lub jeden skrypt oparty o menu wyboru). A potem nadszedł Windows 95, 98, DirectX, i skrypty .BAT przestały interesować normalnych użytkowników (a teraz przyszedł Monad aka Windows PowerShell, i .BAT przestaje interesować nawet tzw. power userów).
OK, koniec tej historii. Teraz o programowaniu obiektowym w .BAT.
Jak wiadomo .BAT trudno posądzić o jakieś większe możliwości. Niby ma goto, całkiem potężną pętle for, wywołania funkcji za pomocą call, no i zmienne do których można odwołać się na dwa sposoby (%normalnie% lub !z_opóźnionym_odczytem_wartości! - trzeba pamiętać iż aby użyć opóźnionego rozwijania wartości trzeba na początku skryptu wrzucić setlocal enabledelayedexpansion, lub ustawić klucz {HKLM,HKCU}\Software\Microsoft\Command Processor\DelayedExpansion na 1 (DWORD)). Ale klasy? Nope. Klas nie ma, więc o programowaniu obiektowym trudno w ogóle mówić. Natomiast, okazuje się że klasy/obiekty można emulować, i to nawet dość skutecznie.
Całość opiera się o mechanizm zmiennych - tych zmiennych które ustawiamy set nazwa=wartosc, i do których odwołujemy się za pomocą podanych wyżej wyrażeń z % lub !. Rozważmy następujący przykład:
set a=ver
echo %a%
By the way...
On 22nd Nov'24 we're running a webinar called "CVEs of SSH" – it's free, but requires sign up: https://hexarcana.ch/workshops/cves-of-ssh (Dan from HexArcana is the speaker).
Interpreter cmd.exe (bo on jest odpowiedzialny za wykonywanie .BAT) otworzy plik z powyższym skryptem, odczyta linię, zapamięta pozycję, zamknie plik (serio, dokładnie tak to wygląda, plik jest na nowo otwierany dla każdej linii, za wyjątkiem wyrażeń w nawiasach / z nawiasami, które są wczytywane w całości), po czym sprawdzi czy są w linii jakieś zmienne, podmieni je na ich wartości, i wykona linie. Oczywiście w pierwszej linii jest tylko ustawienie zmiennej środowiskowej a na wartość ver, więc zmiennych do podmiany nie ma. Natomiast w przypadku drugiej linii taka podmiana już zajdzie, i do wykonania trafi nie echo %a%, a echo ver. Zmieńmy trochę kod - wywalmy echo:
set a=ver
%a%
W tym przypadku do wykonania w drugiej linii trafi polecenie ver, które zostanie poprawnie wykonane, tak jakby autor skryptu napisał po prostu ver a nie %a%. W zmiennych można więc przechowywać nie tylko pojedyncze wyrazy, ale i całe polecenia.
A teraz, użyjmy tego mechanizmy do stworzenia klasy która odczytuje plik tekstowy do tablicy (w .BAT nie ma tablic jako takich, ale można je emulować, analogicznie do tych w skrypcie mIRC'a).
Załóżmy że klasa będzie nazywać się Plik, i jej konstruktor (czy też bardziej fabryka), również będzie tak się nazywać. Klasa będzie posiadać następujące zmienne/pola/zwał jak zwał:
lines - tablica zawierająca linie
name - zmienna zawierająca nazwę pliku
count - zmienna zawierająca ilość wczytanych linii
oraz następujące metody:
read - odczyt wskazanego pliku
Zacznijmy od konstruktora, który przyjmie dwa parametry - docelową nazwę obiektu, oraz nazwę pliku do oczytania:
:Plik
set %1.name=%2
set %1.read=call :Plik.read %1
goto :EOF
Linia pierwsza to oczywiście etykieta (label) oznaczająca miejsce w skrypcie. W drugiej linii jest ciekawa konstrukcja - tworzymy nazwę zmiennej używając pierwszego parametru, oraz sufiksu .name, i tejże zmiennej ustawiamy wartość przepisaną z parametru drugiego (nazwę pliku). Trzecia linia to "podlinkowanie" metody do obiektu, a tak na prawdę stworzenie zmiennej o nazwie obiekt.read która będzie zawierać polecenie call :Plik.read obiekt - czyli w pierwszym parametrze niejawnie (dla końcowego użytkownika) zostanie przekazana nazwa obiektu, którą możemy rozumieć jako pointer na obiekt (takie this, czy self). Czwarta linia to .BAT'owski return.
Przykładowe wywołanie konstruktora wygląda następująco:
call :Plik a ala.txt
Po tym wywołaniu zostanie utworzony obiekt a, i zostaną utworzone dwie dodatkowe zmienne związane z tym obiektem:
a.name=ala.txt
a.read=call :Plik.read a
Teraz użytkownik musi wywołać odczyt pliku wpisując po prostu %a.read%. Sama implementacja odczytu wygląda następująco:
:Plik.read
set j=0
for /f "delims=" %%i in ('type !%1.name!') do (
set %1.lines.!j!=%%i
set /a j=!j!+1
)
set %1.count=%j%
goto :EOF
Pierwsza linia to oczywiście nazwa metody - kropka oczywiście jest częścią nazwy, nie jest to żaden specjalny znak, po prostu użyłem go jako separatora nazwy klasy od nazwy metody czy pola, równie dobrze zamiast kropki mogła by tam być podłoga aka underscore (znak _). Potem mamy standardową funkcję for, która wykonywana jest dla każdej linii z pliku !%1.name! (gdzie %1 to nazwa obiektu, więc to jest !a.name! w naszym przypadku, co jest zamieniane na ala.txt). W pętli do "tablicy" o nazwie %1.lines.!j! - czyli w naszym przypadku będzie to a.lines.0, a.lines.1 etc - wrzucana jest każda kolejna linia pliku. Potem jest standardowa inkrementacja set /a j=!j!+1, i koniec pętli. Po pętli ustawiane jest jeszcze pole a.count na %j%, czyli ostateczną liczbę linii odczytanych z pliku, i następuje powrót.
Jak wygląda wykorzystanie tak przygotowanej klasy? Sprawa jest prosta:
call :Plik a ala.txt
%a.read%
echo Ilosc linii : %a.count%
echo Druga linia : %a.lines.1%
echo Trzecia linia: %a.lines.2%
goto :EOF
OK, mamy klasę i obiekt. Ale co z pointerami na obiekty? Sprawa jest raczej prosta, i zilustruje ją poniższy przykład:
@echo off
call :Plik a ala.txt
%a.read%
call :Plik b beata.txt
%b.read%
echo Plik A: %a.name%
echo Plik B: %b.name%
set ptr=a
echo Plik *Ptr: %%ptr%.name%
set ptr=b
echo Plik *Ptr: %%ptr%.name%
goto :EOF
Jak widać konstrukcja z zagnieżdżonymi zmiennymi rozwiązuje problem pointerów.
OK dalej. Teraz czas na dziedziczenie. Dziedziczenie akurat w językach tego typu jest dość proste - wszystko jest kwestią wywołania w fabryce/konstruktorze klasy dziecka, fabryki klasy matki - ona ustawi wszystkie metody, etc, a dziecko potem "poprawi" co mu się podoba.
Pokazany poniżej program zawiera klasę Figura z której wywodzą się dwie klasy - Kwadrat oraz Trojkat:
@echo off
call :Kwadrat a 10
call :Trojkat b 10 5
!a.pole!
!b.pole!
echo Klasa matka: a !a.matka!, b !b.matka!
set ptr=a
!%ptr%.pole!
set ptr=b
!%ptr%.pole!
goto :EOF
rem Klasa : Figura nazwa_obiektu a b
rem Metody: Figura, pole (wirtualna)
rem Pola : a, b
:Figura
set %1.a=%2
set %1.b=%3
set %1.matka=Figura
goto :EOF
rem Klasa : Kwadrat nazwa_obiektu a b (dziedziczy Figura)
rem Metody: Kwadrat, pole
:Kwadrat
rem Wywolaj super konstruktor
call :Figura %*
set %1.pole=call :Kwadrat.pole %1
goto :EOF
:Kwadrat.pole
set /a pole=!%1.a! * !%1.a!
echo Pole kwadratu %1: %pole%
goto :EOF
rem Klasa : Trojkat nazwa_obiektu a b (dziedziczy Figura)
rem Metody: Trojkat, pole
:Trojkat
rem Wywolaj super konstruktor
call :Figura %*
set %1.pole=call :Trojkat.pole %1
goto :EOF
:Trojkat.pole
set /a pole=!%1.a! * !%1.b! / 2
echo Pole trojkata %1: %pole%
goto :EOF
By the way...
On 22nd Nov'24 we're running a webinar called "CVEs of SSH" – it's free, but requires sign up: https://hexarcana.ch/workshops/cves-of-ssh (Dan from HexArcana is the speaker).
No i w sumie tyle na dzisiaj. W następnym poście pokaże jak w .BAT napisać programik pod OpenGL ;>
P.S. i tak nie polecam obiektowego .BAT do większych projektów ;D
Comments:
respect
Cieszę się że się podoba ;D
ktoś był obecny na ów wykładzie
powinno być
ktoś był obecny na owym wykładzie
Pozdr
Thx fixed ;>
http://99-bottles-of-beer.net/
?
;]
O całkowitym niezrozumieniu zasad dot. programowania obiektowego. Ale nie ma się czym przejmować... większość programistów tak naprawdę ich nie rozumie. Stąd też zdarzają się takie 'perełki'. :)
Jednak sam artykuł jest dosyć ciekawy.. pomijając terminologię w nim użytą. :)
pozdrawiam.
Moim zdaniem, autor wykazał się dobrą znajomością zarówno nowoczesnych i starszych języków programowania oraz historii systemu operacyjnego DOS...też pamiętam menu wyboru w autoexecu i configu :]
Pozdrawiam.
Powodzenia ;>
@Adaslaw
Hmm, mówisz że warto? Wyślę zapytanie do maintainerów ;> Jeżeli dadzą zielone światło to wrzucę ;>
@Jurgi
Którym ? Amstradowym ? Hmm... jeżeli działa tak jak myślę, to jest do zrobienia ;> (nie mylić z "łatwe do zrobienia").
@prs
Ah, no i mnie zaciekawiłeś ;> Gdybyś mógł napisać (z cytatami z artykułu) które konkretnie użycia terminologii są (wg Ciebie) nieprawidłowe, oraz wyjaśnić dlaczego ;> Sądzę że sporo osób na tym skorzysta (w tym ja ;>) ;>
@mikexcr
Hehe nie ma potrzeby tak ostro ;> A nóż się czegoś nowego nauczymy (gdy tylko prs poda konkretny) ;>
@korro
Dziękuje ;>
@Wszyscy
Dzięki za komentarze ;> Sporo dzisiaj komentarzy ;>
Chciałbym jeszcze sprecyzować jedną rzecz. Mianowicie emulacja obiektowości którą opisuję w artykule nie jest skierowana dla wersji DOSowej .BAT. Prawdopodobnie w wersji DOSowej .BAT w ogóle nie ruszy (afair nie było tam deleyed expansion). Język .BAT jest implementowany w cmd.exe w nowych Windowsach, i w każdym Windowsie jest jakaś zmiana, tak że język się nadal rozwija. Natomiast nie ma już dużo wspólnego z DOSem ;>
Z DOSem natomiast miała sporo wspólnego implementacja .BAT znajdująca się w command.com (patrz np Windows XP, są tam DWA interpretery .BAT - 32 bitowy cmd.exe, i działający na NTVDM 16-bitowy command.com). Szczerze, nie testowałem emulacji obiektowości tam ;>
Ale to pewnie już zobaczyłeś po statystykach. ;-)
Hehe widziałem, referery na bieżąco śledzę ;> A i w statystykach się to nieźle odbiło - 7 tyś odwiedzin dzisiaj (normalnie mam 50-70 ;D) ;> Cóż.. szczerze nie spodziewałem się ze post o czymś co od roku wisi w formie loga na wyklady.net wzbudzi takie zainteresowanie ;D
Z ciekawostek refererowych, dowiedziałem się że na jutrzejszym kolokwium (?z języków skryptowych?) ma być programowanie obiektowe w .BAT ;> A, no i moje "kotki" są dość popularne hehehe ;> Have fun ;>
bash go zjada na sniadanko z kopytkami :D
Cóż, trudno zaprzeczyć ;> Ale na tym bym skończył podróż pod tytułem "A jest lepsze od B", nie chcemy przecież flamewar'a ideologicznego, prawda? ;>
Dajmy prs'owi mówić za siebie ;>
Zresztą, na początku arta zaznaczam że to nie jest obiektowość, tylko emulacja obiektowości ;D
Nie bardzo rozumiem po co robić takie rzeczy jak mamy obiektowe .VBS-y wspierane w Windows-ach począwszy od wersji 2000 ?
Oczywiście to bardzo zacne, że użytkownik Windows zapoznał się dogłębnie z Command Interpreter-em. Znaczy się, że jest to zaawansowany użytkownik :)
Hmm, nigdy nie zastanawiałem się czy między .BAT a .CMD są jakieś różnice. Kapkę zajrzałem w temat, i wygląda na to że faktycznie mała różnica jest, i polega ona na zmienieniu bądź nie zmienieniu ERRORLEVEL przy niektórych poleceniach.
Dokładne informacje:
http://groups.google.com/group/microsoft.public.win2000.cmdprompt.admin/msg/ad9066638815812c?pli=1
W związku z czym przypuszczam że zmiana rozszerzenia z .BAT na .CMD i w drugą stronę w przypadku powyższego wpisu jest bez znaczenia. I jednocześnie przypuszczam że to wyżej i tak nie ruszy na NT4, z powodu działania praw Murphiego ;> (zdarzyło mi się kilka razy przy robić pewne zmiany w skryptach przy przerzucaniu np z Visty na 2K).
A co do "po co": bo można ;>
Osobiście nie widzę zastosowania do tego, ale w ramach proof of concept postanowiłem i tak to zrobić ;> Do celów praktycznych VBS czy choćby skrypty Monada są oczywiście dużo lepsze ;>
@echo off
set h=*
for %%h in (9 1 8 5 7 6 2 3 0) do call :h2 %1 %%h
goto :EOF
:h2
if h%1h==h%2h set h=h
if %h%h==hh goto :%h%
echo %h%*
set h=%h%*
:h
W przypadku skryptów BAT można stosować bardzo prosty trik ułatwiający debugowanie - usunąć @echo off.
Wtedy przy wykonaniu każdej linii widzisz w jakiej postaci (tzn zmienne są już rozwinięte) się ona wykonuje, i w jakiej kolejności.
W przypadku przedstawionego przez Ciebie skryptu sprawa jest dość prosta ;>
Na początku zmienna h jest równe jednej gwiazdce ;>
Potem jest pętla w której za %%h (nie mylić z %h%) podstawiana w każdym "obrocie" pętli jest jedna z wymienionych liczb (w zasadzie cyfr, ale to mało istotne): 9 1 8 5 7 6 2 3 0. Dla każdej z tej liczb wywoływana jest funkcja :h2 z dwoma parametrami: %1 który jest parametrem skrypty bat oraz %%h, czyli liczby z zestawu.
W zasadzie to są dwa warianty wywołania tej funkcji:
Jeżeli parametr skryptu będzie podany, to h2 będzie wywołane z %1=parametr i %2=%%h
Natomiast jeżeli parametru nie będzie, to %1=%%h, a %2 będzie puste (ponieważ %1 w wywołaniu jest puste, więc bat traktuje to jak powietrze, i myśli że to jest po prostu call<dwie spacje>%%h)
OK, teraz funkcja h2...
Najpierw jest sprawdzenie czy h%1h==h%2h.. w tym przypadku literka 'h' musi występować żeby nie było syntax error w przypadku gdy %1 lub %2 było by puste (to wynika z faktu rozwinięcia zmiennych przed wykonaniem if; jeżeli nie dać tych literek 'h', to przy pustym %1 i %2 wykonane by było if == set h=h, co jest ofc syntax errorem). Anyway, jeżeli parametr skryptu nie jest podany, to ten if będzie zawsze nieprawdziwy. W przeciwnym wypadku zmienna h będzie ustawiona na literke h gdy funkcja będzie wywołana dla liczby z zestawu równej parametrowi skryptu (czyli np jeżeli ktoś odpali a.bat 9, to ten if wykoan się już w pierwszej iteracji).
Drugi if sprawdza czy pierwszy się wykonał, i jeżeli tak to skacze na koniec funkcji.
Dwie przedostatnie linie funkcji to dość prosta sprawa - wypisanie zmiennej h z gwiazdką dodatkową, oraz ustawienie dodanie do zmiennej h kolejnej gwiazdki (set h=%h%).
I tyle. Ktoś bardzo lubi literkę h ;>>>
Ja napisalem cos takiego:
@echo off
cd C:A
dir /B /S *.txt>>C:Ado_posortowania.txt
for /F "tokens=*" %%a in (do_posortowania.txt) do Sort "%%a" /o Posortowane.txt
cls
echo Sorotwanie zakonczone pomyslnie.
pause
exit
ale za bardzo nie dziala ;|
sort /o imo wszystko psuje - tj nadpisujesz sobie plik wynikowy co iteracje pętli
Tak na prawdę pojawia się pytanie co chcesz osiągnąć.
Opcje są trzy:
1) chcesz wziąć każdy plik z listy, posortować tylko jego, i nadpisać stary plik nowym posortowanym - czyli sortujesz wszystkie pliki, ale każdy pojedynczo
2) chcesz wczytać wszystkie pliki do jednego miejsca, posortować wszystko razem, i zapisać do osobnego pliku
3) chcesz posortować wszystko oddzielnie, ale wynik zapisać do jednego pliku (takiego sklejonego)
Jeżeli chodzi o 1), to najlepiej to zrobić tak:
@echo off
for /f %%i in ('dir /b /s *.txt') do (
echo Sortuje plik %%i
sort %%i /o posortowane.txt
move /y posortowane.txt %%i > nul
)
echo Koniec!
Jeżeli chodzi o 2), to zrób to tak:
@echo off
del wszystko.txt >nul 2>nul
del posortowane.txt >nul 2>nul
for /f %%i in ('dir /b /s *.txt') do (
echo Wczytuje plik %%i
type %%i >> wszystko.txt
)
echo Sortuje!
sort wszystko.txt /o posortowane.txt
del wszystko.txt >nul 2>nul
echo Koniec!
A jeżeli o 3), to tak:
@echo off
del posortowane.txt >nul 2>nul
for /f %%a in ('dir /b /s *.txt') do (
echo Sortuje %%a
echo %%a >> posortowane.txt
sort %%a >> posortowane.txt
)
echo Koniec!
;>
:) n/p
@-=NIKT=-
Ehehe dzięki ^^ (chociaż szczerzę, to wiedza o .bat wydaje mi się trochę przeterminowana, z uwagi na pojawienie się powershella etc)
trzeba znać jego kod ascii ?
Listing na samym dole postu to kompletny program, a w zasadzie kompletny skrypt.
W zasadzie jedyna rzecz której brakuje, to "setlocal enabledelayedexpansion" po @echo off, tak na wszelki wypadek :)
@wuben
Nie za bardzo rozumiem... mówisz o .bat?
W takim wypadku: echo %% >> plik
a jak jest ze znakiem "|"
Kawał dobrej roboty:
Ja stworzyłem coś takiego ale przy tym co ty robisz to jakiś potworek
[size=1]Created by opisywarka[/size]
[size=3][color="yellow"]DANE TECHNICZNE[/color][/SIZE]
[CODE]
[/CODE]
[size=3][color="yellow"]OPIS PROBLEMU[/color][/SIZE]
[QUOTE]
@echo off
cls
color 2
echo.
echo ** ****** **
echo /** ** ** **////** /**
echo /** //** ** ** // ****** ****** /**
echo /****** //*** /** **////**//**//* ******
echo /**///** /** /** /** /** /** / **///**
echo /** /** ** //** **/** /** /** /** /**
echo /****** ** //****** //****** /*** //******
echo ///// // ////// ////// /// //////
echo.
echo.
echo Program rysuje prostokat o podanych parametrach
echo.
echo podaj dlugos prostokotu 1-78
echo.
SET /P h=
echo.
echo podaj wysokosc prostokotu 1-48
echo.
SET /P h2=
cls
color 4
set z2=$
set z4=80
set z5=50
set z7=
set /A w1=%z4%-%h%
set /A w1=%w1%/2
set /A w2=%z5%-h2
set /A w2=%w2%/2
:e01
echo.
set /A w2=%w2%-1
if %w2%==0 goto e02
goto e01
:e02
set z6=%z6%%z7%
set /A w1=%w1%-1
if %w1%==0 goto e1
goto e02
:e1
SET /A h=%h%-1
set z3=%z3%%z2%
if %h%==0 goto e2
goto e1
:e2
echo %z6%%z3%
SET /A w=%h2%-1
set h2=%w%
if %w%==0 goto koniec
goto e2
:koniec
ping localhost -n 10 >nul
[/QUOTE]
echo || >> plik
nie działa
Znakiem "ucieczki" w batchu jest caret (czyli ^, zwany również daszkiem ;>)
echo ^| >> plik
Pozdrawiam ;)
Ten 2 skrypt – kwadrat i trójkąt działa (będę go analizował) tylko ten pierwszy nie mogę uruchomić (xp prof.) może go źle składam? Dodałem setlocal enabledelayedexpansion i tez nic. Mam prośbę o podanie całego skryptu w pliku
setlocal enabledelayedexpansion
call :Plik a ala.txt
:Plik
set %1.name=%2
set %1.read=call :Plik.read %1
goto :EOF
:Plik.read
set j=0
for /f "delims=" %%i in ('type !%1.name!') do (
set %1.lines.!j!=%%i
set /a j=!j!+1
)
set %1.count=%j%
goto :EOF
%a.read%
echo Ilosc linii : %a.count%
echo Druga linia : %a.lines.1%
echo Trzecia linia: %a.lines.2%
goto :EOF
Postaram się rzucić jutro okiem czy ten skrypt działa na XP. Z tego co pamiętam testowałem je na Viście.
Np. użytkownik wprowadza "Jaś_Wąż" a skrypt podstawia pod zmienną n_no_pl ciąg "Jas_Waz"?
#/bin/bash
#Sets gamma and runs UrbanTerror
xgamma -gamma 2
./ioUrbanTerror.x86_64
xgamma -gamma 1
Chce napisać taki skrypt.
1. otwieram plik, mam ciąg danych, chce wczytać pierwsze 6 znaków.
2. użyć tych znaków w jakimś poleceniu(to wiem jak zrobić)
3.zapisać wynik tego polecenia w txt
4. zapętlić i znów wczytać 6 znaków ale z pominięciem pierwszych 6, znaki są napisane jednym ciągiem.
teraz moje pytania.
1. Jak wczytać konkretną ilość znaków i jak ustawić od którego miejsca mają być wczytane dane(to się chyba offset profesjonalnie nazywa)
2. czy jak chce zapisać dane do drugiego pliku txt to pierwszy musi być zamknięty?
chce to uruchomić w cmd
ps. mam coś kombinować z copy czy jest do tego jakaś inna funkcja?
Dziękuje,
pozdrawiam
Hey,
Ad 1.
Hmmm, sam BAT ma kiepską obsługę wyciągania danych z pliku, natomiast jakieś narzędzia z coreutils (http://gnuwin32.sourceforge.net/packages/coreutils.htm) mogą w tym pomóc, np. połączenie head z tail, albo lepiej - dd.
Ad 2.
Nie, nie musi.
Cheers,
Add a comment: