In both cases the directory contains three files named test1.txt, test2.txt, test3.txt
Can someone explain why this works:
echo off
set CP=
for %%f in (*.txt) do (
call :concat %%f
)
echo %CP%
:concat
set CP=%CP%;%1
output:
C:\test>test
C:\test>echo off
;test1.txt;test2.txt;test3.txt
C:\test>
But this does not:
echo off
set CP=
for %%f in (*.txt) do (
set CP=set CP=%CP%;%%f
)
echo %CP%
output:
C:\test>test
C:\test>echo off
;test3.txt
C:\test>
It has to do with Delayed Expansion.
For example, this will work just like your first example:
echo off
SETLOCAL EnableDelayedExpansion
set CP=
for %%f in (*.txt) do (
set CP=!CP!;%%f
)
echo %CP%
ENDLOCAL
When Delayed Expansion is enabled then variables surrounded with ! are evaluated on each iteration instead of only the first time when the loop is parsed (which is how variables surrounded with % are parsed).
Your first example works because the processing is done in a CALL statement which passes control to another segment of the batch file which is technically outside the loop so it is parsed individually each time it is executed.
Related
In a complex batch file I want to read in files with paths, among other things, to read them into a variable one after the other separated by spaces.
This works with the following code so far quite well - but only if the path does not contain an exclamation mark.
Even using the setlocal command (enabledelayedexpansion / disabledelayedexpansion) I did not succeed in processing exclamation marks.
Does anyone here have a clever idea to the problem?
The following example batch creates a text file in the current directory and then reads it in a for /F loop.
At the end all three paths from the text file should be in the variable %Output%. But with the exclamation mark.
#echo off
setlocal enabledelayedexpansion
echo This is an example^^! > "textfile.txt"
echo This is a second example^^! >> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle >> "textfile.txt"
for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
set "Record=%%a"
set "Output=!Output!!Record! - "
)
)
echo %Output%
echo !Output!
endlocal
The Output is like this:
This is an example - This is a second example - And this line have an exclamation mark in the middle
But should be like this:
This is an example! - This is a second example! - And this line have an ! exclamation mark in the middle
It is advisable not using delayed variable expansion on processing files and directories, lines in a text file, strings not defined by the batch file itself, or output captured from the execution of a program or a command line. If it is for some reasons necessary to make use of delayed variable expansion inside a FOR loop, there should be first assigned the file/directory name, the line, or the string to process to an environment variable while delayed expansion is disabled and then enable delayed expansion temporary inside the FOR loop.
Here is a batch file demo which can be simply run from within a command prompt window or by double clicking on the batch file. It creates several files for demonstration in the directory for temporary files, but deletes them all before exiting.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
echo This is an example!> "%TEMP%\TextFile.tmp"
echo This is a second example!>> "%TEMP%\TextFile.tmp"
echo And this line has an exclamation mark ! in the middle.>> "%TEMP%\TextFile.tmp"
set "Output="
(for /F usebackq^ delims^=^ eol^= %%I in ("%TEMP%\TextFile.tmp") do set "Line=%%I" & call :ConcatenateLines) & goto ContinueDemo
:ConcatenateLines
set "Output=%Output% - %Line%" & goto :EOF
:ContinueDemo
cls
echo/
echo All lines concatenated are:
echo/
echo %Output:~3%
set "Output="
del "%TEMP%\TextFile.tmp"
echo File with name ".Linux hidden file!">"%TEMP%\.Linux hidden file!"
echo File with name "A simple test!">"%TEMP%\A simple test!"
echo File with name " 100%% Development & 'Test' (!).tmp">"%TEMP%\ 100%% Development & 'Test(!)'.tmp"
echo/
echo Files with ! are:
echo/
for /F "eol=| tokens=* delims=" %%I in ('dir "%TEMP%\*!*" /A-D /B /ON 2^>nul') do (
set "NameFile=%%I"
set "FileName=%%~nI"
set "FileExtension=%%~xI"
set "FullName=%TEMP%\%%I"
setlocal EnableDelayedExpansion
if defined FileName (
if defined FileExtension (
echo File with ext. !FileExtension:~1!: !NameFile!
) else (
echo Extensionless file: !NameFile!
)
) else echo Extensionless file: !NameFile!
del "!FullName!"
endlocal
)
endlocal
echo/
#setlocal EnableExtensions EnableDelayedExpansion & for /F "tokens=1,2" %%G in ("!CMDCMDLINE!") do #endlocal & if /I "%%~nG" == "cmd" if /I "%%~H" == "/c" set /P "=Press any key to exit the demo . . . "<nul & pause >nul
The output of this batch file is:
All lines concatenated are:
This is an example! - This is a second example! - And this line has an exclamation mark ! in the middle.
Files with ! are:
File with ext. tmp: 100% Development & 'Test(!)'.tmp
Extensionless file: .Linux hidden file!
Extensionless file: A simple test!
The text file example with concatenating lines makes use of a subroutine called from within the FOR loop processing the lines in the text file. The syntax used here is for maximum performance by getting the subroutine as near as possible to the FOR command line. That is important if the FOR loop has to process hundreds or even thousands of items.
The example processing file names enables and disables delayed expansion inside the FOR loop after having assigned all parts of the currently processed file to environment variables. It could be useful to reduce the list of environment variables before processing thousands of files for a better performance on using this method.
Another method is shown in Magoo´s answer using the command CALL to get a command line with referenced environment variables (re)defined inside the loop parsed a second time. I used that method also in the past quite often, but don't that anymore as it is not fail-safe and not efficient. call set results in searching by cmd.exe in current directory and next in all directories of environment variable PATH for a file with name set and a file extension of environment variable PATHEXT. So it results in lots of file system accesses in the background on each iteration of the FOR loop and if there is by chance a file set.exe, set.bat, set.cmd, etc. found by cmd.exe somewhere, the batch file does not work anymore as expected because of running the executable or calling the batch file instead of the (re)definition of the environment variable.
The following answers written by me could be also helpful:
How to read and print contents of text file line by line?
It explains in full details how to process all lines of a text file.
How to pass environment variables as parameters by reference to another batch file?
It explains in full details what the commands SETLOCAL and ENDLOCAL do.
How to pass a command that may contain special characters (such as % or !) inside a variable to a for /f loop?
This is an example of a batch file designed to process video files with any valid file name on any Windows computer very efficient, safe and secure with full explanation.
Well, the main trick is to enable delayed expansion only when it is actually needed and to disable it otherwise. Since you are accumulating multiple strings in a single variable inside of a loop, it becomes a bit more difficult, because you should have delayed expansion disabled during expansion of for meta-variables (like %%a), but enabled when joining the string, leading to setlocal and endlocal statements inside of the loop. The major purpose of these commands is environment localisation, hence any variable changes become lost past endlocal, so a method of tansfering the value beyond endlocal is required, which is incorporated in the following code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem /* At this point delayed expansion is disabled, so there is no need to
rem escape exclamation marks; moreover a redirected block prevents
rem superfluous file close/reopen operations, and there is no more
rem trailing space written to the file (as in your original approach): */
> "textfile.txt" (
echo This is an example!
echo This is a second example!
echo And this line have an ! exclamation mark in the middle
)
rem // Let us initialise the output variable:
set "Output= - "
rem // Using `usebackq` only makes sense when you want to quote a file path:
for /F "usebackq tokens=* delims=" %%a in ("textfile.txt") do (
rem // Remember that delayed expansion is still disabled at this point:
set "Record=%%a"
rem // For concatenation we need delayed expansion to be enabled:
setlocal EnableDelayedExpansion
set "Output=!Output!!Record! - "
rem /* We need to terminate the environment localisation of `setlocal`
rem inside of the loop, but we would lose any changes in `Output`;
rem therefore let us (mis-)use `for /F`, which is iterated once: */
for /F "delims=" %%b in ("!Output!") do endlocal & set "Output=%%b"
rem /* An often used method to transfer a variable beyond `endlocal` is
rem the line `endlocal & set "Output=%Output%`, but this only works
rem outside of a parenthesised block because of percent expansion. */
)
rem /* Echo out text with delayed expansion enabled is the only safe way;
rem surrounding separators ` - ` are going to be removed; since `Output`
rem was initialised with something non-empty, we do not even need to skip
rem sub-string expansion for the problematic case of an empty string: */
setlocal EnableDelayedExpansion
echo(!Output:~3,-3!
endlocal
endlocal
exit /B
Pew. I finally got it to work.
It works via a workaround using a second text file.
Not pretty, not performant, but it works and is sufficient for my purposes.
#Magoo, thanks for your post.
This is my solution:
#echo off
setlocal enableextensions enabledelayedexpansion
echo This is an example^^!> "textfile.txt"
echo This is a second example^^!>> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle>> "textfile.txt"
echo.
echo Content of the textfile:
type "textfile.txt"
set output=
del "textfile2.txt" 1> nul 2>&1
setlocal disabledelayedexpansion
for /f "usebackq tokens=* delims=" %%a IN ("textfile.txt") do (
rem Write each line without a newline character into a new text file
echo|set /p "dummy=%%a, ">>"textfile2.txt"
)
endlocal
rem Loading the content of the new text file into the variable
set /p output=<"textfile2.txt"
del "textfile2.txt" 1> nul 2>&1
echo.
echo --------------------------------------------
echo Content of the variable:
set out
endlocal
The output looks like this:
Content of the textfile:
This is an example!
This is a second example!
And this line have an ! exclamation mark in the middle
--------------------------------------------
Content of the variable:
output=This is an example!, This is a second example!, And this line have an ! exclamation mark in the middle,
It's delayedexpansion mode that appears to raise this problem.
#ECHO OFF
setlocal enabledelayedexpansion
echo This is an example^^^! > "textfile.txt"
echo This is a second example^^^! >> "textfile.txt"
echo And this line have an ^^^! exclamation mark in the middle >> "textfile.txt"
TYPE "textfile.txt"
SETLOCAL disabledelayedexpansion
for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
set "Record=%%a"
CALL set "Output2=%%Output2%%%%record%% - "
CALL set "Output=%%Output%%%%a - "
SET out
)
)
endlocal&SET "output=%output%"
echo %Output%
echo !Output!
SET out
I've no doubt that with delayedexpansion off, there would be the same problem with %. Just special characters, I suppose.
Note that with endlocal&SET "output=%output%", the set is executed in delayedexpansion mode.
I used the following code, but set Content is blank in my case. Please help. Thanks.
set content=
for /f "delims=" %%i in (fileA.txt) do set content=%%i
for /f "delims=" %%i in (FileA.txt) do set content=%content% %%i
ECHO %content%> result.txt
FileA.txt
test
A
Testing
B
Expected Output:
test A
Testing B
You need a single for command to process all lines and this simple logic: if it is the first line read, store it; else show the stored first line and the second one AND delete the first line, so the same logic be used in all line pairs:
#echo off
setlocal EnableDelayedExpansion
set "firstLine="
(for /F "delims=" %%a in (FileA.txt) do (
if not defined firstLine (
set "firstLine=%%a"
) else (
echo !firstLine! %%a
set "firstLine="
)
)) > result.txt
Your two for work independently (the second starts when the first is finished).
Your first loop gets the last line of the file and then the second adds every line of the textfile to the variable (there is a limit for variable length and you will soon reach it with this method).
The empty variable at the end is due to lack of using delayed expansion.
Work with a single for and an alternating flag instead:
#echo off
setlocal enabledelayedexpansion
set flag=0
(for /f "delims=" %%i in (FileA.txt) do (
if !flag!==0 (
<nul set /p ".=%%i "
) else (
echo %%i
)
set /a "flag=(flag+1) %% 2"
))>result.txt
Note: due to batch/cmd limitations, this may have some problems (line length, special characters
We need '#echo off' statement to not to print code on every execution of the program and only echo statements, 'rem' is to mention the line is a comment. 'SETLOCAL EnableExtensions EnableDelayedExpansion' is need to enable ! statements to resolve the variables.
#echo off
rem this for loop reads the file FileA.txt line by line by specifying delims= (nothing)
rem then checks the condition if the line is even line or not, if odd then adding it to myVar variable
rem if even then printing both earlier odd with the current even line to the result.txt file.
set myVar=
set nummod2=0
set /a i=0
rem creating an empty file on everytime the program runs
copy /y nul result.txt
SETLOCAL EnableExtensions EnableDelayedExpansion
for /f "delims=" %%a in (FileA.txt) do (
set /a i=i+1
set /a nummod2=i%%2
if !nummod2!==0 (
echo !myVar! %%a
) else (
set myVar=%%a
)
) >> result.txt;
echo 'Done with program execution. Result saved to result.txt in the same folder of this batchfile'
rem pause
I want to loop through all .h files in the current directory and call a batch function for the corresponding .cpp file. Actually I had it working already, but when I tried to use local variables I get some strange output.
SETLOCAL
for /R %%f in (*.h) do (
SET header=%f%
ECHO header=%header%
SET source=%%~df%%~pf%%~nf.cpp
ECHO source=%source%
)
I get this output:
SETLOCAL
C:\WorkingDirectory>(
SET header=
ECHO header=
SET source=C:\WorkingDirectory\SomeFile.cpp
ECHO source=
)
header=
source=
Why is %%~df%%~pf%%~nf.cpp correctly expanded, but ECHO %source% prints nothing? How can I correctly SET header=%f%?
1) The variable you are accessing is not accessed with %f% but %%f
2) As #npocmaka already mentioned as a comment DelayedExpansion is needed as in batch every block of closed parenthesis is parsed at once when using the usual %. To get rid of that problem, add setlocal EnableDelayedExpansion to your script after #echo off and change %header% to !header!. The same goes for %source%, but not so for the loop variable %%f!
SETLOCAL EnableDelayedExpansion
for %%f in (*.h) do (
SET header=%%~ff
ECHO header=!header!
SET source=%%~df%%~pf%%~nf.cpp
ECHO source=!source!
)
I have a very weird error occurring in my Batch script where a For loop is only looping once when it should loop many more times (at least 10 times).
Can you tell me why my For loop is only looping once and how I can fix it?
#ECHO off
CLS
SETLOCAL
SET macroFolder=_PLACE_4DM_FILES_HERE
REM the following for loop only loops once when it should be looping more
REM because theres over 10 *.4dm files in the folder??
for /r ./%macroFolder% %%i in ("*.4dm") do SET "file=%%i" (
echo %file%
REM Note I need to store %%i in a variable so I can edit it later
REM And placing %%i in a variable within the for loop results in
REM the var being empty for some reason. For eg
SET file=%%i
ECHO file is %file%
REM Prints "file is "
)
ECHO.
PAUSE
ENDLOCAL
You have (at least) two problems. The first is that you seem to be mixing the bracketed and non-bracketed form of the for statement:
for /r ./%macroFolder% %%i in ("*.4dm") do SET "file=%%i" (
blah blah
)
In that case, the non-bracketed bit is executed per-loop-iteration but the bracketed bit executes after loop exit. You can see this with:
for /r ./%macroFolder% %%i in ("*.4dm") do echo 1 (
echo 2
)
which outputs (in my case where there are three 4dm files):
1 (
1 (
1 (
2
Your second problem is that %% variable expansion is done before the command executed and the for command is a multi-line one, from the for to the closing ) bracket. Any variables you need expanded, that have been set within the loop, need to be done with delayed expansion.
Start with this:
#SETLOCAL enableextensions enabledelayedexpansion
#ECHO off
CLS
SET macroFolder=4dmfiles
for /r ./%macroFolder% %%i in ("*.4dm") do (
SET file=%%i
ECHO file is !file!
)
ECHO.
PAUSE
ENDLOCAL
and you'll see (for my three files):
file is C:\Users\Pax\Documents\4dmfiles\1.4dm
file is C:\Users\Pax\Documents\4dmfiles\2.4dm
file is C:\Users\Pax\Documents\4dmfiles\3.4dm
I have as command-line parameters to my batch script a list of filenames and a folder. For each filename, I need to print all subfolders of the folder where the file is found (the path of that file). The subfolder names should be sorted in descending order of the file sizes (the file can have various sizes in different subfolders).
I have done this so far, but it doesn't work:
::verify if the first parameter is the directory
#echo off
REM check the numbers of parameters
if "%2"=="" goto err1
REM check: is first parameter a directory?
if NOT EXIST %1\NUL goto err2
set d=%1
shift
REM iterate the rest of the parameters
for %%i in %dir do (
find %dir /name %i > temp
if EXIST du /b temp | cut /f 1 goto err3
myvar=TYPE temp
echo "file " %i "is in: "
for %%j in %myvar do
echo %j
echo after sort
du /b %myvar | sort /nr
)
:err1
echo Two parameters are necessary
goto end
:err2
echo First parameter must be a directory.
goto end
:err3
echo file does not exist.
goto end
:end
I don't feel guilty answering this homework question now that the semester is long past. Print folders and files recursively using Windows Batch is a closed duplicate question that discusses the assignment.
My initial solution is fairly straight forward. There are a few tricks to make sure it properly handles paths with special characters in them, but nothing too fancy. The only other trick is left padding the file size with spaces so that SORT works properly.
Just as in the original question, the 1st parameter should be a folder path (.\ works just fine), and subsequent arguments represent file names (wildcards are OK).
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
set "root="
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
echo(
echo %%~nxF
echo --------------------------------------------
(
#echo off
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo !size! !mypath!
endlocal
)
) >%tempfile%
sort /r %tempfile%
)
)
if exist %tempfile% del %tempfile%
if defined root popd
I had hoped to avoid creation of a temporary file by replacing the redirect and subsequent sort with a pipe directly to sort. But this does not work. (see my related question: Why does delayed expansion fail when inside a piped block of code?)
My first solution works well, except there is the potential for duplicate output depending on what input is provided. I decided I would write a version that weeds out duplicate file reports.
The basic premise was simple - save all output to one temp file with the file name added to the front of the sorted strings. Then I need to loop through the results and only print information when the file and/or the path changes.
The last loop is the tricky part, because file names can contain special characters like ! ^ & and % that can cause problems depending on what type of expansion is used. I need to set and compare variables within a loop, which usually requires delayed expansion. But delayed expansion causes problems with FOR variable expansion when ! is found. I can avoid delayed expansion by calling outside the loop, but then the FOR variables become unavailable. I can pass the variables as arguments to a CALLed routine without delayed expansion, but then I run into problems with % ^ and &. I can play games with SETLOCAL/ENDLOCAL, but then I need to worry about passing values across the ENDLOCAL barrier, which requires a fairly complex escape process. The problem becomes a big vicious circle.
One other self imposed constraint is I don't want to enclose the file and path output in quotes, so that means I must use delayed expansion, FOR variables, or escaped values.
I found an interesting solution that exploits an odd feature of FOR variables.
Normally the scope of FOR variables is strictly within the loop. If you CALL outside the loop, then the FOR variable values are no longer available. But if you then issue a FOR statement in the called procedure - the caller FOR variables become visible again! Problem solved!
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
if exist %tempfile% del %tempfile%
set "root="
(
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
set "file=%%~nxF"
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo(!file!/!size!/!mypath!
endlocal
)
)
)
)>%tempfile%
set "file="
set "mypath="
for /f "tokens=1-3 eol=/ delims=/" %%A in ('sort /r %tempfile%') do call :proc
if exist %tempfile% del %tempfile%
if defined root popd
exit /b
:proc
for %%Z in (1) do (
if "%file%" neq "%%A" (
set "file=%%A"
set "mypath="
echo(
echo %%A
echo --------------------------------------------
)
)
for %%Z in (1) do (
if "%mypath%" neq "%%C" (
set "mypath=%%C"
echo %%B %%C
)
)
exit /b