windows cmd enabledelayedexpansion not working - windows

I'm having trouble running !var! examples ad described here http://ss64.com/nt/delayedexpansion.html
Instead of the expected variable content output as the example describes, I get the literal "bang V A R bang" output, any idea?
C:\>Setlocal EnableDelayedExpansion
C:\>Set _var=first
C:\>Set _var=second& Echo %_var% !_var!
first !_var!
thanks.

You are getting an unexpected result because you are issuing the commands at the command prompt. Create a batch file by putting the following commands in a file with a .bat extension then run the batch file.
#echo off
Setlocal EnableDelayedExpansion
Set _var=first
Set _var=second& Echo %_var% !_var!
E.g., if I created a batch file named delayedexp.bat with the above contents, I would see the following when I run it:
C:\Users\JDoe\Documents\>delayedexp
first second

setlocal only works within the confines of a command script:
help setlocal

If you have access to the parameters of the cmd call, you can set parameter /v. Must be first. And use ! instead % for variables.
%windir%\system32\cmd.exe /v /c set a=10&echo a=!a!&echo My Path is %CD%&pause
This is how, for example, you can get the date in the Russia-France format directly in the Windows shortcut, where a simple percentage is impossible due to its doubling. In std queries with the /v parameter, both a percentage and an exclamation mark will work fine, but single % for cicles.
%windir%\system32\cmd.exe /v /c echo off&for /F "tokens=1-6 delims=:., " %A In ("!date! !time!") Do (Echo %A.%B.%C %D:%E:%F)&pause

Related

How to assign values to environment variables with dynamic name while parsing similar named XML elements?

I have an XML file in the following manner:
<pools>
<pool>aaa</pool>
<pool>bbb</pool>
<pool>ccc</pool>
<pool>ddd</pool>
<pool>eee</pool>
</pools>
I want to parse these tags in such a way that they will be assigned to variables as
Pool1 = aaa
Pool2 = bbb
and so on
I have tried the below code:
echo off
set /a x=0
SETLOCAL enableextensions enabledelayedexpansion
for /f "tokens=2 delims=<>" %%a in ('find /i "<pool>" ^< "pool_info.xml"') do (
set /a "x+=1"
call ECHO pool%%x%%=%%a
)
And it just prints them properly. I tried the set command for assigning them, but it does not work.
I went through many Stack Overflow problems, but was not able to find any solution that would match my requirement. If anyone could please help me out.
PS: The <pool> tags count here is 5, however, the count can change, so I want it to be flexible.
The task can be done with:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Delete all environment variables of which name starts with Pool.
for /F "delims==" %%I in ('set Pool 2^>nul') do set "%%I="
set "PoolCount=0"
for /F "tokens=2 delims=<> " %%I in ('%SystemRoot%\System32\findstr.exe /I /L /C:"<pool>" "pool_info.xml"') do (
set /A PoolCount+=1
call set "Pool%%PoolCount%%=%%I"
)
rem Output all environment variables of which name starts with Pool.
set Pool
endlocal
ATTENTION: The delimiters are the two angle brackets, a horizontal tab character and a normal space character. Please make sure that the batch file contains exactly those four characters after delims= in that order.
The horizontal tab and the normal space are needed as delimiters to have a working solution independent on leading spaces/tabs on the lines with the pool elements.
The wrong token respectively the missing delimiters tab/space resulted with posted code in question in getting element name pool output instead of the values of the XML element pool.
There is no need to use delayed environment variable expansion in this case.
However, the usage of call to force a second parsing of the command line
call set "Pool%%PoolCount%%=%%I"
modified already during parsing of the entire command block to
call set "Pool%PoolCount%=%I"
before execution of set is slower in comparison to using delayed expansion as used in the code below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Delete all environment variables of which name starts with Pool.
for /F "delims==" %%I in ('set Pool 2^>nul') do set "%%I="
set "PoolCount=0"
for /F "tokens=2 delims=<> " %%I in ('%SystemRoot%\System32\findstr.exe /I /L /C:"<pool>" "pool_info.xml"') do (
set /A PoolCount+=1
set "Pool!PoolCount!=%%I"
)
rem Output all environment variables of which name starts with Pool.
set Pool
endlocal
The reason is explained by jeb in the DosTips forum post CALL me, or better avoid call. The Windows command processor searches with using call set "Pool%%PoolCount%%=%%I" in the batch file in current directory and next in all directories of environment variable PATH for a file matching the wildcard pattern set.*. If there is indeed a file found like set.txt in one of the directories, it searches next in that directory for set.COM, set.EXE, set.BAT, set.CMD, ... according to list of file extensions of environment variable PATHEXT. If there is really an executable or script found by cmd.exe with file name set in current directory or another other directory of PATH with a file extension of PATHEXT, it executes the executable/script instead of running internal command SET.
For that reason it is definitely better to use delayed expansion solution as it is faster and more safe.
The disadvantage is that a pool value with one or more ! is not correct processed with enabled delayed expansion. So once again cmd.exe proves itself that the Windows command processor is designed for executing commands and executables, but not for processing data in text files.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... used for double parsing the command line before execution of set.
echo /?
endlocal /?
findstr /?
for /?
rem /?
set /?
setlocal /?
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded set command line with using a separate command process started in background with %ComSpec% /c and the command line within ' appended as additional arguments.
...
set /a "x+=1"
call SET pool%%x%%=%%a
)
SET pool
The first set assigns the value in %%a to the variable pool?
The second set displays all of the currently-set environment variables whose name starts pool.
setx is a command designed to record a variable assignment for future instances of cmd. It's an entirely different matter and should be raised as a separate question, but there's plenty of SO items about setx so raising it (again) as a separate issue will likely be closed as a duplicate. Best use the search facility for setx.

How do I format Tesseract-OCR language settings within a .bat file's "for" command

I'm new to Windows cmd and .bat, and to Tesseract. But thanks to this list I've managed a couple of successes.
My first success was this cmd-window line:
tesseract.exe -l eng+lat+ita D:\TIFs\Convivio.tiff D:\TIFs\Convivio
My next success was the .bat file:
:Start
#Echo off
ECHO.
ECHO This is a batch file
ECHO.
PAUSE
BREAK=ON
Set _SourcePath=D:\temp\TIFs\*.tif
Set _OutputPath=D:\temp\TIFs\
Set _Tesseract="D:\temp\Tesseract-OCR\tesseract.exe"
:Convert
For %%A in (%_SourcePath%) Do Echo Converting "%%A"...... &"D:\temp\Tesseract-OCR\tesseract.exe" "%%A" "%_OutputPath%%%~nA"
PAUSE
:End
Set "_SourcePath="
Set "_OutputPath="
Set "_Tesseract="
The problem now is how to include in the .bat file that"-l eng+lat+ita" bit from the cmd-window line.
I got the idea that this is possible from an explanation of the "For" command, which states that "do command" can be followed by "CommandLineOptions" (i.e., "-l eng+lat+ita").
Any help would be much appreciated... 'cause I've been banging my head on this one for hours now...
UPDATE: Found an alternative, but still would like an answer to my question.
I didn't know that "FOR" commands could be run from cmd. So, I pasted the folllowing line in the cmd window:
for %i in (*.tif) do "D:\temp\Tesseract-OCR\tesseract.exe" -l eng+lat+ita "%i" "D:\temp\%~ni"
And, it worked!
As I say, though, how to do this with a .bat file?
#ECHO OFF
SETLOCAL
:Start
#Echo off
ECHO.
ECHO This is a batch file
ECHO.
PAUSE
BREAK=ON
Set "_SourcePath=D:\temp\TIFs\*.tif"
Set "_OutputPath=D:\temp\TIFs"
Set "_Tesseract=D:\temp\Tesseract-OCR\tesseract.exe"
:Convert
For %%A in ("%_SourcePath%") Do Echo Converting "%%A"...... &"%_Tesseract%" -l eng+lat+ita "%%A" "%_OutputPath%\%%~nA"
PAUSE
:End
rem Set "_SourcePath="
rem Set "_OutputPath="
rem Set "_Tesseract="
GOTO :EOF
Since I don't have the tesseract utility, I used another. The above worked for me as I expected with that other utility, so no guarantees with tesseract.
It's normal practice to start a batch with setlocal which makes the clean-up effort unnecessary (hence remmed-out) since an implicit endlocal is executed when the batch terminates, restoring the environment to its initial state.
Assigning values containing quotes is valid but awkward when combining elements. Ditto terminating a value with a backslash. I've converted your code to my preferred syntax. Note that the syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
Will it work in your situation? Over to you to try.

Modify for loop to not use delayedexpansion in batch script

In my efforts to understand the for..do loops syntax and their use of %% variables. I have gone through 2 specific examples/implementations where the one for loop does not use DELAYEDEXPANSION and another where it does use DELAYEDEXPANSION with the ! notation. The 1st for loop appears to be compatible with older OSs like the Windows XP whereas the 2nd for loop example does not.
Specifically, the 1st for loop example is taken from this answer (which is related to this) and the 2nd for loop example is taken from this answer.
Modified code for both examples copied below:
1st for loop
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
set "YY=%dt:~2,2%"
set "YYYY=%dt:~0,4%"
set "MM=%dt:~4,2%"
set "DD=%dt:~6,2%"
set "HH=%dt:~8,2%"
set "Min=%dt:~10,2%"
set "Sec=%dt:~12,2%"
set "datestamp=%YYYY%%MM%%DD%"
set "timestamp=%HH%%Min%%Sec%"
echo datestamp: "%datestamp%"
echo timestamp: "%timestamp%"
2nd for loop
SETLOCAL ENABLEDELAYEDEXPANSION
set "path_of_folder=C:\folderA\folderB"
for /f "skip=5 tokens=1,2,4 delims= " %%a in (
'dir /ad /tc "%path_of_folder%\."') do IF "%%c"=="." (
set "dt=%%a"
set vara=%%a
set varb=%%b
echo !vara!, !varb!
set day=!vara:~0,2!
echo !day!
)
Since I have been reading and seeing issues where delayed expansion (or the ! notation) is not compatible with older OSs (e.g. Windows XP), I would like to see how to write the 2nd loop like the 1st loop; i.e. without the use of DELAYEDEXPANSION.
I explain in detail what aschipfl wrote already absolutely right in his comment.
Both batch files work also on Windows 2000 and Windows XP using also cmd.exe as command processor. The batch files do not work on MS-DOS, Windows 95 and Windows 98 using very limited command.com as command interpreter.
A command can be executed with parameter /? in a command prompt window to get output the help for this command. When in help is written with enabled command extensions it means supported only by cmd.exe on Windows NT based Windows versions and not supported by MS-DOS or Windows 9x using command.com. That means, for example, for /F or if /I or call :Subroutine are not available on Windows 9x, or on Windows NT based Windows with command extensions explicitly disabled. On Windows 9x it is not even possible to use "%~1" or "%~nx1".
The first batch file executes in FOR loop only one command exactly once:
set "dt=%%a"
That command line requires already enabled command extensions. All other commands below are executed after the FOR loop finished. In other words the FOR loop in first batch file does not use a command block to run multiple commands within the FOR loop.
Whenever the Windows command processor detects the beginning of a command block on a command line, it processes the entire command block before executing the command on this command line the first time.
This means for second batch file all variable references using %Variable% are expanded already before the command FOR is executed and then the commands in the command block are executed with the values of the variables as defined above FOR command line. This can be seen by removing #echo off from first line of batch file or change it to #echo ON and run the batch file from within a command prompt window because now it can be seen which command lines respectively entire command blocks defined with ( ... ) are really executed after preprocessing them by the Windows command processor.
So whenever an environment variable is defined or modified within a command block and its value is referenced in same command block it is necessary to use delayed expansion or use workarounds.
One workaround is demonstrated below:
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." (
set "VarA=%%a"
set "VarB=%%b"
call echo %%VarA%%, %%VarB%%
call set "Day=%%VarA:~0,2%%
call echo %%Day%%
)
endlocal
pause
As there is no #echo off at top of this batch code it can be seen on executing the batch file what happens here. Each %% is modified on processing the command block to just %. So executed are the command lines.
call echo %VarA%, %VarB%
call set "Day=%VarA:~0,2%
call echo %Day%
The command CALL is used to process the rest of the line a second time to run the ECHO and the SET commands with environment variable references replaced by their corresponding values without or with string substitution.
The disadvantage of this solution is that CALL is designed primary for calling a batch file from within a batch file. For that reason the command lines above result in searching first in current directory and next in all directories of environment variable PATH for a file with name echo respectively set with a file extension of environment variable PATHEXT. That file searching behavior causes lots of file system accesses, especially on running those command lines in a FOR loop. If there is really found an executable or script file with file name echo or set, the executable respectively the script interpreter of the script file would be executed instead of the internal command of cmd.exe as usually done on using such a command line. So this solution is inefficient and not fail-safe on execution of the batch file.
Another workaround to avoid delayed expansion is using a subroutine:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." call :ProcessCreationDate "%%a" "%%b"
endlocal
pause
exit /B
:ProcessCreationDate
echo %~1, %~2
set "Day=%~1"
set "Day=%Day:~0,2%
echo %Day%
goto :EOF
A subroutine is like another batch file embedded in current batch file.
The command line with exit /B avoids a fall through to the code of the subroutine.
The command line with goto :EOF would not be necessary if the line above is the last line of the batch file. But it is recommended to use it nevertheless in case of more command lines are ever added later below like a second subroutine.
The second batch file is for getting the day on which the specified folder was created. It would be possible to code this batch file without usage of delayed expansion and any workarounds.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /ad /tc "%FolderPath%\." 2^>nul') do if "%%c"=="." set "CreationDate=%%a, %%b" & goto OutputDateAndDay
echo Failed to get creation date of "%FolderPath%"
endlocal
pause
exit /B
:OutputDateAndDay
echo %CreationDate%
set "Day=%CreationDate:~0,2%
echo %Day%
endlocal
pause
Once the line of interest with the creation date of specified folder is found, the creation date/time is assigned to an environment variable and the FOR loop is exited with using command GOTO to continue execution on a label below. For the meaning of operator & see single line with multiple commands using Windows batch file.
This solution is better than all other methods because the FOR loop executes the single command line with the three commands IF, SET and GOTO only once which makes this solution the fastest. And it outputs an error message when it was not possible to determine the creation date of the directory because of the directory does not exist at all.
Of course it would be possible to add a GOTO command also on the other solutions to exit FOR loop once the creation date of the directory was determined and output. The last solution is nevertheless the fastest and in my point of view best one for this task.
BTW: All posted batch file examples were tested on Windows XP and produced the expected output.

How to process 2 FOR loops after each other in batch?

My problem is that two FOR loops are working separately, but don't want to work one after another.
The goal is:
The first loop creates XML files and only when the creation has already been done the second loop starts and counts the size of created XML files and writes it into .txt file.
#echo off
Setlocal EnableDelayedExpansion
for /f %%a in ('dir /b /s C:\Users\NekhayenkoO\test\') do (
echo Verarbeite %%~na
jhove -m PDF-hul -h xml -o C:\Users\NekhayenkoO\outputxml\%%~na.xml %%a
)
for /f %%i in ('dir /b /s C:\Users\NekhayenkoO\outputxml\') do (
echo %%~ni %%~zi >> C:\Users\NekhayenkoO\outputxml\size.txt
)
pause
This question can be answered easily when knowing what jhove is.
So I searched in world wide web for jhove, found very quickly the homepage JHOVE | JSTOR/Harvard Object Validation Environment and downloaded also jhove-1_11.zip from SourceForge project page of JHOVE.
All this was done by me to find out that jhove is a Java application which is executed on Linux and perhaps also on Mac using the shell script jhove and on Windows the batch file jhove.bat for making it easier to use by users.
So Windows command interpreter searches in current directory and next in all directories specified in environment variable PATH for a file matching the file name pattern jhove.* having a file extension listed in environment variable PATHEXT because jhove.bat is specified without file extension and without path in the batch file.
But the execution of a batch file from within a batch file without usage of command CALL results in script execution of current batch file being continued in the other executed batch file without ever returning back to the current batch file.
For that reason Windows command interpreter runs into jhove.bat on first file found in directory C:\Users\NekhayenkoO\test and never comes back.
This behavior can be easily watched by using two simple batch files stored for example in C:\Temp.
Test1.bat:
#echo off
cd /D "%~dp0"
for %%I in (*.bat) do Test2.bat "%%I"
echo %~n0: Leaving %~f0
Test2.bat:
#echo %~n0: Arguments are: %*
#echo %~n0: Leaving %~f0
On running from within a command prompt window C:\Temp\Test1.bat the output is:
Test2: Arguments are: "Test1.bat"
Test2: Leaving C:\Temp\Test2.bat
The processing of Test1.bat was continued on Test2.bat without coming back to Test1.bat.
Now Test1.bat is modified to by inserting command CALL after do.
Test1.bat:
#echo off
cd /D "%~dp0"
for %%I in (*.bat) do call Test2.bat "%%I"
echo Leaving %~f0
The output on running Test1.bat from within command prompt window is now:
Test2: Arguments are: "Test1.bat"
Test2: Leaving C:\Temp\Test2.bat
Test2: Arguments are: "Test2.bat"
Test2: Leaving C:\Temp\Test2.bat
Test1: Leaving C:\Temp\Test1.bat
Batch file Test1.bat calls now batch file Test2.bat and therefore the FOR loop is really executed on all *.bat files found in directory of the two batch files.
Therefore the solution is using command CALL as suggested already by Squashman:
#echo off
setlocal EnableDelayedExpansion
for /f %%a in ('dir /b /s "%USERPROFILE%\test\" 2^>nul') do (
echo Verarbeite %%~na
call jhove.bat -m PDF-hul -h xml -o "%USERPROFILE%\outputxml\%%~na.xml" "%%a"
)
for /f %%i in ('dir /b /s "%USERPROFILE%\outputxml\" 2^>nul') do (
echo %%~ni %%~zi>>"%USERPROFILE%\outputxml\size.txt"
)
pause
endlocal
A reference to environment variable USERPROFILE is used instead of C:\Users\NekhayenkoO.
All file names are enclosed in double quotes in case of any file found in the directory contains a space character or any other special character which requires enclosing in double quotes.
And last 2>nul is added which redirects the error message output to handle STDERR by command DIR on not finding any file to device NUL to suppress it. The redirection operator > must be escaped here with ^ to be interpreted on execution of command DIR and not as wrong placed redirection operator on parsing already the command FOR.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
cd /?
dir /?
echo /?
for /?
And read also the Microsoft article Using command redirection operators.
You need to use the START command with the /WAIT flag when you launch an external application.
I believe it would look something like this:
START /WAIT jhove -m PDF-hul -h xml -o C:\Users\NekhayenkoO\outputxml\%%~na.xml %%a
That should cause the batch file to pause and wait for the external application to finish before proceeding.

Set the value of a variable with the result of a command in a Windows batch file [duplicate]

This question already has answers here:
Assign output of a program to a variable using a MS batch file
(12 answers)
Closed 9 months ago.
When working in a Bash environment, to set the value of a variable as the result of a command, I usually do:
var=$(command -args)
where var is the variable set by the command command -args. I can then access that variable as $var.
A more conventional way to do this which is compatible with almost every Unix shell is:
set var=`command -args`
That said, how can I set the value of a variable with the result of a command in a Windows batch file? I've tried:
set var=command -args
But I find that var is set to command -args rather than the output of the command.
To do what Jesse describes, from a Windows batch file you will need to write:
for /f "delims=" %%a in ('ver') do #set foobar=%%a
But, I instead suggest using Cygwin on your Windows system if you are used to Unix-type scripting.
One needs to be somewhat careful, since the Windows batch command:
for /f "delims=" %%a in ('command') do #set theValue=%%a
does not have the same semantics as the Unix shell statement:
theValue=`command`
Consider the case where the command fails, causing an error.
In the Unix shell version, the assignment to "theValue" still occurs, any previous value being replaced with an empty value.
In the Windows batch version, it's the "for" command which handles the error, and the "do" clause is never reached -- so any previous value of "theValue" will be retained.
To get more Unix-like semantics in Windows batch script, you must ensure that assignment takes place:
set theValue=
for /f "delims=" %%a in ('command') do #set theValue=%%a
Failing to clear the variable's value when converting a Unix script to Windows batch can be a cause of subtle errors.
Here's how I do it when I need a database query's results in my batch file:
sqlplus -S schema/schema#db #query.sql> __query.tmp
set /p result=<__query.tmp
del __query.tmp
The key is in line 2: "set /p" sets the value of "result" to the value of the first line (only) in "__query.tmp" via the "<" redirection operator.
The only way I've seen it done is if you do this:
for /f "delims=" %a in ('ver') do #set foobar=%a
ver is the version command for Windows and on my system it produces:
Microsoft Windows [Version 6.0.6001]
Source
Here are two approaches:
#echo off
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;;set "[[=>"#" 2>&1&set/p "&set "]]==<# & del /q # >nul 2>&1" &::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: --examples
::assigning chcp command output to %code-page% variable
chcp %[[%code-page%]]%
echo 1: %code-page%
::assigning whoami command output to %its-me% variable
whoami %[[%its-me%]]%
echo 2: %its-me%
::::::::::::::::::::::::::::::::::::::::::::::::::
;;set "{{=for /f "tokens=* delims=" %%# in ('" &::
;;set "--=') do #set "" &::
;;set "}}==%%#"" &::
::::::::::::::::::::::::::::::::::::::::::::::::::
:: --examples
::assigning ver output to %win-ver% variable
%{{% ver %--%win-ver%}}%
echo 3: %win-ver%
::assigning hostname output to %my-host% variable
%{{% hostname %--%my-host%}}%
echo 4: %my-host%
the output of the script:
1: Active code page: 65001
2: mypc\user
3: Microsoft Windows [Version 10.0.19042.1110]
4: mypc
Explanation:
1] the parts ending with &:: are end-of-line comment and can be ignored. I've put them only for macro enclosing
2] ;; at the start of the line will be ignored as ; is a standard delimiter for batch script. It is put there as it reminds a comment and to further enhance where the macro definitions are assigned.
3] the technique used is called 'macro' it assigns command to a variable and when the variable is invoked the command is executed.
4] the first macro defined contains two parts:
set "[[=>"#" 2>&1&set/p "
and
set "]]==<# & del /q # >nul 2>&1"
separated by & which allows me to define them on one line. The first takes the output of a command and redirects it ot a file #. And adds set/p in order to start the reading the file with set /p technique .The second macro finishes the set /p reading with <# and then deletes the file. The text between two macros is the name of the variable. Something like set /p myVariable=<#
5] The second macro contains three parts and expanded is just a for /f loop. Probably can be done in a more elegant way.
Set "dateTime="
For /F %%A In ('powershell get-date -format "{yyyyMMdd_HHmm}"') Do Set "dateTime=%%A"
echo %dateTime%
pause
Official Microsoft docs for for command

Resources