использование переменной индекса DelayedExpansion для массива в операторе IF не удается - PullRequest
0 голосов
/ 17 мая 2018

У меня есть пакетный файл, в котором я отображаю все файлы * .pem в каталоге, давая пользователю возможность выбрать один из них, и в настоящее время просто пытаюсь показать пользователю, какой файл он выбрал, используя выбранное числоЭХО содержимое массива.Я полагаю, что каким-то образом, поскольку он находится внутри оператора IF, индексная переменная «selectedPem» не расширяется в индексных скобках массива, потому что она использует% и не использует отложенное расширение!, Которое, похоже, не работает в индексе массива.скобки.Я думаю, что это потому, что если я установил переменную selectedPem перед оператором IF, он будет правильно отображаться.Я думаю, что единственный вариант для этого - как-то использовать подпрограмму.Почему переменная selectedPem не работает внутри оператора IF?

@echo off
setlocal enabledelayedexpansion 
set dir=%~dp0
cd %dir%

ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
    REM echo %%~nxp
    set pems[%pemI%]=%%~nxp
    REM echo !pems[%pemI%]!
    set /a pemI=%pemI%+1
)
REM echo %pems[0]%
set /a totalPems=%pemI%-1
REM This is the test selectedPem variable that showed me the variable works 
REM if I set it outside 
REM of the "if defined pems[0]" statement
REM set selectedPem=
if defined pems[0] (
    echo PEM Files Found:
    for /l %%p in (0,1,%totalPems%) do (
        echo %%p^) !pems[%%p]!
    )
    ECHO Select a pem file to be used as your private key. Otherwise, press 
    Q to not select one.
    set /P selectedPem=Enter a number or Q:
    IF "!selectedPem!"=="q" (
        ECHO Skipping private key selection.
    ) ELSE (
        ECHO !selectedPem!
        ECHO %pems[0]%
        REM The below ECHO only works when the above selectedPem is set 
        REM outside of the "IF defined" statement
        ECHO !pems[%selectedPem%]!

        REM Tried these other implementations:
        REM ECHO !pems[!!selectedPem!!]!
        REM ECHO %pems[!selectedPem!]%
        REM setlocal disabledelayedexpansion
        REM ECHO %pems[%%selectedPem%%]%

        set privatekey=!pems[%selectedPem%]!
        ECHO You chose %privatekey%
    )   
)`

Вывод:

Performing initial search of private and public keys...
PEM Files Found:
0) brandon.pem
Select a pem file to be used as your private key. Otherwise, press Q to not 
select one.
Enter a number or Q:0
0
brandon.pem
ECHO is off.
You chose

Я понимаю, что "ECHO выключен".вместо этого выводится, потому что интерпретирует мою ссылку на переменную массива как пустую.Я ценю ваше время на чтение моего сценария, я, вероятно, слишком усложняю его.

Ответы [ 2 ]

0 голосов
/ 18 мая 2018

Вы, похоже, хорошо знаете о отложенном расширении , так как вы используете его в большинстве случаев.

Однако то, что у вас есть в коде, я обычно называю вложенными переменными, потому что в пакетном скриптинге нет реальных массивов, поскольку каждый элемент массива - это не что иное, как отдельная скалярная переменная (pems[0], pems[1], pems[2], так далее.); так что мы можем называть что-то подобное также псевдо-массивами.

В любом случае, что-то вроде echo !pems[%selectedPem%]! может быть правильно расширено, если только оно не помещено в строку или блок кода, в котором selectedPem обновляется раньше, потому что тогда нам также потребуется задержанное расширение для selectedPem. echo !pems[!selectedPem!]! не работает, потому что он пытается раскрыть переменные pems[ и ], а selectedPem интерпретируется как буквенная строка.

Прежде чем двигаться вперед, давайте сначала вернемся к echo !pems[%selectedPem%]!: почему это расширяется? Хитрость в том, что у нас есть две фазы расширения: нормальное или немедленное расширение (%), за которым следует отложенное расширение (!). Важной вещью является последовательность: внутренняя из вложенных переменных (отсюда и индекс массива) должна быть израсходована перед внешней (поэтому элемент массива). Нечто подобное echo %pems[!selectedPem!]% не может быть правильно развернуто, потому что (скорее всего) нет переменной с именем pems[!selectedPem!], и после развертывания ее в пустую строку два ! исчезают, и поэтому фаза отложенного расширения никогда их не увидит.

Теперь давайте сделаем еще один шаг: чтобы развернуть нечто подобное echo !pems[%selectedPem%]! внутри строки или блока кода, который также обновляет selectedPem, мы должны избегать немедленного расширения. Мы уже узнали, что отложенное расширение не может быть вложенным, но есть некоторые другие расширения:

  1. Команда call вводит еще одну фазу расширения после отложенного расширения, которая снова обрабатывает % -знаки; поэтому полная последовательность - это немедленное расширение, отложенное расширение и другое % -разложение. Отклоняя первое, давайте используем последние два таким образом, что внутренняя из вложенных переменных будет расширена перед внешней, что означает: call echo %%pems[!selectedPem!]%%. Чтобы пропустить фазу немедленного расширения, мы просто удваиваем % знаков, которые заменяются буквальными, поэтому у нас есть echo %pems[!selectedPem!]% после немедленного расширения. Следующим шагом является отложенное расширение, а затем упомянутое следующее % -расширение.
    Обратите внимание, что метод call довольно медленный, поэтому его интенсивное использование может значительно снизить общую производительность вашего сценария.
  2. Расширение переменных for loop происходит после немедленного расширения, но перед отложенным расширением. Итак, давайте обернем цикл for, который повторяется только один раз и возвращает значение selectedPem, например: for %%Z in (!selectedPem!) do echo !pems[%%Z]!. Это работает, потому что for не обращается к файловой системе, пока не появится подстановочный знак * или ?, который в любом случае не должен использоваться в именах переменных или (псевдо) массивах.
    Вместо стандартного цикла for можно также использовать for /F: for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]! (не требуется строка параметра, например "delims=", если ожидается, что selectedPem будет содержать только индексный номер ).
    Также можно использовать for /L loop (для числовых индексов, конечно): for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]!.
  3. Неявное расширение, установленное командой set /A , означающее, что ни %, ни ! не требуется для чтения переменных, также можно использовать, но только если элемент массива содержит числовое значение (обратите внимание, что /A обозначает арифметику). Поскольку это особенность, характерная для set /A, такое расширение происходит во время выполнения команды, что, в свою очередь, происходит после отложенного расширения. Таким образом, мы можем использовать это так: set /A "pem=pems[!selectedPem!]" & echo !pem!.
  4. Просто для полноты, вот еще один способ: set /A при выполнении в cmd, а не в пакетном контексте, выводит (последний) результат на консоль; учитывая, что это составляет порядковый номер, мы можем записать его на for /F и расширить элемент массива следующим образом: for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]!. set /A выполняется в новом cmd экземпляре for /F, поэтому этот подход, конечно, не самый быстрый.

Существует большой и всеобъемлющий пост, посвященный этой теме, который вы должны подробно изучить: Массивы, связанные списки и другие структуры данных в сценарии cmd.exe (пакетная обработка) .

Для синтаксического анализа командных строк и пакетных сценариев, а также раскрытия переменных в целом обратитесь к этому удивительному потоку: Как анализирует сценарии интерпретатора команд Windows (CMD.EXE)?


Теперь давайте посмотрим на ваш код. Есть несколько вопросов:

  • используйте cd /D вместо cd, чтобы при необходимости также заменить привод; кстати, временная переменная dir не нужна (позвольте мне рекомендовать не использовать имена переменных, которые равны внутренним или внешним командам для удобства чтения), просто сразу используйте cd на пути;
  • вы пропустили использование отложенного расширения в строке set pems[%pemI%]=%%~nxp, оно должно читать set pems[!pemI!]=%%~nxp, так как pemI обновляется в окружающем цикле for;
  • вам нужно либо задержанное расширение в строке set /a pemI=%pemI%+1, либо вы используете неявное расширение переменной set /A, поэтому set /A pemI=pemI+1 будет работать; однако это даже можно упростить: set /A pemI+=1, что полностью эквивалентно;
  • Я бы использовал сравнение без учета регистра, особенно когда речь идет о вводе пользователем, например, проверка на Q, которая будет if /I "!selectedPem!"=="q";
  • Теперь мы подошли к линии, которая нуждается в том, что мы узнали выше: ECHO !pems[%selectedPem%]! должно стать call echo %%pems[!selectedPem!]%%; альтернативный способ использования for также вставлен в комментарий (rem);
  • затем есть еще одна строка с той же проблемой: set privatekey=!pems[%selectedPem%]! должен стать call set privatekey=%%pems[!selectedPem!]%% (или подход, основанный также на for);
  • еще раз вы пропустили использование отложенного расширения, на этот раз в строке ECHO You chose %privatekey%, которая должна читать echo You chose !privatekey!;
  • наконец-то я улучшил цитату вашего кода, в частности команду set, которую лучше написать как set "VAR=Value", чтобы любые невидимые конечные пробелы не становились частью значения, а само значение становилось защищен, если содержит специальные символы, но кавычки не становятся частью значения, что может особенно помешать загрязнению;

Итак, вот фиксированный код (со всеми вашими rem замечаниями):

@echo off
setlocal EnableDelayedExpansion 
cd /D "%~dp0"

echo Performing initial search of private and public keys...
set "pemI=0"
set "keyI=0"
for %%p in (*.pem) do (
    set "pems[!pemI!]=%%~nxp"
    set /A "pemI+=1"
)
set /A "totalPems=pemI-1"
if defined pems[0] (
    echo PEM Files Found:
    for /L %%p in (0,1,%totalPems%) do (
        echo %%p^) !pems[%%p]!
    )
    set /P selectedPem="Enter a number or Q: "
    if /I "!selectedPem!"=="q" (
        echo Skipping private key selection.
    ) else (
        echo !selectedPem!
        echo %pems[0]%
        call echo %%pems[!selectedPem!]%%
        rem for %%Z in (!selectedPem!) do echo !pems[%%Z]!
        call set "privatekey=%%pems[!selectedPem!]%%"
        rem for %%Z in (!selectedPem!) do set "privatekey=!pems[%%Z]!"
        echo You chose !privatekey!
    )   
)

Должен признать, что я не проверил логику вашего сценария подробно.

0 голосов
/ 17 мая 2018

С некоторыми переходами вы можете избежать большинства (кодовых блоков).

В тех случаях, когда это невозможно, используйте ! и альтернативное задержанное расширение с вызовом и удвоенное значение %%.

Не проверено:

@echo off
setlocal enabledelayedexpansion 
set dir=%~dp0
cd %dir%

ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
    REM echo %%~nxp
    set pems[!pemI!]=%%~nxp
    REM call echo %%pems[!pemI!]%%
    set /a pemI+=1
)
REM echo %pems[0]%
set /a totalPems=%pemI%
REM This is the test selectedPem variable that showed me the variable works 
REM if I set it outside 
REM of the "if defined pems[0]" statement
REM set selectedPem=

if pemI==0 (Echo no *.pem files found & goto :End)

echo PEM Files Found:
for /l %%p in (0,1,%totalPems%) do (
    echo %%p^) !pems[%%p]!
)
ECHO Select a pem file to be used as your private key. Otherwise, press 
Q to not select one.
set /P selectedPem=Enter a number or Q:
IF /I "!selectedPem!"=="q" (ECHO Skipping private key selection.& goto :End

ECHO !selectedPem!
ECHO %pems[0]%
REM The below ECHO only works when the above selectedPem is set 
REM outside of the "IF defined" statement
ECHO !pems[%selectedPem%]!

REM Tried these other implementations:
REM ECHO !pems[!!selectedPem!!]!
REM ECHO %pems[!selectedPem!]%
REM setlocal disabledelayedexpansion
REM ECHO %pems[%%selectedPem%%]%

set privatekey=!pems[%selectedPem%]!
ECHO You chose %privatekey%
)   
:end
REM ddo wahatever
pause
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...