Nie można uzyskać poprawnego zachowania w pętli wsadowej

2

Korzystam z następującej pętli w pliku wsadowym, aby indywidualnie wywoływać „funkcje” w pliku, zaczynając od wyświetlania tekstu, a kończąc sprawdzaniem i wyświetlaniem poziomu błędu.

Wywołanie funkcji pętli wykorzystuje 2 zmienne.

  1. nazwa funkcji podrzędnej, która ma zostać wywołana
  2. tekst do wyświetlenia na ekranie dla wygody użytkownika

Tak wyglądają te linie telefoniczne

call :loop BackupDatabase "Taking database backup"
call :loop StopBGService "Stopping the background service"

To jest sama funkcja pętli

:loop
    :retry
    echo %~2
    call :%~1

    IF %ERRORLEVEL% NEQ 0 (
        SET ERRORLEV=%ERRORLEVEL%
        ECHO Executing %~2 failed
        ECHO Error Level: %ERRORLEV%
        ECHO.

        set /p ans=Do you want to retry %~2 [y/n]^>
        if "%ans%"== "y" goto :retry
        if "%ans%"== "Y" goto :retry
    )
GOTO :EOF

Poniżej znajdziesz funkcje wywoływane przez każdą pętlę

:BackupDatabase
    Perform database backup commands
GOTO :EOF

:StopBGService
    Perform stop background service commands
GOTO :EOF

Problem, który mam, polega na tym, że gdy „pętla” nie działa poprawnie, skrypt powinien zapytać, czy wymagana jest ponowna próba. Za pierwszym razem, gdy tak się stanie, jeśli wpiszesz „n”, ponawia próbę? Czytałem w kółko skrypt pętli i nie mogę powiedzieć, dlaczego tak jest. Jeśli ktoś może to naprawić, byłaby to świetna pomoc lub ktoś mógłby wskazać lepszy sposób robienia tego, co próbuję osiągnąć.

Ghandi Manning
źródło
Próbowałeś użyć choicezamiast set?
Karan

Odpowiedzi:

2

Problem polega na tym, że w wierszu polecenia systemu Windows / interprecie wiersza poleceń (CLI), znanym jako CMD.EXE, występuje błąd (cóż, jestem pewien, że Microsoft uważa, że ​​jest to funkcja), w którym, gdy czyta konstrukcję bloku, taką jak IF … ( … )lub FORpętla, to interpretuje wszystkie zmienne natychmiast , zanim pętla jest wykonywana. W twoim przykładzie, jeśli jest pusta po wejściu do bloku, wówczas wszystkie wystąpienia wewnątrz tego bloku są oceniane na zero, nawet jeśli zmienisz się w tym bloku. Możesz to zobaczyć, jeśli wyjdziesz (lub włączysz go wcześniej ).%variable_name%ansIF %ERRORLEVEL% NEQ 0 ( … )%ans%ansECHOIF %ERRORLEVEL% NEQ 0

Rozwiązaniem jest powiedzenie CMD, aby zezwoliła na rozszerzenie zmiennych we właściwym czasie, gdy wykonywane są instrukcje odnoszące się do nich. Robisz to, dodając

setlocal enabledelayedexpansion

gdzieś na początku pliku wsadowego i zmienianie kodu okna dialogowego użytkownika, aby wyglądał

    set /p ans=Do you want to retry %~2 [y/n]^>
    if "!ans!" == "y" goto :retry
    if "!ans!" == "Y" goto :retry

za pomocą formularza, aby aktywować opóźnione rozszerzenie. Zobacz i więcej informacji.!variable_name!SET /?SETLOCAL /?

Scott
źródło
To idealnie, dzięki Scott. Czy widzisz jakiś powód, dla którego nie należy domyślnie włączać opóźnionego rozszerzenia SetLocal do wszystkich moich skryptów wsadowych, a następnie użyć! Nazwa_zmiennej! notacja, gdy jest wymagana?
Ghandi Manning
Hmm Niejasno pamiętam, że plik wsadowy, który wymagał opóźnionego rozszerzenia, został wyłączony, ale robił coś niezwykłego. Prawdopodobnie dobrym pomysłem jest włączenie go przez cały czas.
Scott
Wydaje się, że nie sprawia mi to żadnych problemów, więc zostawię to w tym skrypcie lub w innych, które wymagają poprawnego korzystania z pętli FOR. Gdybym miał skrypt, w którym spowodowałoby to problem, wyłączałbym go przed pętlą FOR i ponownie włączał po pętli FOR.
Ghandi Manning
@GhandiManning: Uważaj na to. Czy obejrzysz SETLOCAL /?? „Wyłączasz” a SETLOCAL, mówiąc ENDLOCAL––, i to usuwa wszystkie zmienne, które ustawiłeś od czasu SETLOCAL, przenosi cię z powrotem do katalogu, w którym byłeś przed SETLOCAL, i może niektórych innych rzeczy.
Scott,
Dzięki, będę o tym pamiętać. Na obecnym etapie skrypt nie wymaga, ENDLOCALwięc nie napotkam tych problemów.
Ghandi Manning