Вопрос или проблема
Я хочу запустить однострочное выражение, чтобы получить хэш строки и в конечном итоге сравнить его с хэшем, который я могу просто скопировать в конце команды позже. Я уверен, что смогу понять, как сделать сравнение, как только пойму, что не так с моим выводом. Цель на данный момент – просто напечатать хэш файла с помощью certutil, поскольку она встроена в Windows. Я выполняю это:
setlocal enabledelayedexpansion && set "firstLine=1" & for /f "skip=1delims=" %i in ('certutil -hashfile file.zip SHA512') do (if firstLine==1 (set "x=%i"&set "firstLne=0"&echo "%x%")) & endlocal
и в итоге получаю:
C:\Users\John\Downloads>(if firstLine == 1 (set "x=9ba9467f05f1c7fa7161f857b0085461ce28401a2fe01a8062eec2254eaafc4b239fb3dc9298b5df5f27c2bb64618a8606a6885aa171604c541f4d5fe394b361" & set "firstLne=0" & echo "%x%" ) ) & endlocal
C:\Users\John\Downloads>(if firstLine == 1 (set "x=CertUtil: -hashfile command completed successfully." & set "firstLne=0" & echo "%x%" ) ) & endlocal
C:\Users\John\Downloads>
Похоже, что все после do выполняется снова как строка. Первая строка вывода пропущена, но поскольку все в if-выражении не может выполниться, следующие две строки из for-выражения “выполняются”.
Полезно развернуть ваш код на отдельные строки при его отладке. Вы даже можете поместить строки в файл «batch», чтобы запустить его и протестировать свои попытки решить задачу. Я покажу изменения, которые я внес, развернув их на несколько строк для удобочитаемости.
Для справки, в развернутом виде ваш код выглядит так:
setlocal enabledelayedexpansion && set "firstLine=1"
for /f "skip=1delims=" %i in ('certutil -hashfile file.zip SHA512') do (
if firstLine==1 (
set "x=%i"
set "firstLne=0"
echo "%x%"
)
)
endlocal
Обратите внимание, что для размещения вышеуказанного в batch-файле вам также потребуется заменить %i
на %%i
. DOS/CMD странен в этом плане.
Проблема и решение.
В вашем вопросе на самом деле две проблемы… и одна из них скрывает другую.
Проблема, которая вводит вас в заблуждение, заключается в том, что вы оставили “echo” включенным. Чтобы объяснить, for
выполняет все после do
как команду один раз для каждой итерации цикла. В DOS/CMD команды выводятся на терминал до их выполнения. Таким образом, каждый раз, когда for
выполняет команды, которые вы ему дали, CMD выводит эти команды на терминал. Это совершенно нормально. Однако это сбивает с толку и вводит в заблуждение. Чтобы изменить это поведение, вам нужно сначала выключить ECHO
. Это можно сделать так:
echo off
setlocal enabledelayedexpansion && set "firstLine=1"
for /f "skip=1delims=" %i in ('certutil -hashfile file.zip SHA512') do (
if firstLine==1 (
set "x=%i"
set "firstLne=0"
echo "%x%"
)
)
endlocal
Результатом будет отсутствие какого-либо вывода. Это из-за настоящей проблемы. Эта проблема заключается в вашем условии if-выражения. Вы забыли “маркеры переменной”.
firstLine==1
будет сравнивать строку firstLine
с 1
и определит, что они не равны… потому что, конечно, они не равны. Но вы не можете просто поставить %
вокруг firstLine
. Есть подвох с задержкой расширения переменных… вы хотите использовать !
вместо %
. Таким образом, условие должно быть !firstLine!==1
.
Вам также необходимо исправить echo %x%
на echo !x!
То, что происходило, это эхо команд, которые выполнялись… но условие if всегда было ложным, поэтому ничего больше не происходило. Эхо команд стало ложной уликой, скрывающей настоящую проблему.
Также хорошей практикой является заключение строк в квадратные скобки ([]
) при их сравнении. Большинство источников предложат использовать двойные кавычки "
вместо этого, однако это может привести к ряду проблем с тем, как DOS/CMD парсит двойные кавычки. И использование ничего, как у вас здесь, может создать проблемы, когда ваша переменная не имеет значения вообще. Исключение составляет выполнение численного сравнения с численным оператором сравнения, таким как EQU
или GTR
.
Это дает вам:
echo off
setlocal enabledelayedexpansion && set "firstLine=1"
for /f "skip=1delims=" %i in ('certutil -hashfile file.zip SHA512') do (
if [!firstLine!]==[1] (
set "x=%i"
set "firstLne=0"
echo "!x!"
)
)
endlocal
Но превращение этого в однострочную команду не сработает по нескольким другим причинам:
Другие критические проблемы
SETLOCAL
работает только в BATCH-файлах
Чтобы процитировать текст помощи самой команды:
C:\Users\wolferz>help setlocal
Начинает локализацию изменений окружения в batch-файле.
Изменения в окружении, выполненные после выдачи SETLOCAL, локальны для batch-файла. ENDLOCAL должно быть выдано для восстановления предыдущих настроек. Когда программа batch завершает выполнение, происходит неявный ENDLOCAL для любых выданных команд SETLOCAL.
Тем не менее! Существует обходной путь для использования задержки расширения в однострочной команде на командной строке, который также сохраняет ваши переменные локальными для выполнения этой одной строки. Просто добавьте команду CMD /V:ON /C
в начало, удалите setlocal enabledelayedexpansion
и endlocal
, и поместите вашу однострочную команду в двойные кавычки.
Что это делает?
CMD
– это команда для запуска командной строки. Вы на самом деле можете запустить командную строку внутри командной строки. В данном случае это имеет эффект локализации любых переменных окружения, которые мы устанавливаем во время его выполнения.
/V:ON
включает задержку расширения для всех команд, выполняемых в этой новой, вложенной командной строке.
А /C
говорит CMD, что следующее в двойных кавычках – это команда, которую вы хотите выполнить в этой новой, вложенной командной строке.
Таким образом, это выполнит вашу однострочную команду внутри новой, вложенной командной строки с включенной задержкой расширения. Замечательно! Но это еще не все.
Опции For
должны иметь пробел между ними
"skip=1delims="
нужно заменить на "skip=1 delims="
.
Пропущена i
в firstLine
в блоке кода if
firstLne=0
нужно заменить на firstLine=1
Исправленная однострочная команда
На данный момент у нас есть:
echo off
set "firstLine=1"
for /f "skip=1 delims=" %i in ('certutil -hashfile file.zip SHA512') do (
if [!firstLine!]==[1] (
set "x=%i"
set "firstLine=0"
echo "!x!"
)
)
Это сводится к однострочной команде (с CMD /V:ON /C
):
cmd /v:on /c "echo off & set "firstLine=1" & for /f "skip=1 delims=" %i in ('certutil -hashfile file.zip SHA512') do ( if [!firstLine!]==[1] ( set "x=%i" & set "firstLine=0" & echo "!x!"" ) )
Теперь я не могу обещать, что это сделает именно то, что вам нужно… но это не потерпит неудачу из-за синтаксических ошибок.
Также, последнее совет: изучите PowerShell, а не DOS/CMD. Помимо того, что DOS/CMD ограничен десятилетними проблемами и запутанным синтаксисом из-за обратной совместимости… это также не является оболочкой по умолчанию в современных установках Windows и, вероятно, будет полностью заброшено в ближайшие десятилетие. PowerShell является свежим началом с более разумным синтаксисом и намного более мощными возможностями сценариев. И, с появлением PowerShell Core 6, он теперь переносим на другие платформы, кроме Windows.
- Ну, если вам нужна одна строка, чтобы сравнить строку SHA512, и она известна заранее:
cmd /v /s /c ""%__APPDIR__%certutil.exe" -hashfile 7z1900-x64.exe SHA512|find "af8f38679e16c996ffac152cac49369cf4b609abbd2cad07f49a114a82c6b5e564be29630c0fd2418110cf1a3d0ef3c9cc12f9164a69a575c91d9b98ce0df1a9">nul&&@echo\yEp!||@echo\nOp!"
- Используйте в командной строке или bat/cmd-файле:
cmd /v /s /c " "%__APPDIR__%certutil.exe" -hashfile file.ext SHA512|find "Your String SHA512">nul && @echo\yEp! || @echo\nOp!"
.
Ответ или решение
Для вычисления хеша строки и сравнения его с известным хешем на операционных системах Windows можно использовать встроенную утилиту certutil
. Однако, важно учитывать корректный синтаксис командной оболочки Windows, чтобы избежать распространенных ошибок. В этом ответе предоставляется теоретическое обоснование, практический пример и шаги для применения в командной строке.
Теория
certutil
— это мощная утилита, доступная в Windows, которая, среди прочего, может использоваться для вычисления хешей файлов. Она поддерживает различные алгоритмы хеширования, включая SHA-512. Использование командной оболочки Windows (cmd) иногда может вызывать трудности, связанные с синтаксисом и особенностями обработки переменных. Текущий вопрос касается правильного получения хеша файла и вывода его на экран для дальнейшего сравнения.
Пример
Рассмотрим пример кода, который вычисляет SHA-512 хеш файла и выводит его на экран:
@echo off
setlocal enabledelayedexpansion
set "firstLine=1"
for /f "skip=1 delims=" %%i in ('certutil -hashfile file.zip SHA512') do (
if !firstLine! == 1 (
set "x=%%i"
set "firstLine=0"
echo !x!
)
)
endlocal
В этом коде:
- Устанавливается режим задержанного раскрытия переменных с помощью
setlocal enabledelayedexpansion
. - В цикле
for
выполняется командаcertutil
, хеширующая файл. skip=1
пропускает первую строку вывода (certutil
выводит имя команды на первой строке).- Состояние первой строки контролируется переменной
firstLine
. - Хеш сохраняется в переменной
x
и выводится на экран.
Применение
Для удобства и краткости можно сжать эту команду в однострочник, который также будет сравнивать вычисленный хеш с ожидаемым, используя команду find
. Вот как это может выглядеть в практическом применении:
cmd /v:on /c "set "firstLine=1" & for /f "skip=1 delims=" %i in ('certutil -hashfile file.zip SHA512') do ( if !firstLine!==1 ( set "x=%i" & set "firstLine=0" & echo !x!" ) ) | find "expected_hash_string" >nul && echo Success || echo Failure"
Примечания по использованию:
- Синтаксис
%i
используется при запуске команды из командной строки. Для бат-скриптов используйте%%i
. - Замена
expected_hash_string
на ваш хеш позволяет автоматически сравнить его с вычисленным значением. - Команда
find
ищет строку в выводе. Если строка найдена, будет напечатано "Success"; иначе — "Failure".
Этот подход дает гибкость в работе с командной строкой Windows и обеспечивает простой способ сравнения файловых хешей. Если вас интересует более мощная и универсальная платформа, рассмотрите возможность использования PowerShell, который предлагает расширенные функции и более современный подход к управлению системами.