I'm looking to use a variable which gets defined inside a for loop and use it to call a predefined variable.
For example: To start the program "Slack" I need to call the variable %Slack%.
The batch file will take your input, so If I input "Slack", the variable !filename[%%i]! will be assigned "Slack". Once that is done, I wish to call %Slack% using !filename[%%i]!. As per logic: %!filename[%%i]!%
Using %!filename[%%i]!% will make the batch file crash and close.
Setting set file=!filename[%%i]! and running it with %file% will return nothing.
Any smart trick?
#echo off
setlocal EnableDelayedExpansion
:: Predefined variables
for /f "delims== tokens=1,2" %%G in (D:\programs.txt) do set %%G=%%H
set /P "filenames=Enter programs to install: "
:: Puts input into array
set n=0
for %%a in (%filenames%) do (
set /A n+=1
set "filename[!n!]=%%~a"
)
:: Run variables imported from programs.txt
for /L %%i in (1,1,%n%) do (
:: This is where I want !filename[%%i]! to be called as %!filename[%%i]!%
)
pause
endlocal
program.txt example
Slack=C:\Users\username\AppData\Local\slack\slack.exe
Text=C:\textfile.txt
Program=C:\program.bat & C:\program_license.txt
I don't like to use the CALL hack described in the comments because 1), it is relatively slow, and 2) it causes an extra round of parsing that can require additional escaping (not an issue here). Even worse, it will corrupt quoted ^ - "^" becomes "^^", and there is no escape sequence that can prevent that.
My solution is to add an additional FOR loop so that the expansion can be staged.
for /L %%i in (1,1,%n%) do for %%A in ("!filename[%%i]!") do echo !%%~A!
This is certainly more verbose than the CALL hack, but it is a good code pattern to learn because it is more universally applicable.
set var=C:\Users\user\Desktop\bla\bla.exe
set var=%var:*\%
echo %var%
this returns Users\user\Desktop\bla\bla.exe - is there any way to make it focus on the last \ and not the first one so that it would just return bla.exe? bear in mind that this will be used on multiple files and folders so i won't always know how many sub-folders there are.
#ECHO OFF
SETLOCAL
set "var=C:\Users\user\Desktop\blah blah\bla.exe"
FOR %%a IN ("%var%") DO (
SET "filename=%%~nxa"
FOR %%b in ("%%~dpa.") DO SET "lastleaf=%%~nxb"
)
ECHO filename is "%filename%"
ECHO lastleaf is "%lastleaf%"
GOTO :EOF
Normally, the next question is about how to obtain the last leaf of the directory-tree. No subroutines required...
Note positioning of quotes to minimise problems with separators. ALso minor directory name-change to exhibit differences.
The following snippet shows one way to do it.
#setlocal enableextensions enabledelayedexpansion
#echo off
:main
set var=C:\Users\user\Desktop\blah blah\yada yada.exe
call :basename result "%var%"
echo %result%
endlocal
goto :eof
:basename
set %1=%~nx2
goto :eof
It basically calls a function basename (named after the UNIX utility), passing the full name and the variable you want to assign the base name to, and you need to make sure you quote it properly lest filenames containing spaces will cause you problems.
The full set of variable modifiers can be seen in the call /? help output.
Alternatively, you can use the same basename functionality in a one-liner for statement:
for /f "delims=" %%I in ("%var%") do set result=%%~nxI
This allows you to get the base name without having to call a function. I tend to prefer the function myself since it's more readable but you could probably alleviate that by just including a comment:
rem Get base name of var into result:
rem eg: var = C:\Users\user\Desktop\blah blah\yada yada.exe
rem result = yada yada.exe
for /f "delims=" %%I in ("%var%") do set result=%%~nxI
I would like to create a batch-file that reads the first 10 file names in a spefic directory, and then sets the paths to 10 different variables. For a simple example, the path would be c:\test and inside there, there would be lots of files named file1.tif, file2.tif, etc. I would like to set the variable filepath1 equal to the path of the 1st file, which would be c:\test\file1.tif, and so on for the first 10 files. Here is the code:
#echo off
cd C:\TEST
setlocal ENABLEDELAYEDEXPANSION
FOR /f "delims=|" %%a IN ('dir /b') DO (
CALL SET /a x = !x! +1
if !x! == 1 (
CALL SET /a filepath!x!="C:\TEST\%%a"
)
)
echo %filepath1%
echo %filepath2%
pause
goto EOF
When I run the program, it seems to perform the FOR loop fine, but for filepath1 it displays just 0 and it does not display anything for filepath2. I belive the problem is in the if !X! == 1 and setting the filepath!x!. If I change anything to do with the !x!, it breaks the loop. What can I do to set the variables correctly and limit the loop to perform action on only 10 files?
I'm not quite sure if I got your question correct, because your orginal code contains some things that wouldn't make sense then (or are at least not necessary) :-)
the "delims=|" option you used is not doing anything in your case
when /a option is used for arithmetic (see help set) and is wrong when used with your set filepath!x!... line.
Anyway, the following should work:
setlocal ENABLEDELAYEDEXPANSION
cd C:\Test
FOR /f %%a IN ('dir /b') DO (
SET /a x = !x! +1
SET filepath!x!="C:\TEST\%%a"
if !x! equ 10 goto done
)
:done
rem filepath1 to filepath10 are defined now, given there were up to 10 matching
rem files in the first place.
echo %filepath1%
echo %filepath2%
pause
The code above does not do any error checking and does not verify that the files in question are actually called file1.tif to file10.tif. From your question (and sample code) it is not quite clear if that is really necessary in your case. You might want to clarify that, so that the above code can be improved regarding it.
I have this code
#Echo Off
SetLocal EnableDelayedExpansion
SET mydir=D:\
SET DirCount=2
SET T=
For /F %%i In (qqq.txt) Do (
set fg=%%i
FOR /L %%G IN (2, 1, %DirCount%) DO (call :subroutine "%%i")
)
:subroutine
Set T=!T!../
start /wait %mydir%program.exe %T%%fg%
echo %t%%fg% >>%cd%see.log
qqq.txt has rows
1
2
3
In result i get:
../1
../../2
../../../3
../../../../3
But I need like this:
../1
../../1
../2
../../2
../3
../../3
Please Help what I'm doing wrong?
Just a little additional requried
I need what first ocur in
set "T=!T!../"
will be without dots
set "T=!T!/"
result must be
/1
../1
../../1
/2
../2
../../2
how and where to add counter and make visible for each loop somthing like
echo Now looping: %%i row of %countrow(in qqq.txt), left %countrow-%%I
echo Now looping: Dir Nr%%G !T!%%i
Sorry for stupid questions but batch coding is difficult to me.
You have 3 problems:
1) You don't exit your program after the main loop so it falls through into the subroutine
2) Your FOR /L IN() clause is wrong, the first number should be 1, not 2
3) You need to reset T back to undefined within the outer loop.
EDIT
I believe you may have an additional problem. When redirecting output you prefix your file name with %cd%. I don't think that is giving the result you want. If your current directory is C:\test, then >>%cd%see.log will result in output sent to file named testsee.log in the root C:\ directory. If you wanted the output in the current directory then it should be >>%cd%\see.log, but the path info is not really needed since the default is to use the current directory. So really all you would need is >>see.log. Since I don't know your intent, I have left the redirection as originally written. End Edit
#echo Off
SetLocal EnableDelayedExpansion
SET mydir=D:\
SET DirCount=2
For /F %%i In (qqq.txt) Do (
SET T=
set fg=%%i
FOR /L %%G IN (1, 1, %DirCount%) DO (call :subroutine "%%i")
)
exit /b
:subroutine
Set T=!T!../
start /wait %mydir%program.exe %T%%fg%
echo %t%%fg% >>%cd%see.log
There is no need to call a subroutine in your case, especially since you have already enabled delayed expansion. The following gives the same result with less code, and it is faster because it avoids the relatively slow CALL statement. (performance is probably not an issue in this small example, but if using loops with many iterations it can make a huge difference)
#echo Off
SetLocal EnableDelayedExpansion
SET mydir=D:\
SET DirCount=2
For /F %%i In (qqq.txt) Do (
SET "T="
FOR /L %%G IN (1, 1, %DirCount%) DO (
set "T=!T!../"
start /wait %mydir%program.exe !T!%%i
echo !T!%%i >>%cd%see.log
)
)
Partial answer to additional questions
You can use FOR /F to capture the result of FIND /C to get the number of lines in "qqq.txt". This should be done before you enter the main loop:
for /f %%N in ('find /c /v "" ^<qqq.txt') do set "rowCount=%%N"
You can use SET /A to do basic math, thus enabling you to keep track of the number of remaining lines
First initialize the remaining count before entering the main loop:
set "remainingLines=%rowCount%"
Then within the main loop use SET /A to decrement the value.
set /a remainingLines-=1
A have a text file that contains the results of a dir
dir "%local%" /b /a:d /s >> FolderList.txt
But I want to iterate in a For loop going from the last to the first line.
Since I believe this cannot be done in the For command, how can I generate a new file containing the same lines but in a inverse order?
You can't using the For command. But you can reverse the order of the dir listing that created the text file, using dir "%local%" /o-n /b /a:d /s >> FolderList.txt; the - means "reversed".
I like the general strategy of both of Aacini's original solutions, but as written they have problems (some trivial, some significant)
Original Aacini solution 1 using temp file with SORT:
Corrupts lines containing exclamation point (!)
Strips leading colon(s) (:) from each line
temp file creation using >> not as efficient as >
Uses default SORT maximum line length of 4096 bytes
Line count unnecessarily capped at 1 million
Doesn't actually provide the asked for solution (an actual file output)
Leaves behind the temporary file
Modified solution 1
Here is a version that fixes the problems. The only practical limitation is a maximum line length of 8180 bytes (characters). I'm not sure how high FINDSTR can count, but this solution will handle up to 999 billion lines. (I agree with Aacini, no one would ever want to wait for such a large file to finish using a batch solution) The line limit can easily be adjusted.
#echo off
setlocal DisableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set tempfile="%temp%\revfile%random%.txt"
(
for /f "delims=" %%a in ('findstr /n "^" %file%') do (
set "ln=%%a"
setlocal EnableDelayedExpansion
for /f "delims=:" %%n in ("!ln!") do set "prefix=000000000000%%n"
echo !prefix:~-12!!ln:*:=!
endlocal
)
)>%tempfile%
(
for /f "delims=" %%a in ('sort /rec 8192 /r %tempfile%') do (
set "ln=%%a"
setlocal EnableDelayedExpansion
echo(!ln:~12!
endlocal
)
)>%revfile%
del %tempfile%
Aacini modified solution 1
Aacini dramatically improved the robustness and performance with a modified solution 1 using SET /P and multiple TEMP files. The SET /P solution eliminates the need for a looped SETLOCAL/ENDLOCAL toggle, but it does have a few limitations.
Lines must be terminated by <LF><CR> (normal for Windows, but Unix style is sometimes encountered in Windows world).
Lines must be <= 1024 characters
Control characters at end of line will be stripped.
Modified solution 1 take 2
If any of the above limitations are a problem, here is an adaptation of my 1st solution that uses multiple temp files. Like Aacinis modified solution, it performs linearly with file size. It is about 40% slower than Aacinis modified version.
#echo off
setlocal DisableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set "tempfile=%temp%\revfile%random%.txt"
findstr /n "^" %file% >"%tempfile%.1"
(
for /f "usebackq delims=" %%a in ("%tempfile%.1") do (
set "ln=%%a"
setlocal EnableDelayedExpansion
for /f "delims=:" %%n in ("!ln!") do set "prefix=000000000000%%n"
echo !prefix:~-12!!ln:*:=!
endlocal
)
)>"%tempfile%.2"
sort /rec 8192 /r "%tempfile%.2" >"%tempfile%.3"
(
for /f "usebackq delims=" %%a in ("%tempfile%.3") do (
set "ln=%%a"
setlocal EnableDelayedExpansion
echo(!ln:~12!
endlocal
)
)>%revfile%
del "%tempfile%*"
Original Aacini solution 2 using environment variables:
Corrupts lines containing exclamation point (!)
Strips blank lines
Doesn't actually provide the asked for solution (an actual file output)
Modified solution 2
Here is a version that fixes the problems. The only known limitations are
A maximum line length between 8181 and 8190, depending on line number
A maximum file size slightly under 64MB.
This was my favorite solution because the file output can probably be eliminated by processing the file in the variables directly, thus completely avoiding the creation of any temporary file. Edit But based on info provided by Aacini, I learned it has severe performance problems as the environment grows. The problem is worse than Aacini realized - Even a simple SET command suffers dramatically with large environment sizes. I've posted a question regarding this phenomenon at DosTips. http://www.dostips.com/forum/viewtopic.php?f=3&t=2597 (I originally posted on SO, but apparently the question is too open ended for this site)
#echo off
setlocal disableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set num=0
for /f "delims=" %%a in ('findstr /n "^" %file%') do (
set /a "num+=1"
set "ln=%%a"
setlocal enableDelayedExpansion
for %%n in (!num!) do for /f "delims=" %%b in (""!ln:*:^=!"") do endlocal&set "ln%%n=%%~b"'
)
setlocal enableDelayedExpansion
(
for /l %%n in (!num! -1 1) do echo(!ln%%n!
)>%revfile%
There are two relatively easy ways to sort a file in reversed order. The first one is a direct method over file contents: add line numbers to all lines, sort the file in reversed order, eliminate line numbers:
#echo off
setlocal EnableDelayedExpansion
rem Insert line numbers in all lines
for /F "tokens=1* delims=:" %%a in ('findstr /n ^^ %1') do (
set /A lineNo=1000000+%%a
echo !lineNo!:%%b>> tempfile.txt
)
rem Sort the file and show the result
for /F "tokens=1* delims=:" %%a in ('sort /r tempfile.txt') do (
echo Line %%a is %%b
)
The other method consist in load the file lines in a Batch array, that may be processed in any way you wish:
#echo off
setlocal EnableDelayedExpansion
rem Load file lines in a Batch array
set lineNo=0
for /F "delims=" %%a in (%1) do (
set /A lineNo+=1
set "line[!lineNo!]=%%a"
)
rem Process array elements in reversed order:
for /L %%i in (%lineNo%,-1,1) do (
echo Line %%i is !line[%%i]!
)
This last method works only if the size of the file is below 64 MB, because this is the limit for Batch variables.
Both methods can be modified to correctly process special characters (> < |).
HOWEVER
If you want to delete all the tree contents of a folder in bottom-up order, the "right" way to do that is via a recursive subroutine...
EDIT Answer to dbenham
As I wrote in my answer, the two methods I proposed can be modified to correctly process special characters and blank lines. In my answer I showed a general method to "change the order of lines" in reversed order paying no special attention on create an output file because the OP said in his own answer that "The objective was to reorder a list of folders to prevent problems while deleting them in sequence", so I thought that was enough to show him how to process the folders in reversed order. I also assumed that the list of folders:
Have not exclamation points (!).
Have not leading colons (:).
Folder names are shorter than 4096 bytes.
Have less than 1000000 lines.
Have not blank lines.
I even thought (and still think) that the method the OP want to use to delete a list of folders is not adequate, and I mentioned this point under a big HOWEVER in my answer proposing to use a recursive subroutine instead.
However it seems that dbenham thought that the original question was something similar to "What is the most efficient method to sort a large file in reversed order?" and criticize my methods because they lack of such features. For this reason, I should reply in terms of this new question (efficient method), right?
In first place, it's funny to me that dbenham critizice my methods because "Doesn't actually provide the asked for solution (an actual file output)", but in his own Modified solution 2 he wrote that "This is my favorite solution because the file output can probably be eliminated by processing the file in the variables directly, thus completely avoiding the creation of any temporary file". ???
The two methods proposed by dbenham have a serious problem in terms of efficiency that was already discussed in this question: the pair of setlocal EnableDelayedExpansion and endlocal commands are executed with every line of the file. If the file is large (i.e. 200 000 lines and about 8 MB, as in the previous mentioned question) the environment will be copied to a new memory area and then deleted, and this will be repeated for 200000 times! Of course, this task is time-consuming. This problem becomes worse in dbenham's Modified solution 2: as the processing of lines go on, the environment grow as it store the file contents at that point. In the last lines of the file an environment almost equal to the size of the whole file will be copied to a new memory area for every remaining line of the file. Of course, this is the worst way to achieve this process in terms of efficiency!
There is another way to process empty lines and special characters that don't require the setlocal EnableDelayedExpansion - endlocal pair. For details on this method and further discussion on efficient ways to process large files, see the previously mentioned question.
The following Batch files are my modified versions on "How to sort a large file in reversed order in an efficient way".
Modified solution 1: using temp file with SORT
#echo off
setlocal EnableDelayedExpansion
set revfile="%~1.rev"
set tempfile=%temp%\revfile%random%
rem Insert line numbers in all lines
findstr /n ^^ %1 > "%tempfile%1.txt"
find /c ":" < "%tempfile%1.txt" > "%tempfile%2.txt"
set /P lines=< "%tempfile%2.txt"
call :JustifyLineNumbers < "%tempfile%1.txt" > "%tempfile%2.txt"
del "%tempfile%1.txt"
rem Sort the file in reversed order
sort /rec 8192 /r "%tempfile%2.txt" /o "%tempfile%3.txt"
del "%tempfile%2.txt"
rem Remove line numbers
call :RemoveLineNumbers < "%tempfile%3.txt" > %revfile%
del "%tempfile%3.txt"
goto :EOF
:JustifyLineNumbers
for /L %%i in (1,1,%lines%) do (
set /A lineNo=1000000000+%%i
set /P line=
echo !lineNo!!line:*:=!
)
exit /B
:RemoveLineNumbers
for /L %%i in (1,1,%lines%) do (
set /P line=
echo !line:~10!
)
exit /B
This solution still have a limit of "only" 1147483647 lines (the maximum 32-bits signed positive integer minus the initial seed). Although this limit can be easily increased in the way suggested by dbenham, that modification imply a slower execution speed. The conclusion is: if you really want to reverse-sort a very large file don't use a Batch file, but a more efficient programming language (like C).
Modified solution 2: using a Batch variable array
#echo off
setlocal EnableDelayedExpansion
set revfile="%~1.rev"
set tempfile=%temp%\revfile%random%
rem Load file lines in a Batch array
findstr /n ^^ %1 > "%tempfile%1.txt"
find /c ":" < "%tempfile%1.txt" > "%tempfile%2.txt"
set /P lines=< "%tempfile%2.txt"
del "%tempfile%2.txt"
call :CreateArray < "%tempfile%1.txt"
del "%tempfile%1.txt"
rem Process array elements in reversed order:
(for /L %%i in (%lines%,-1,1) do echo=!ln%%i!) > %revfile%
goto :EOF
:CreateArray
for /L %%i in (1,1,%lines%) do (
set /P line=
set ln%%i=!line:*:=!
)
exit /B
EDIT A possible solution for large environment problem.
I devised an idea that may solve, at least in part, the performance problems of SET command caused by a very large environment. Let's suppose that the internal operation of SET VAR=VALUE command follow these steps:
When a new variable is defined with a value that exceed the current environment size, the environment is copied to a new area if the area beyond it is not available.
The new area is just large enough to receive the new variable. No additional space is reserved.
The important one: When a large variable is deleted, the remaining free space is NOT released. The environment memory block is never shrunk.
If previous steps are true, then the performance problems may decrease if we first reserve the desired environment space via large (8 KB) variables with the same name of the working variables. For example, to reserve 1024 KB we define 128 large variables; I suppose that the time required to define these 128 variables will be less than the time required to fill the same 1024 KB with shorter variables.
When the process is running, the definition of the first 128 working variables will take the time necessary to delete an 8 KB variable and define a shorter one, but for the variable 129 on the process must be faster because it just define a new variable in an already available space. To aid to this process, the variables must have names that place them at the end of the environment as dbenham indicated.
:ReserveEnvSpace sizeInKB
rem Define the first large variable (reserving 6 bytes for variable name)
rem (this method may be done in larger chunks until achieve the fastest one)
set z1=X
for /L %%i in (1,1,8184) do set z1=!z1!X
rem Define the rest of large variables
set /A lastVar=%1 / 8
for /L %%i in (2,1,%lastVar%) do set z%%i=!z1!
exit /B
You may use MEM /P command to check the size and placement of the environment memory block. In old MS-DOS (command.com) days the environment was placed after command.com, but if a resident program was placed after the environment, then it can't grow anymore. For this reason, the /E:nnnnn switch was provided in command.com to reserve a certain size in bytes for the environment.
I have no time to check this method for the rest of the day, but here it is for you!
The objective was to reorder a list of folders to prevent problems while deleting them in sequence.
I came up with the following algorithm. I accept suggestions to make it more efficient or better.
#ECHO off
setLocal EnableDelayedExpansion
:: File that contains a list of folders
set file_from=%~1
:: Destination file, that will contain the sorted list
if "%2"=="" (
set replace=1
set file_to=_%file_from%
) else (
set file_to=%~2
)
:: Create empty destination file
if exist "%file_to%" del "%file_to%"
copy NUL "%file_to%"
:: Temporary file
if exist ".\~Remaining.txt" del ".\~Remaining.txt"
copy "%file_from%" .\~Remaining.txt
:: Sort the order of folders
:while
set untouched=1
For /f "tokens=* delims=" %%a in (.\~Remaining.txt) Do (
:: check if line was already added
FindSTR /X /C:%%a "%file_to%"
if errorlevel 1 (
set untouched=0
:: check if folder contains sub-folders to be added
FindSTR /B /C:%%a\ .\~Remaining.txt
if errorlevel 1 (
:: remove current line from "~Remaining.txt"
FindSTR /V /B /E /C:%%a .\~Remaining.txt> .\~Remaining_new.txt
move .\~Remaining_new.txt .\~Remaining.txt
:: add current line to destination file
>> "%file_to%" ECHO %%a
goto while
)
)
)
if untouched LSS 1 (
goto while
)
if exist .\~Remaining.txt del .\~Remaining.txt
if defined replace (
ECHO REPLACE!
:: destination was not provided, so replace
if exist "%file_from%" del "%file_from%"
move "%file_to%" "%file_from%"
)
This code will reverse a text file, but with a few limitations. Blank lines are omitted and lines containing special charaters cause it to fail: & < > |
#Echo Off
If "%1"=="" Goto Syntax
If "%2"=="" Goto Syntax
If Not Exist %1 (
Echo File not found: %1
Exit /B 2
)
SetLocal EnableDelayedExpansion
Set SOF=~StartOfFile~
Set InFile=%~snx1~in
Set OutFile=%2
Set TempFile=%~snx1~temp
If Exist %OutFile% Del %OutFile%
If Exist %TempFile% Del %TempFile%
Copy %1 %InFile% >nul
:Loop
Set "Line=%SOF%"
For /F "tokens=*" %%a In (%InFile%) Do (
If Not "!Line!"=="%SOF%" Echo !Line!>>%TempFile%
Set "Line=%%a"
)
Echo %Line%>>%OutFile%
Del %InFile%
If Not Exist %TempFile% (
EndLocal
Exit /B 0
)
Rename %TempFile% %InFile%
Goto Loop
:Syntax
Echo Usage:
Echo %~n0 input-file output-file
Echo.
Exit /B 1