» Логические конструкции в скриптах NSIS

K.A.V.
www.oszone.net
22.02.2010
Автор: kotkovets
Последнее обновление: 18.12.2010

Условно выполняющийся код или выполняющийся код в цикле может быть создан с помощью команд:
StrCmp  IntCmp  IfErrors  Goto   IfAbort  IfFileExists  IfRebootFlag  IfSilent используя метки или относительные переходы.
С помощью библиотеки Logiclib.nsh которая содержит множество макросов, можно использовать простые конструкции сложных логических структур.
В скрипт эта библиотека подключается просто:
!include Logiclib.nsh
Давайте рассмотрим практическое использование макросов с библиотеки Logiclib.nsh
Пример проверки значения переменной без логических конструкций:
Section
StrCpy $0 11
StrCmp $0 11 0 done
 MessageBox MB_OK $$0=12
 Goto done
StrCmp $0 12 0 done
 MessageBox MB_OK $$0=11
 Goto done
#else
 MessageBox MB_OK '$$0 is "$0"'
done:
SectionEnd
Пример проверки значения переменной c помощью логических конструкций:
Section
StrCpy $0 11
${If} $0 == 11
   MessageBox MB_OK $$0=11
${ElseIf} $0 == 12
   MessageBox MB_OK $$0=12
${Else}
   MessageBox MB_OK "прочие значения $$0"
${EndIf}
SectionEnd
Макрос ${If} открывает конструкцию, а макрос ${EndIf} закрывает конструкцию.
В этом примере с помощью StrCpy мы поместили в переменную $0 число 11, следовательно сработает макрос ${If}
и выполнится код под этим макросом, если в $0 поместим число 12, сработает макрос ${ElseIf}
Если $0 равно любому другому значению, то сработает макрос ${Else}
Причем макрос ${If} пишется один раз (так, как он открывает конструкцию)
Макрос ${ElseIf} можем писать сколь угодно раз, и вовсе необязательно проверять одну переменную, а множество переменных с множеством значений!
Если вам по условию не нужна проверка иных значений пременных, исключаем ${Else}
Так же это касательно макроса ${ElseIf}. Макрос ${EndIf} закрывает конструкцию, так что пишем его всегда и один раз.
Section
${If} $0 == 11
   MessageBox MB_OK $$0=11
${ElseIf} $0 == "some value 1"
   MessageBox MB_OK "$$0=some value 1"
${ElseIf} $R0 == "some value 2"
   MessageBox MB_OK "$$R0=some value 2"
${ElseIf} $R1 == "some value 3"
   MessageBox MB_OK "$$R1=some value 3"
${EndIf}
SectionEnd

Примеры на сравнении числовых значений.

Посмотрите здесь, а так на логической конструкции:
Section
StrCpy $0 "7"
${If} $0 == 5
    MessageBox MB_OK "$0 = 5"
${ElseIf} $0 < 5
    MessageBox MB_OK "$0 < 5"
${ElseIf} $0 > 5
    MessageBox MB_OK "$0 > 5"
${EndIf}
SectionEnd
Вот еще примерчик:
Section
StrCpy $0 "7"
${If} $0 => 5
    MessageBox MB_OK "$0=>5"
${EndIf}
SectionEnd
В этом случае макрос ${If} сработает, если $0 равно 5 или больше 5.
Еще интересный пример:
Section
StrCpy $0 "5"
${If} $0 <> 5
    MessageBox MB_OK "$$0<>$0"
${EndIf}
SectionEnd
В этом случае макрос ${If} сработает, если $0 равно любому значению, кроме $0=5
Следующий пример уведомит пользователя, если значения переменных $0 и $1 будут равны:
Section
StrCpy $0 0
StrCpy $1 0
 ${If} $0 == 0
 ${AndIf} $1 == 0
    MessageBox MB_OK $0=$1
 ${EndIf}
SectionEnd
Макрос ${AndIf} сработает (при выполнении условия равенства в ${AndIf}) в том случае,
если выполнится условие равенства в макросе ${If}, в ином случае ${AndIf} игнорируется.
Рассмотрим пример на макросе ${OrIf}
Section
StrCpy $0 0
StrCpy $1 0
 ${If} $0 == 0
 ${OrIf} $1 == 0
    MessageBox MB_OK OrIf
 ${EndIf}
SectionEnd
Если в макросе ${If} выполнится условие равенства, то макрос ${OrIf} будет срабатывать всегда, игнорируя условия равенства значений.
Если в макросе ${If} не выполнится условие равенства, то макрос ${OrIf} сработает, если выполнится условие равенства значений.

Директивы в логических конструкциях.

В логических конструкция есть встроеные директивы (макросы). Такие как: ${Silent}  ${Abort}  ${Errors}  ${RebootFlag}  ${FileExists} и т.д.
Подробней смотрите в файле ${NSISDIR}\Include\Logiclib.nsh
Посмотрите здесь пример, а на логике пример:
${If} ${Silent}
   MessageBox MB_OK "Тихий запуск"
${Else}
   MessageBox MB_OK "Обычный запуск"
${EndIf}
Пример проверки отмеченных компонентов:
!include "MUI2.nsh"
!include Logiclib.nsh

!insertmacro MUI_PAGE_WELCOME
 !define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "Russian"

OutFile "component.exe"
Name "Select components"
Caption "Select component"
ShowInstDetails show

Section /o "компонент 1" comp_1
 DetailPrint "выбран компонент 1"
SectionEnd

Section /o "компонент 2" comp_2
 DetailPrint "выбран компонент 2"
SectionEnd

Section /o "компонент 3" comp_3
 DetailPrint "выбран компонент 3"
SectionEnd

Function ComponentsLeave
 ${IfNot} ${SectionIsSelected} ${comp_1}
    MessageBox MB_OK "Выберите компонент 1"
    Abort
 ${ElseIfNot} ${SectionIsSelected} ${comp_2}
    MessageBox MB_OK "Выберите компонент 2"
    Abort
 ${ElseIfNot} ${SectionIsSelected} ${comp_3}
    MessageBox MB_OK "Выберите компонент 3"
    Abort
 ${EndIf}
FunctionEnd
Макрос ${IfNot} работает с точностью до наоборот относительно макроса ${If}
Это касательно и других логических макросов.
Пример выполнения встроенной команды в зависимости от значения переменной:
Section
 StrCpy $1 0
 ${IfThen} $1 = 0 ${|} MessageBox MB_OK "IfThen" ${|}
SectionEnd
Макрос ${IfThen} сработает в том случае, если $1=0.
Пример на макросе ${IfNotThen}:
Section
 StrCpy $1 0
 ${IfNotThen} $1 = 1 ${|} MessageBox MB_OK "IfNotThen" ${|}
SectionEnd
Макрос ${IfNotThen} сработает в любом случае, кроме случая, когда $1=1.
Примеры логических макросов, выполняющих встроенные команды, в зависимости от истинности значения функции или команды:
Section
 StrCpy $R2 0
 ${IfCmd} MessageBox MB_YESNO "Нажмите кнопку $\"да$\"" IDYES ${||} StrCpy $R2 1 ${|}
 MessageBox MB_OK $$R2=$R2
SectionEnd
При нажатии в окне сообщения кнопки "да" в переменную $R2 возвратится число 1.
Еще пример:
Section
 ${Unless} ${Cmd} 'MessageBox MB_YESNO "Нажмите кнопку $\"нет$\"" IDYES'
    MessageBox MB_OK 'Вы нажали кнопку "нет"'
 ${EndUnless}
SectionEnd
При нажатии в окне сообщения кнопки "нет" появится соответствующие сообщение.

Множественные условия.

Давайте рассмотрим пример с одной переменной принимающей множесто значений на макросе ${Switch}:
Section
StrCpy $0 2
${Switch} $0
 ${Case} 1
   MessageBox MB_OK '$$0=1'
   ${Break}
 ${Case} 2
   MessageBox MB_OK '$$0=2'
   ${Break}
 ${Default}
   MessageBox MB_OK "прочие значения $$0"
${EndSwitch}
SectionEnd
Конструкцию открывает макрос ${Switch}, закрывает макрос ${EndSwitch}
Данная конструкция работает, только с одной выбранной переменной!
В этом примере, мы поместили в переменную $0 число 2, следовательно сработает макрос ${Case} со значением равенства 2
Если значение $0 возвратится число 1, следовательно сработает макрос ${Case} со значением равенства 1
Макрос ${Break} прерывает конструкцию, там, где сработал ${Case} удовлетворяющему значению.
Макрос ${Default} срабатывает при иных значених $0 неуказанные в конструкции.
Естественно, если вам нужны иные условия логики, можно исключить ${Break}, тогда условный код начнет
выполняться от сработавшего макроса ${Case}, игнорируя другие условия (принцип домино) ${Case} до конца конструкции.
Макрос ${Case} можно писать неограниченно число раз, т.е столько, сколько вам нужно.
Если вам нужно по условиям логики ${Default} можете исключить.
Пример на макросе ${Select}
Section
StrCpy $0 spravka
${Select} $0
  ${Case} "4"
     MessageBox MB_OK '$$0=$0'
  ${Case2} "2" "3"
     MessageBox MB_OK '$$0=$0'
  ${Case5} "1" "russian" "spravka" "nsis" "good"
     MessageBox MB_OK '$$0=$0'
  ${CaseElse}
${EndSelect}
SectionEnd
Здесь макрос ${Case} может срабатывать до пяти значений переменной $0
Макрос ${CaseElse} сработает в иных случаях, неуказанные в ${Case}
${Case} можно писать сколь угодно раз, т.е столько, сколько вам нужно.

Циклы на логических конструкциях.

Посмотрите здесь пример цикла, а вот так на логике:
!include "LogicLib.nsh"

OutFile "EnumRegKey.exe"
ShowInstDetails show

Section EnumRegKey
StrCpy $0 0
 ${Do}
   EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
   ${If} $1 == ''
     ${ExitDo}
   ${EndIf}
   IntOp $0 $0 + 1
   DetailPrint "$0) $1"
 ${loop}
SectionEnd
Макрос ${Do} начинает цикл, макрос ${loop} возвращает на начало цикла ${Do}
Когда в переменную $1 "свалится" пустое значение, сработает макрос ${ExitDo} - выход из цикла.
А можно сделать и так:
Section EnumRegKey
StrCpy $0 0
 ${Do}
   EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
   ${If} $1 == 'nsis'
      MessageBox MB_OK "NSIS установлен"
      ${ExitDo}
   ${EndIf}
   IntOp $0 $0 + 1
   DetailPrint "$0) $1"
 ${LoopUntil} $1 == ''
SectionEnd
Здесь будет выход из цикла, когда в переменную $1 возвратится nsis или пустое значение.
А вот еще интересный цикл:
Section EnumRegKey
StrCpy $0 0
StrCpy $1 0
 ${DoUntil} $1 == ''
   EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
   IntOp $0 $0 + 1
   DetailPrint "$0) $1"
 ${Loop}
SectionEnd
Здесь не начнется цикл, если в момент начало цикла, если $1, будет равно пустому значению.
В то же самое время цикл закончится, когда в $1 свалится пустое значение.

Примеры числовых циклов.
Пример отсчета до 5:
!include "LogicLib.nsh"

OutFile "While.exe"
ShowInstDetails show

Section
 ${While} $R1 < 5
    Sleep 500 #для наглядности
    IntOp $R1 $R1 + 1 #c каждым циклом значение $R1 увел. на 1
    DetailPrint $R1
 ${EndWhile}
SectionEnd
Цикл начнется, когда переменная $R1 меньше 5, причем отсчет начнется, от текущего значения $R1, в момент начала цикла.
Цикл закончится, когда в переменную $R1 "свалится" число 5.
Пример отсчета на макросе ${For}:
!include "LogicLib.nsh"

OutFile "For.exe"
ShowInstDetails show

Section
 ${For} $R1 0 5
    Sleep 500 #для наглядности
    DetailPrint $R1
 ${Next}
SectionEnd
Отсчет начнется от 0, закончится когда $R1=5, причем все изменения помещаются в $R1.
Еще пример:
!include "LogicLib.nsh"

OutFile "For.exe"
ShowInstDetails show

Section
 ${For} $R1 0 7
   Sleep 500 #для наглядности
   ${If} $R1 == 4
      MessageBox MB_OK $$R1=$R1
      ${ExitFor}
   ${EndIf}
   DetailPrint $R1
 ${Next}
SectionEnd
Здесь отсчет до 7, когда в $R1 свалится число 4, сработает макрос ${ExitFor} - выход из цикла.
Пример на макросе ${ForEach}:
!include "LogicLib.nsh"

OutFile "ForEach.exe"
ShowInstDetails show

Section
 ${ForEach} $R1 16 4 - 3
    Sleep 500 #для наглядности
    DetailPrint $R1
 ${Next}
SectionEnd
Здесь отсчет начинается от 16, все результаты в $R1, с каждым циклом $R1 уменьшается на 3,
до тех пор пока в $R1 свалится число 4 - условие выхода из цикла.

Библиотека LogicLib.nsh устраняет необходимость в метках и относительных переходах,
таким образом предотвращает конфликты имен меток, и отпадает необходимость вручную
корректировать относительные смещения переходов каждый раз, когда скрипт изменяется.
Еще примеры смотрите в ${NSISDIR}\Examples\LogicLib.nsi