Moduł 12

Materiały do zajęć z Systemów operacyjnych prowadzonych na Wydziale Matematyki i Informatyki Uniwersytetu im. Adama Mickiewicza w Poznaniu.

« Wróć do spisu materiałów

Kontrola wykonywania poleceń

Do tej pory konstruowaliśmy programy wsadowe, które można by określić mianem liniowych – polecenia były wykonywane jedno po drugim, począwszy od pierwszego, aż do ostatniego. Często zachodzi jednak dość oczywista potrzeba ściślejszej kontroli nad przepływem programu wsadowego, w tym możliwość odwołania się do zewnętrznych programów, przemieszczania się wewnątrz poleceń w naszym programie wsadowym, czy w końcu pętle i wykonywanie warunkowe.

Polecenie pause

Podstawowym poleceniem służącym do chwilowego wstrzymania wykonywania programu wsadowego jest polecenie pause. Powoduje ono wyświetlenie odpowiedniego komunikatu i wznowienie wykonywania programu po rozpoznaniu wciśnięcia dowolnego klawisza.

Uruchom poniższy program wsadowy:

@echo off
echo Napis
pause
echo Inny napis

Sprawdź efekty.

Polecenia goto oraz call

Interpreter programów wsadowych pozwala na stosowanie etykiet do oznaczania wewnątrz programu miejsc, do których być może będziemy chcieli się odwołać w przyszłości. Aby utworzyć etykietę, należy poprzedzić jej nazwę symbolem dwukropka. Odwołanie do niej możliwe jest za pomocą polecenia goto, którego argumentem jest jej nazwa.

Spójrz na kod źródłowy poniższego programu wsadowego:

@echo off
:wypisz
echo Napis
goto wypisz

Czy ten program się kiedyś zakończy? Dlaczego? Jaką znaną konstrukcję pętli Ci to przypomina?

Sprawdź, czy wielkość liter w nazwach etykiet ma znaczenie. W tym celu spróbuj odwołać się do etykiety wypisz z poprzedniego ćwiczenia za pomocą polecenia goto WYPISZ.

Polecenie goto można wywołać z argumentem :eof. Wówczas działanie programu wsadowego zostanie przeniesione na koniec pliku, co w praktyce spowoduje, że program ten się zakończy. Zwróć uwagę na fakt, że w tym wywołaniu argument wyjątkowo poprzedzony jest symbolem dwukropka.

Wykonaj poniższy program wsadowy:

@echo off
echo Napis
goto :eof
echo Inny napis

Czy drugi napis się wyświetli?

Zaproponowane przejście do końca pliku nie czyni żadnej istotnej różnicy względem poznanego już na poprzednich ćwiczeniach polecenia exit z parametrem /b.

Polecenie call pozwala wykonać fragment bieżącego programu (poprzez odwołanie przez etykietę) lub cały zewnętrzny program wsadowy jak jedno polecenie. Funkcjonowanie polecenia call najlepiej przetestować na przykładach.

Aby odwołać się do fragmentu bieżącego programu wsadowego poprzez etykietę, należy wywołać polecenie call z argumentem będącym nazwą tej etykiety poprzedzoną dwukropkiem.

W przypadku polecenia goto nie musieliśmy poprzedzać nazwy etykiety dwukropkiem (z wyjątkiem :eof). Polecenie call może jednak przyjmować jako argument nazwę zewnętrznego programu wsadowego. Poprzedzenie etykiety symbolem dwukropka wprowadza jednoznaczność interpretacji.

Utwórz program wsadowy o następującej treści:

@echo off
echo Witaj swiecie
call :funkcja
echo Funkcja sie skonczyla
goto :eof

:funkcja
echo Jestem funkcja
goto :eof

Sprawdź, co by się stało, gdyby zamienić ostatni (9.) wiersz tego programu na exit lub exit /b.

Utwórz program wsadowy o następującej treści:

@echo off
echo Moje argumenty to: %1 %2 %3
call :funkcja ala ma kota
goto :eof

:funkcja
echo Moje argumenty to: %1 %2 %3
goto :eof

Następnie wywołaj go z dowolnymi trzema argumentami. Wyciągnij wnioski. Sprawdź, czy podobna własność zachodzi dla zmiennych środowiskowych.

Polecenie call może także służyć do wywoływania zewnętrznych programów wsadowych. Warto jednak zdawać sobie sprawę z konsekwencji jego pominięcia do tego celu.

Utwórz program wsadowy napis.cmd o zawartości:

@echo off
echo Jestem programem napis.cmd. Moje argumenty to: %1 %2 %3

oraz program wsadowy program.cmd o zawartości:

@echo off
napis ala ma kota
echo Program napis.cmd sie wykonal

Czy wywołanie programu program.cmd spowoduje wypisanie napisu Program napis.cmd sie wykonal? Czy zmieni się to, jeśli program wsadowy napis.cmd wywołamy za pośrednictwem polecenia call?

Uporządkuj sobie wiadomości dotyczące poleceń goto oraz call. Jeśli to niezbędne, wykonaj eksperymenty we własnym zakresie.

Pętla for

W języku programów wsadowych systemu Windows istnieje konstrukcja pętli for o ogólnej strukturze:

for %zmienna in (zbior) do polecenie parametry

W przypadku pętli wykorzystywanych w programach wsadowych, konstrukcja ta ma postać:

for %%zmienna in (zbior) do polecenie parametry

Jako że na tych ćwiczeniach zajmujemy się programami wsadowymi, będziemy stosować głównie podwójny zapis. Taki też należy zachować, odwołując się do zmiennej iteracyjnej w wykonywanym poleceniu.

Odwołania do zmiennej zmienna wewnątrz pętli możliwe są przez zastosowanie konstrukcji z jednym znakiem procenta (lub dwoma w programach wsadowych), znanej nam już odkąd zapoznaliśmy się z parametrami pozycyjnymi. Należy jednak pamiętać o jednej ważnej rzeczy: nazwa zmiennej iteracyjnej może mieć co najwyżej jeden znak długości. Uwaga: zmienne iteracyjne są jednym z niewielu elementów języka wiersza poleceń, w którym wielkość liter ma znaczenie. Zmienna %a jest zatem inną zmienną niż %A.

Wykonaj bezpośrednio w wierszu poleceń polecenie

> for %f in (ala ma kota) do echo %f

Następnie napisz program wsadowy, który działa tak samo.

Wykonaj poniższy program wsadowy:

@echo off
set wprowadzenie=W koszyczku lezy
for %%f in (kot pies mysz) do echo %wprowadzenie% %%f.

Zauważ, w jaki sposób odwołujemy się do zmiennych środowiskowych.

Jeśli rozszerzenia poleceń są włączone (a w naszym przypadku są), to polecenie for można wywołać z następującymi przełącznikami:

/Djeśli w zbiorze wartości są stosowane dopasowania wzorów, to stosuje je do katalogów, a nie plików
/Ruruchamia pętlę for w każdym katalogu drzewa o wskazanym korzeniu; jeśli korzenia nie wskazano, uznaje się za niego bieżący katalog; jeśli zbiór będzie określony pojedynczym symbolem kroki, wówczas pętla będzie iterować po katalogach
/Lstosuj iterację numeryczną; zbiór wartości określa wówczas odpowiednio wartość początkową, krok i wartość końcową

Przeanalizuj następujące przykłady zastosowania pętli for:

> for %f in (r*) do @echo %f
> for /D %f in (r*) do @echo %f
> for /R %f in (r*) do @echo %f
> for /R %f in (*) do @echo %f
> for /R C:\Users\Bartek %f in (.) do @echo %f
> for /R /D %f in (a*) do @echo %f
> for /L %f in (1,2,10) do @echo %f

Pamiętaj, że jeśli umieszczasz pętle wewnątrz programu wsadowego, zmienne iteracyjne powinny być poprzedzone podwójnym symbolem procenta.

Instrukcja if

Podstawowa konstrukcja if przyjmuje jedną z trzech form:

if [not] errorlevel liczba polecenie parametry
if [not] ciag1==ciag2 polecenie parametry
if [not] exist plik polecenie parametry

Pierwsza z nich powoduje wykonanie polecenia, jeśli wartość zmiennej %errorlevel% jest równa co najmniej wskazanej liczbie. Druga służy do porównywania ciągów znaków, trzecia zaś weryfikuje istnienie pliku. Instrukcja if przyjmuje opcję /I, która powoduje, że różnice w wielkości znaków są ignorowane.

Przetestuj działanie następujących poleceń (w folderze zawierającym plik plik.txt oraz katalog katalog):

if abc==abc echo Rowne
if ABC==abc echo Rowne
if /i ABC==abc echo Rowne
if not "abc"==abc echo Rozne
if exist plik.txt echo Plik istnieje
if not exist katalog echo Katalog nie istnieje

Czy konstrukcja exist może być stosowana do weryfikowania istnienia katalogów?

Instrukcja if pozwala na zastosowanie słowa kluczowego else. Musi ono jednak wystąpić w tej samej linii, np.:

if exist plik.txt (echo Plik istnieje) else (echo Plik nie istnieje)

Warto zauważyć, że w tym przypadku zastosowano nawiasy grupujące polecenia.

Sprawdź, co by się stało, gdyby pominąć nawiasy grupujące. Sprawdź też, czy zapis

if exist plik.txt (
    echo Plik istnieje
) else (
    echo Plik nie istnieje
)

jest równoważny. Jeśli tak, to w jaki sposób można wykonać blok instrukcji w przypadku, gdy warunek (nie) jest spełniony?

Jeśli rozszerzenia poleceń są włączone, to instrukcja if staje się o wiele bardziej rozbudowana. Przyjmuje ona wówczas kilka innych form, w tym

if [not] ciag1 operator ciag2 polecenie parametry

gdzie operator jest jednym z poniższych:

EQUoperator równości
NEQoperator różności
LSSoperator mniejszości
LEQoperator mniejszości lub równości
GTRoperator większości
GEQoperator większości lub równości

oraz

if [not] defined zmienna polecenie parametry

która pozwala na sprawdzenie, czy zmienna o wskazanej nazwie została zdefiniowana. Ta konstrukcja nie może być jednak stosowana do sprawdzenia, czy parametr istnieje.

Operacje na zmiennych w blokach

Spójrz na poniższy program:

@echo off
set var=a
if 1 equ 1 (
    set var=b
    echo %var%
)

Jaka wartość powinna być wypisana na ekranie? Jaka w rzeczywistości została? Dowiedz się, dlaczego.

Zmodyfikuj program z poprzedniego ćwiczenia tak, aby działał poprawnie. W tym celu zastosuj mechanizm z poleceniem call.

Zobacz odpowiedź

@echo off
set var=a
if 1 equ 1 call :fun
goto :eof

:fun
set var=b
echo %var%
goto :eof

Rozwiązanie będące efektem powyższego ćwiczenia nie jest zbyt czytelne. Na szczęście problem można rozwiązać inaczej.

Zmodyfikuj program z ćwiczenia przed poprzednim tak, aby działał poprawnie. W tym celu zmień jego treść na:

@echo off
setlocal EnableDelayedExpansion
set var=a
if 1 equ 1 (
    set var=b
    echo !var!
)

Co się zmieniło? Dowiedz się, czym jest EnableDelayedExpansion. W jaki sposób odwołujemy się do zmiennych wewnątrz bloku instrukcji if? Na jakim etapie są one zamieniane na wartości?

Ćwiczenia podsumowujące

Napisz program wsadowy, który wypisze przekazane argumenty w odwrotnej kolejności.

Zobacz odpowiedź

@echo off
if "%1"=="" exit /b
set wynik=%1
:poczatek
if "%2"=="" goto wypisz
set wynik=%2 %wynik%
shift
goto poczatek
:wypisz
echo %wynik%

Zmienna %RANDOM% zwraca losową wartość całkowitą dodatnią z~przedziału od 0 do 32767 za każdym razem, gdy jest wywoływana. Wykorzystaj ją, aby napisać program wsadowy, w którym użytkownik zgaduje wylosowaną liczbę z przedziału od 100 do 1000. Program ten powinien naprowadzać użytkownika na prawidłowe rozwiązanie, wypisując komunikaty "Więcej" lub "Mniej", w zależności od wprowadzonej liczby. Gdy użytkownik wprowadzi dobry wynik, program powinien zakończyć działanie.

Uwaga. Operacja dzielenia modulo jest oznaczana przez pojedynczy symbol procenta tylko bezpośrednio w wierszu poleceń. W programach wsadowych należy zapisywać ją podwójnie (%%). Gdzie jeszcze taki zapis był niezbędny?

Zobacz odpowiedź

@echo off
set /a n=100+(%random% %% 901)
:zgaduj
set /p l=Podaj liczbe:
if %l% gtr %n% echo Mniej
if %l% lss %n% echo Wiecej
if %l% equ %n% exit /b
goto zgaduj

Zaimplementuj algorytm Euklidesa (z dzieleniem) wyznaczania NWD dwóch liczb dodatnich. Program wsadowy powinien pobierać po dwie liczby ze zbioru argumentów, przy czym można wprowadzić więcej zestawów liczb (np. program wywołany z sześcioma parametrami powinien wypisać na wyjściu trzy wartości, po jednej w linii, każda będąca NWD kolejnych dwóch liczb).

Zobacz odpowiedź

@echo off
setlocal EnableDelayedExpansion
:poczatek
if "%1"=="" goto :eof
set a=%1
set b=%2
set /a r="%a% %% %b%"
:petla
if %r% neq 0 (
    set a=!b!
    set b=!r!
    set /a r="!a! %% !b!"
    goto petla
)
echo %b%
shift
shift
goto poczatek

Napisz program wsadowy, który przekształca przekazane jako argumenty liczby dziesiętne do postaci dwójkowej. Każda wartość powinna być wypisana w osobnej linii.

Zobacz odpowiedź

@echo off
setlocal EnableDelayedExpansion
:poczatek
if "%1"=="" exit /b
set liczba=%1
set /a wynik=%liczba% %% 2
set /a liczba=%liczba%/2
:petla
if %liczba% gtr 0 (
    set /a temp=!liczba! %% 2
    set wynik=!temp!!wynik!
    set /a liczba=!liczba!/2
    goto petla
)
echo %wynik%
shift
goto poczatek