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.
So I was tasked with making a batch file that does a few specific things. I've never worked with batch before, and I'm finding it hard to find tutorials on what exactly I need. (I've done basic tutorials)
I'm trying to get the most currently edited file from a directory. The only thing I've came up with (and I've noticed other people said to do) is a for loop of files in the directory sorted by date and then just get the first file and break the loop.
Some problems:
1) My loop never breaks
2) My ECHO %variable% doesn't work at the end.
#echo off
SET count=0
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF count==0 (
SET NewestFile=%%i
SET count=1
)
)
#echo on
ECHO %NewestFile%
When I run this, I get:
C:\>testing.bat
C:\>ECHO
ECHO is on.
I am 100% new to Batch. Maybe I'm doing something that this is really picky about? (Other StackOverflow questions have been solved by people just adding aa space or stuff like that)
Your condition is never met because the string count is never equal to the string 0. You need
if !count!==0 (
set NewestFile=%%i
set count=1
)
But then you also need delayed expansion (at the beginning of your batch file):
setlocal enabledelayedexpansion
The problem here is that you need to tell the batch file that there is a variable. Like foo in Perl won't magically resolve to the contents of the $foo variable count in your batch file isn't equivalent to %count% (the variable contents) or !count! (the same, but with delayed expansion).
Delayed expansion is necessary because the whole for loop is parsed at once. And cmd replaces normal (%foo%) variables with their contents during parsing so that during execution only the values remain. So once execution reaches the if there would be the condition 0==0 because that's what count's value was before the loop. Delayed expansion (using the !foo! syntax) expands the variables immediately prior to execution, so this does not happen.
For more help on delayed expansion you can read help set.
Another way would be to just use absence or presence of the count variable:
SET count=
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF not defined count (
SET NewestFile=%%i
SET count=1
)
)
This works around the problem above because there is no variable to replace during parsing. All we're doing is a run-time check whether the variable count exists.
If you supplied accurate code then you want to get the first line - and this is one way to do that.
#echo off
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO SET "NewestFile=%%i" & goto :done
:done
ECHO %NewestFile%
If you change the dir command to list the files in ascending order instead of descending order, you can use this one-liner which doesn't need any of the common bizarre cmd.exe scripting hacks. It just keeps the last line of output in the NewestFile variable (I guess it might qualify as a cmd.exe scripting hack, but I don't think it qualifies as bizarre):
for /f %%i in ('DIR Y:\ /B /O:D') do set NewestFile=%%i
Below is my script. I am trying to look into folders one level below and pick out only those folders, hence the ~-9 which extracts the last 9 chars from the path. But the set var= does not unset the variable because the output comes back with the same folder name repeated # times. Also batch doesn't allow me to do this extract trick directly on %%i, hence the need for the local variable.
How do I clear this variable so that it takes the new value in the next iteration?
#echo off
for /d %%i in (%1\*) do (
set var=%%i
echo %var:~-9%
set "var="
)
http://judago.webs.com/variablecatches.htm has an explanation for my problem. The magic lines were setlocal enabledelayedexpansion and calling var as echo !var:~-9!. ! vs % ...wow! cmd still amazes me.
You found the source of your problem, as well as the solution - delayed expansion.
But using FOR while delayed expansion is enabled can cause problems if any of the filenames contain the ! character. The expansion of the for variable %%i will be corrupted if the value contains ! and delayed expansion is enabled. This is not a frequent problem, but it happens.
The solution is to toggle delayed expansion on and off within the loop
#echo off
setlocal disableDelayedExpansion
for /d %%i in (%1\*) do (
set var=%%i
setlocal enableDelayedExpansion
echo !var:~-9!
endlocal
)
I'm also wondering what you mean by "I am trying to look into folders one level below and pick out only those folders, hence the ~-9 which extracts the last 9 chars from the path". I suspect your are trying to get the name of the child folder, without the leading path information. If that is so, then using the substring operation is not a good solution because the length of folder names varies.
There is a very simple method to get the name of the folder without the leading path info:
for /d %%i in (%1\*) do echo %%~nxi
How would I achieve this:
for i in *.e; do mv $i ${i%-b*.e}.e; done
in a Windows batch file? (It renames files containing "-b" to the part before "-b". Note that this is not necessarily the end of the string! e.g. "file-b-4.e" will become "file.e")
If you really want to do this in batch, this should work
#echo off
setlocal disableDelayedExpansion
for %%F in (*.e) do (
set "var=%%~F"
setlocal enableDelayedExpansion
set "var=!var:-b=.e:!"
for /f "eol=: delims=:" %%A in ("!var!") do (
endlocal
echo ren "%%F" "%%A"
)
)
Edit
The comment by panda-34 alluded to the fact that the original posted code failed if the file name begins with -b. The code above was fixed by incorporating the extension into the replacement string. (thanks panda-34 for alerting me to the problem)
panda-34 also provided an alternate solution that uses command injection with search and replace. The injected command is the REM statement.
The panda-34 solution works as long as the file name does not contain & or ^ characters, but fails if it does.
Below is a modified version of the command injection technique that should work with all valid Windows file names. There are 2 critical mods, 1) make sure the special chars in the file name are always quoted, and 2) do not pass the value as a CALL argument, otherwise ^ will be doubled to ^^.
#echo off
setlocal disableDelayedExpansion
for %%i in (*-b*.e) do (
set old="%%~ni"
call :ren_b
)
exit /b
:ren_b
set v=%old:-b=.e"&rem "%
echo ren "%old:~1,-1%.e" %v%
exit /b
Final Edit (I hope):
As baruch indicates in his comment, the solutions above remove starting with the 1st occurance, whereas the original bash command removes starting with the last occurance.
Below is a version that should be an exact equivalent of the original bash command.
#echo off
setlocal disableDelayedExpansion
set "search=-b"
for %%A in (*%search%*.e) do (
set "old=%%A"
setlocal enableDelayedExpansion
set "new=\_!old:%search%=\_!"
for %%B in ("!new!") do (
endlocal
set "new=%%~pB"
setlocal enableDelayedExpansion
set "new=!new:~2,-1!.e"
echo ren "!old!" "!new:\_=%search%!"
endlocal
)
)
Simple, really
for %%i in (*-b*.e) do call :ren_b %%~ni
goto :eof
:ren_b
set v=%*
set v="%v:-b=.e" ^& rem %
ren "%*.e" %v%
Here's a variant to keep the name till the last -b occurence
setlocal enabledelayedexpansion
for %%i in (*-b*.e) do (
set v=%%~ni
set v=!v:-b=\!
for %%j in ("\!v!") do (
set v=%%~pj
set v=!v:~1,-1!
set v=!v:\=-b!
ren "%%i" "!v!.e"
)
)
It will fail for names containing ! and starting with -b.
P.S, Didn't see, dbenham already provided the equivalent solution, probably with more provisions for terminal cases of file names.
Forget it, some convenient things cannot be done in NT scripting. What you are asking here is not possible to my knowledge. And I've written and maintained complex NT scripts bigger than 50 KiB, using all kinds of tricks. The book "Windows NT Shell Scripting" points out many of these, for the same and more see Rob van der Woude's scripting pages.
I reckon you could do part of this, but certainly not in a one-liner due to how variable expansion works in NT scripting. For example you could extract the part of the string that you expect to be -b and check whether it is -b, then extract the other parts and rename from the original name to the one that is comprised of only the extracted parts.
But you'll likely need ten to fifteen lines to achieve that. In that light, consider using a different scripting language for the purpose. Especially if this is a modern Windows version.
I realize this is not the desired answer (i.e. that this is possible and a sample), but cmd.exe is very limited compared to Bash, albeit by far not as limited as some opponents of traditional batch scripting are pointing out.
I need to grab the folder name of a currently executing batch file. I have been trying to loop over the current directory using the following syntax (which is wrong at present):
set mydir = %~p0
for /F "delims=\" %i IN (%mydir%) DO #echo %i
Couple of issues in that I cannot seem to pass the 'mydir' variable value in as the search string. It only seems to work if I pass in commands; I have the syntax wrong and cannot work out why.
My thinking was to loop over the folder string with a '\' delimiter but this is causing problems too. If I set a variable on each loop then the last value set will be the current folder name. For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
I need to parse that value out as its name will be part of another folder I am going to create further down in the batch file.
Many thanks if anyone can help. I may be barking up the wrong tree completely so any other approaches would be greatly received also.
After struggling with some of these suggestions, I found an successfully used the following 1 liner (in windows 2008)
for %%a in (!FullPath!) do set LastFolder=%%~nxa
You were pretty close to it :) This should work:
#echo OFF
set mydir="%~p0"
SET mydir=%mydir:\=;%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
#echo %LAST%
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
For some reason the for command doesn't like '\' as a delimiter, so I converted all '\' to ';' first (SET mydir=%mydir:\=;%)
I found this old thread when I was looking to find the last segment of the current directory.
The previous writers answers lead me to the following:
FOR /D %%I IN ("%CD%") DO SET _LAST_SEGMENT_=%%~nxI
ECHO Last segment = "%_LAST_SEGMENT_%"
As previous have explained, don't forget to put quotes around any paths create with %_LAST_SEGMENT_% (just as I did with %CD% in my example).
Hope this helps someone...
This question's a little old, but I've looked for a solution more than once so here's a completely new take on it that I've just put together.
The trick is that we take the desired path, back up one level to create a folder mask for substitution and then replace the folder mask with nothing.
To test it, simple copy and paste into a command script (.cmd) in any directory, then run it. It will spit out only the deepest directory you're currently in.
Notes:
Replace %~dp0 with whatever path you like (as it is, it will return the deepest folder the batch file is run from. This is not the same as %cd%.)
When specifying the 'pathtofind' variable ensure there are no quotes e.g. c:\some path and not "c:\some path".
The original idea for folder masking is mine
Spaces in the path are no problem
Folder depth is not a problem
It was made possible by the genius of this batch scripting tip http://www.dostips.com/DtCodeBatchFiles.php#Batch.FindAndReplace
Hope this helps someone else.
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
call set "path3=%%path1:%path2%\=%%"
echo %path3%
pause>nul
3 lines of script gets the result...
Found 2 additional ways to accomplish the goal, and unlike the other answers to this question, it requires no batch "functions", no delayed expansion, and also does not have the limitation that Tim Peel's answer has with directory deepness :
#echo off
SET CDIR=%~p0
SET CDIR=%CDIR:~1,-1%
SET CDIR=%CDIR:\=,%
SET CDIR=%CDIR: =#%
FOR %%a IN (%CDIR%) DO SET "CNAME=%%a"
ECHO Current directory path: %CDIR%
SET CNAME=%CNAME:#= %
ECHO Current directory name: %CNAME%
pause
REVISION: after my new revsion, here is an example output:
Current directory path: Documents#and#Settings,username,.sqldeveloper,tmp,my_folder,MY.again
Current directory name: MY.again
Press any key to continue . . .
This means that the script doesn't handle '#' or ',' in a folder name but can be adjusted to do so.
ADDENDUM: After asking someone in the dostips forum, found an even easier way to do it:
#echo off
SET "CDIR=%~dp0"
:: for loop requires removing trailing backslash from %~dp0 output
SET "CDIR=%CDIR:~0,-1%"
FOR %%i IN ("%CDIR%") DO SET "PARENTFOLDERNAME=%%~nxi"
ECHO Parent folder: %PARENTFOLDERNAME%
ECHO Full path: %~dp0
pause>nul
To return to the original poster's issue:
For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
The simple solution for that is:
for /D %%I in ("C:\Folder1\Folder2\Folder3\Archive.bat\..") do echo parentdir=%%~nxI
will give 'Folder3'. The file/path does not need to exist. Of course, .... for the parent's parent dir, or ...... for the one above that (and so on) work too.
Slight alteration for if any of the folders have spaces in their names - replace space to ':' before and after operation:
set mydir="%~p0"
set mydir=%mydir:\=;%
set mydir=%mydir: =:%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
set LAST=%LAST::= %
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
Sheesh guys, what a mess. This is pretty easy, and it's faster to do this in memory without CD.
This gets the last two directories of a path. Modify it as required to get the last tokens of any line. My original code I based this on has more complexity for my own purposes.
Fyi, this probably doesn't allow paths with exclamation marks since I'm using enabledelayedexpansion, but that could be fixed.
It also won't work on a plain drive root. This could be averted in a number of ways. Check what the input path ends with, or a counter, or modifying the token and check behaviour, etc.
#echo off&setlocal enableextensions,enabledelayedexpansion
call :l_truncpath "C:\Windows\temp"
----------
:l_truncpath
set "_pathtail=%~1"
:l_truncpathloop
for /f "delims=\ tokens=1*" %%x in ("!_pathtail!") do (
if "%%y"=="" (
set "_result=!_path!\!_pathtail!"
echo:!_result!
exit/b
)
set "_path=%%x"
set "_pathtail=%%y"
)
goto l_truncpathloop
I modified answer given by #Jonathan, since it did not work for me in a batch file, but this below does work, and also supports folders with spaces in it.:
for %%a in ("%CD%") do set LastFolder=%%~nxa
echo %LastFolder%
This takes the current directory and echoes the last, deepest folder, as in below example, if the folder is this:
C:\Users\SuperPDX\OneDrive\Desktop Environment\
The batch code echoes this: Desktop Environment
In batch files in the FOR command you'll need to prepend %whatever with an extra % (e.g. %%whatever).
'echo %~p0' will print the currently directory of the batch file.
This is what we had in the end (little bit more crude and can only go so deep :)
#echo off
for /f "tokens=1-10 delims=\" %%A in ('echo %~p0') do (
if NOT .%%A==. set new=%%A
if NOT .%%B==. set new=%%B
if NOT .%%C==. set new=%%C
if NOT .%%D==. set new=%%D
if NOT .%%E==. set new=%%E
if NOT .%%F==. set new=%%F
if NOT .%%G==. set new=%%G
if NOT .%%H==. set new=%%H
if NOT .%%I==. set new=%%I
if NOT .%%J==. set new=%%J
)
#echo %new%
I don't know if it's the version of windows I'm on (win2k3), but the FOR loop isn't giving me anything useful for trying to iterate through a single string.
According to my observation (and the FOR /? info) you get one iteration for each line of input to FOR, and there is no way to change this to iterate within a line. You can break into multiple tokens for a given line, but it is only one invocation of the FOR loop body.
I do think the CALL :LABEL approach in these answers does a great job. Something I didn't know until looking at this was that ";" and "," are both recognized as argument separators. So once you replace backslashes with semicolons, you can call your label and iterate through with SHIFT.
So working off of what is posted by others here, I have the below solution. Instead of grabbing the last folder name, I actually wanted to find everything up until some known directory name.. this is what is implemented below.
#echo off
if "%1"=="" goto :USAGE
set FULLPATH=%~f1
set STOPDIR=%2
set PATHROOT=
:: Replace backslashes with semicolons
set FULLPATH=%FULLPATH:\=;%
:: Iterate through path (the semicolons cause each dir name to be a new argument)
call :LOOP %FULLPATH%
goto :EOF
:LOOP
::Exit loop if reached the end of the path, or the stop dir
if "%1"=="" (goto :EOF)
if "%1"=="%STOPDIR%" (goto :EOF)
::If this is the first segment of the path, set value directly. Else append.
if not defined PATHROOT (set PATHROOT=%1) else (set PATHROOT=%PATHROOT%\%1)
::shift the arguments - the next path segment becomes %i
SHIFT
goto :LOOP
:USAGE
echo Usage:
echo %~0 ^<full path to parse^> ^<dir name to stop at^>
echo E.g. for a command:
echo %~0 c:\root1\child1\child2 child2
echo The value of c:\root1\child1 would be assigned to env variable PATHROOT
Unfortunatelly, this is working great only when put on some depth but have problems with being on the very top of the mountain... Putting this program into "C:\Windows" e.g. will result with... "C:\Windows", not expected "Windows". Still great job, and still damage can be repaired. My approach:
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
set path4=%~dp1
call set "path3=%%path1:%path2%\=%%"
call set "path5=%%path3:%path4%*\=%%"
echo %path5%
pause>nul
And it's working just fine for me now, thanks for the idea, I was looking for something like that for some time.