get the hash of a string and eventually compare it to a hash
It's helpful to expand out your code into separate lines when debugging it. You can even place the lines in a "batch" file to run it and test your attempts at solving it. I'll be showing the changes I make expanded out into multiple lines for readability.
For reference, expanded out into multiple lines your code looks like this:
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
Note that, to place the above into a batch file, you would also need to replace %i
with %%i
. DOS/CMD is weird like that.
The Problem and Solution.
There are actually two problems in your question... and one of them is masking the other.
The problem that you are being mislead by is that you have left "echo" turned on. To explain, for
executes everything after do
as a command once for each iteration of the loop. In DOS/CMD commands are echoed to the terminal before executing them. Thus, every time for
executes the commands you gave it, CMD prints those commands to the terminal. This is perfectly normal. However, it is confusing and misleading. To change this behavior you need to issue an ECHO OFF
first. Like so:
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
The result will be no output of any kind. That's because of the real problem. That problem is your if-statement's conditional. You left out the "variable markers".
firstLine==1
will compare the string firstLine
with 1
and determine they are not the same... because of course they aren't. But you can't just put %
around firstLine
. There's a gotcha with delayed expansion of variables... you want to use !
not %
. So that conditional should be !firstLine!==1
.
You also need to correct the echo %x%
to echo !x!
What was happening is that it was echoing the commands it executed... but the if conditional was always false so it didn't do anything else. The echoing of the commands became a red-herring masking the real problem.
It's also good practice, when comparing strings, to enclose them in square brackets ([]
). Most sources will suggest using double quotes "
instead, however, this can lead to a number of problems with how DOS/CMD parse double quotes. And using nothing, as you have here, can create problems when your variable has no value at all. The exception is when you performing a numerical comparison with a numerical comparison operator such as EQU
or GTR
.
This gives you:
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
But turning this into a one-liner wont quite work for several other reasons:
Other Critical Issues
SETLOCAL
Only Works in BATCH Files
To quote the help text from the command itself:
C:\Users\wolferz>help setlocal
Begins localization of environment changes in a batch file. Environment changes made after SETLOCAL has been issued are local to the batch file. ENDLOCAL must be issued to restore the previous settings. When the end of a batch script is reached, an implied ENDLOCAL is executed for any outstanding SETLOCAL commands issued by that batch script.
However! There is a workaround for using delayed expansion in a one-liner on the command line that also keeps your variables local to the execution of that one line. Simply prefix your command with CMD /V:ON /C
, remove setlocal enabledelayedexpansion
and endlocal
, and place your one-liner in double quotes.
What does this do?
CMD
is the command to run the command prompt. You can actually run the command prompt inside the command prompt. In this case it has the effect of localizing any environment variables we set while it's running.
The /V:ON
enables delayed expansion for all commands run under this new, nested command prompt.
And /C
tells CMD that what follows in double quotes is a command you want executed in this new, nested command prompt.
So this will run your one-liner inside a new, nested command prompt with delayed expansion turned on. Great! Still not quite there though.
For
Options Must Have A Space Between Them
"skip=1delims="
needs to be "skip=1 delims="
.
Missing i
In firstLine
In if
Code Block
firstLne=0
needs to be firstLine=1
The Fix'd One-Liner
At this point we have:
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!"
)
)
This condenses down to a one-liner of (with 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!"" ) )
Now I can't promise this will do exactly what you want it to... but it won't fail because of syntactical errors.
Also, last bit of advice: learn PowerShell, not DOS/CMD. In addition to DOS/CMD being hampered by decades of issues and confusing syntax caused by backwards compatibility... it's also not the default shell on modern Windows installations, and will likely be abandoned entirely in the next decade. PowerShell is a fresh start with a much more intelligent syntax and far, far more powerful scripting capabilities. And, with the advent of PowerShell Core 6, it's now portable to platforms other than Windows.