This question already has answers here:
'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell
(12 answers)
Closed 3 years ago.
I just checked stackoverflow that seemed to be very helpful and worked fine on Windows XP. But using Windows 7 it does not work for some obscure reason.
The PATH variable looks like this
C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\QuickTime\QTSystem\
It obviously contains \ as well as semicolons I use to split in a batch that contains this FOR-loop:
FOR /F "delims=;" %%A IN ("%PATH%") DO (
echo %%A
)
Executing does not cause any error but it provides just one (the first) token
C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common
I had no idea why FOR terminates and played around with several variations that have been suggested on the net but none did the job.
Any help will be highly appreciated.
Christian
You could do it this way.
for %%A in ("%path:;=";"%") do (
echo %%~A
)
(Source)
The problem with the way you have it is that, using the for /F switch, %%A only specifies the first token. You would have to do for /f "tokens=1-9 delims=;" %%A in ("%PATH%") and read in %%A through %%I that way.
Combining things learned on this and various other stackoverflow pages, the OP can be extended to:
How to ensure the PATH variable has unique values?
Which can be done this way, using array variables:
REM usage: call :make_path_unique VARNAME "%VARNAME%"
REM 1: splits VARNAME on ';' and builds an array of unique values (case insensitive)
REM 2: glues the array back into a single variable
REM 3: set the VARNAME to this newly unique-ified collection.
REM
REM From: various StackOverflow pages:
REM http://stackoverflow.com/questions/5471556/pretty-print-windows-path-variable-how-to-split-on-in-cmd-shell
REM http://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal
REM http://stackoverflow.com/questions/14879105/windows-path-variable-how-to-split-on-in-cmd-shell-again
REM
:make_path_unique
setlocal EnableDelayedExpansion
set VNAME=%~1
set VPATH=%~2
set I=0
for %%A in ("%VPATH:;=";"%") do (
set FOUND=NO
for /L %%B in (1,1,!I!) do (
if /I "%%~A"=="!L[%%B]!" set FOUND=YES
)
if NO==!FOUND! (
set /A I+=1
set L[!I!]=%%~A
)
)
set FINAL=!L[1]!
for /L %%n in (2,1,!I!) do (
set FINAL=!FINAL!;!L[%%n]!
set L[%%n]=
)
for %%P in ("!FINAL!") do (
endlocal
set %VNAME%=%%~P
)
exit /b 0
Summary of steps:
for loop splitting PATH at ';' and properly managing quotes
for loop looking at all previously stored paths
only extend the array if this is a new path to be added
glue the array pack together, clearing the array variable as we go
replace the path and clear the temporary variables
return from function with no errors.
invoked, of course, with:
set PATH=%PATH%;%MY_PATH_ADDITIONS%
call :make_path_unique PATH "%PATH%"
Related
I have a batch file that (among other things) turns a list like this:
'foo_ph1-1.tif', 'foo_ph2-1', 'foo_ph2-2'
into a list like this, in a local variable called INVNOS:
'fooph1', 'fooph2', 'fooph2'
I want to remove the duplicates from the second list. I've been trying to do this when I create the list, from the answers to this question, to no avail.
Here's how I make the list.
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%f in ("*.tif") do #echo %%~nf>>list.lst
set FNAMES=
set INVNOS=
for /f %%i in ('type list.lst') do (
set FNAMES=!FNAMES!'%%i.jpg',
for /f "tokens=1 delims=-" %%a in ("%%i") do (
set BEFORE_HYPHEN=%%a
set INVNOS=!INVNOS!'!BEFORE_HYPHEN:_=!',
)
)
set "FNAMES=%FNAMES:~0,-2%"
set "INVNOS=%INVNOS:~0,-2%"
echo %INVNOS%
endlocal
Solutions with findstr won't work because I need to initialize INVNOS with an empty string, and I get stuck with the difference between % and '!', and slicing, inside the for loop.
I know this is easy in Python, however I'd like to do it with what's native (Windows 10/Windows Server), so CMD or Powershell.
Any suggestions?
Just to sketch the bigger picture, INVNOS (inventory numbers) is derived from directories full of tif's, so we can check whether or not they exist in some sql database.
I would approach the problem differently:
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%f in (*.tif) do (
for /f "delims=-" %%g in ("%%~nf") do set "~%%g=."
)
for /f "delims=~=" %%a in ('set ~') do set "INVOS='%%a', !INVOS!"
set "INVOS=%INVOS:~0,-2%
echo %INVOS:_=%
The trick is to define variables for each filename (the variableNAMES contain the filenames. A variable can only exist once, so per definition, there are no duplicates)
With another for loop extract the names from the defined variables and join them. The underscores can be deleted in one go instead of removing them from each substring.
When needed, you can delete the variables with for /f "delims==" %%a in ('set ~') do set "%%a=", but they are destroyed anyway when the script ends. (same line when you want to be sure, no variable starting with ~ is defined by accident before you set them)
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
:: The values assigned to these variables suit my system and test environment
SET "sourcedir=u:\your files"
SET "tempfile=%temp%\tempfile.txt"
:: remove variables starting :
FOR /F "delims==" %%a In ('set : 2^>Nul') DO SET "%%a="
(for %%f in ("%sourcedir%\*.tif") do echo %%~nf)>"%tempfile%"
set "FNAMES="
set "INVNOS="
for /f "usebackqdelims=" %%i in ("%tempfile%") do (
set FNAMES=!FNAMES!'%%i.jpg',
for /f "tokens=1 delims=-" %%a in ("%%i") do (
set "BEFORE_HYPHEN=%%a"
SET "before_hyphen=!BEFORE_HYPHEN:_=!"
IF NOT DEFINED :!BEFORE_HYPHEN! set "INVNOS=!INVNOS!'!BEFORE_HYPHEN:_=!', "&SET ":!BEFORE_HYPHEN!=Y"
)
)
set "FNAMES=%FNAMES:~0,-2%"
set "INVNOS=%INVNOS:~0,-2%"
echo %INVNOS%
IF DEFINED tempfile DEL "%tempfile%"
GOTO :EOF
You would need to change the value assigned to sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
%tempfile% is used temporarily and is a filename of your choosing.
The usebackq option is only required because I chose to add quotes around the source filename.
it is standard practice on SO to use the syntax set "var=value" for string
assignments as this ensures stray trailing spaces on the line are ignored.
Evil trailing space on OP's code set INVNOS... within the for ... %%a loop.
Given OP's original filename list, foo_ph1-1.tif foo_ph2_1 foo_ph2-2, the processing should produce fooph1 fooph21 fooph2, not fooph1 fooph2 fooph2 as claimed.
My testing included foo_ph2-2.tif
The code is essentially the same, but first clearing any environment variables that start :, on the Irish principle.
The temporary file nominated is recreated avoiding the (unfulfilled) requirement to first delete it.
BEFORE_HYPHEN is explicitly expunged of underscores before the if not defined test is applied. I selected : because : can't be part of a filename. Once the name is applied to the invnos list, the :!BEFORE_HYPHEN! variable is established to prevent further accumulation of repeat BEFORE_HYPHEN values into invnos.
If you wanted to step up to PowerShell, something like this could be done in a .bat file script. Of course, It would be easier to write and maintain if it were all written in PowerShell.
=== doit.bat
#ECHO OFF
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"(Get-ChildItem -File -Filter '*.tif' |" ^
"ForEach-Object { '''' + $($_.Name.Split('-')[0].Replace('_','')) + '''' } |" ^
"Sort-Object -Unique) -join ','"') DO (
SET "INVNOS=%%~A"
)
ECHO INVNOS is set to %INVNOS%
EXIT /B
Get-ChildItem produces a list of all the *.tif files in the directory. Split() does what "delims=-" does in a FOR loop. The [0] subscript chooses everything up to the first '-' character in the file name. Replace will remove the '_' characters. Sort-Object removed duplicates to produce a unique list. The -join converts the list of names to a single, comma delimited string. The resulting string is stored into the INVNOS variable.
Do you really want APOSTROPHE characters around each name in the list?
This question already has answers here:
Make an environment variable survive ENDLOCAL
(8 answers)
Closed 3 years ago.
I'm trying to use a FOR loop to read the lines in a text file, but I also need to keep track of some variables and evaluate them. The easiest way to do that is by enabling DelyaedExpansion. Actually, it seems to be the ONLY way as everything else I've tried in relation to variables fails miserably if I don't use it. Unfortunately, this means that if any of the lines of text in the file contain exclamation points, they will be stripped out.
I thought I had found a solution by reading a line of text and putting it into a variable, THEN enabling DelayedExpansion, doing the variable operations, and finally using ENDLOCAL & SET VARIABLE=%VARIABLE% to preserve the value. Unfortunately that doesn't seem to work if the ENDLOCAL statement is inside a loop.
For example;
echo off
for /F "delims=" %%F in (test.txt) do (
set Line=%%F
setlocal enabledelayedexpansion
set /a Count=Count+1
echo !Count! - !Line!
endlocal & set Count=%Count%
)
echo Total: %Count%
Each time the loop repeats, the value of "Count" is reset to zero.
If I move the SETLOCAL command before the FOR command, it will strip any "!" from the text, which is unacceptable.
Please note: The example above is only a small part of a much larger script that does many things with the variables inside the loop. I have boiled the problem down to the bare minimum to make it easy to understand. I need to preserve "!" in text read from a file while also being able to perform multiple variable operations within each loop.
So I either need a way to read text from a file, one line at a time, with DeleyedExpansion enabled AND preserve any "!" in the text, or preserve the value of variables that are defined within the SETLOCAL/ENDLOCAL commands within a loop.
With Help from dbenham and his answer here, There is a Solution that exists for this Scenario.
The key, as Dave has Shown, is in Setting the variables PRIOR to using SetlocalEnableDelayedExpansion so that ! is preserved.
#echo off
Set "count=0"
For /F "delims=" %%F in (test.txt) do (
Call :LineParse "%%~F"
)
REM The Below Loop demonstrates Preservation of the Values
Setlocal EnableDelayedExpansion
For /L %%a in (1,1,!count!) DO (
ECHO(!line[%%a]!
)
Endlocal
pause
exit
:LineParse
Set /a count+=1
Set "Line[%count%]=%~1"
Setlocal EnableDelayedExpansion
ECHO(!Line[%count%]!
ECHO(Total: !count!
(
ENDLOCAL
)
GOTO :EOF
There are still a few characters that will not be parsed as desired with this Method, noted in test.txt:
test.txt
Safe Characters: ! > * & ` ' . ] [ ~ # # : , ; ~ } { ) ( / \ ? > < = - _ + $ |
problem Characters: ^ "" %%
problem examples:
line disappears " from single doublequote
but not "" from escaped doublequote
%% will not display unless escaped. % unescaped Percent Symbols will attempt to expand %
caret doubles ^ ^^ ^^^
Don't need to complicate...
Just replace:
echo/ to set /p
setlocal enabledelayedexpansion to cmd /v /c
#echo off
for /F "delims=" %%F in ('type test.txt')do set /a "Count+=1+0" && (
(echo/ & cmd /v/s/c "set/p "'=!Count! - %%F"<nul")>>".\newfile.txt")
cmd /v /c echo/ Total: !Count! && call set "Count="<nul && goto :EOF
In Windows batch, I have a for loop like so:
for /l %%a in (0,1,337) do (
for /F "tokens=*" %%b IN ("tile%%a.jpg") DO set size=%%~zb
if !size! GTR 0 (
echo Size is greater than 0
) ELSE (
)
)
I know this code doesn't make much sense right now, but I'm going to develop it further. I just want to know how to subtract 1 from %%a in the ELSE statement. Basically I want to be able to "redo" a loop number when the IF isn't true, if that makes sense. Thanks.
You can't modify the value of a loop variable. You can only modify the value of an environment variable.
But why using for /L %%a in (0,1,337) do at all?
Better would be for example:
#echo off
for %%A in (tile*.jpg) do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo File size of %%A is greater than 0.
)
)
This loop processes simply all tile*.jpg in current directory.
But this loop can't be used if files with 0 bytes are deleted in current directory. Processing the list of tile*.jpg files in current directory and change the files list in the same loop is no good idea because simply not working. The solution is using command DIR to get first the list of all files matching the file name pattern and next process the output of DIR line by line using FOR.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%A in ('dir /A-D /B /OS tile*.jpg 2^>nul') do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo First file with more than 0 bytes is: %%A
goto ExitLoop
)
)
:ExitLoop
endlocal
The command DIR is executed to output the list of files matching the pattern tile*.jpg with ignoring directories which by chance would be matched also by this wildcard pattern because of option /A-D in bare format (only file name) because option /B in order sorted by file size because of option /OS from smallest to largest file.
2^>nul redirects the error message output by command DIR to handle STDERR on not finding any file matching the wildcard pattern to device NUL to suppress this error message. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and interpreted as redirection operator on execution of DIR command line by FOR.
The loop is immediately exited once a file with more than 0 bytes is found as all further files have surely also more than 0 bytes.
One more loop can be used after label ExitLoop which should be renamed to something more suitable in this case for example to renumber the remaining files using command REN when first loop deletes files with 0 bytes.
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.
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
setlocal /?
See also the Microsoft article Using command redirection operators for an explanation of 2>nul.
You cannot modify the loop variable %%a. Only the loop itself can modify it.
If you want to calculate a new value you can do:
set /A NEW_VALUE=%%a-1
echo %NEW_VALUE% (prove that NewValue is now 1 smaller than %%a)
You cannot modify a for variable reference like %%a, but you can store its value into a standard environment variable (like index) and modify this. For this to work you need to enable and use delayed expansion, because the variable is modified and read within the same block of code, namely the loop body, so read it like !index!; using normal expansion like %index% returned the value present before the loop has even started:
#echo off
setlocal EnableDelayedExpansion
for /L %%a in (0,1,337) do (
set /A "index=%%a-1"
echo %%a - 1 = !index!
)
endlocal
A nice alternative that avoids need of delayed expansion is to use an embedded for /F loop that gets the output of the subtraction and iterates once only per iteration of the surrounding for /L loop, like this:
#echo off
for /L %%a in (0,1,337) do (
for /F %%b in ('set /A "%%a-1"') do (
echo %%a - 1 = %%b
)
)
This works because the for /F loop executes the set /A command in cmd context, in which it returns the resulting value -- in contrast to the aforementioned approach, where set /A is executed in batch-file context, in which it does not output anything.
This question already has answers here:
Convert absolute path to relative path in batch file
(3 answers)
Closed 7 years ago.
I have full path ( using "%%~fl" or %%~l )
E:\Documents\Windows-Bash\CheckModels\SB3\models\TEST3\Cerus\Modbridge\Core\g-221.mdl
My base path ( according to %~dp0 ) is:
E:\Documents\Windows-Bash\CheckModels\SB3\
How can I extract:
models/TEST3/Cerus/Modbridge/Core/g-221.mdl
To search for it inside a text file.
My batch script is like follows:
echo off
set DataBase=E:\Documents\Windows-Bash\CheckModels\SB3\DB\trackassembly_init.lua
set AddModel=models_present.txt
set MisModel=models_missing.txt
set BasePath=%~dp0
set Model
dir /a-d /b /s *.mdl > %AddModel%
for /F "tokens=*" %%l in ('type "%AddModel%"') do (
set Model=%%~l
:: ?[1] Process the %Model% so it becomes "models/TEST3/Cerus/Modbridge/Core/g-221.mdl"
:: ?[2] IF (not found %Model% in the contents of %DataBase% ) THEN
:: ?[2] echo Model > MisModel ( Append missing file )
:: ?[2] END
)
del %AddModel%
echo End
timeout 25
How can I replace the pseudo-code in ?[1] and ?[2] with the real thing ?
I can easily convert the string using C/C++, but I do not need additional files. Is there a way the whole thing to be converted to C/C++ ?
#ECHO Off
SETLOCAL
SET "basepath=u:\sourcedir\t w o"
PUSHD "%basepath%"
for /F "tokens=*" %%l in ('dir /a-d /b /s *.mdl ') do (
set "Model=%%~l"
call set "relpath=%%model:*%basepath%\=%%"
call set "relpath=%%relpath:\=/%%"
CALL echo %%relpath%% FOR %%l
)
popd
GOTO :EOF
Note positioning of quotes to ensure value assigned does not include stray trailing spaces in the sourcecode (difficult to find if they exist)
basepath is the relative root for the directory scan. Change to suit yourself - I used a directory that suits my system.
The call executes a set command with the single-% variable substituted.
set "var1=%var2:string1*=string2%"
sets var1 to the value of var2, with the characters up to and including string1 replaced by string2
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