Batch: for loop on files issue with symbol in name - for-loop

Why does this for loop not work for files with "!" in the filename? And how can I make it recognize files with "!" or other possible symbols that may not work.
#Echo off
SETLOCAL EnableExtensions EnableDelayedExpansion
set my_dir=C:\Test
set my_ext=txt
cd /d !my_dir!
for %%F in ("*.!my_ext!") do (
for /F "tokens=1,* delims=|" %%K in ('
forfiles /M "%%~F" /C "cmd /C echo #path^|#ext"
') do (
echo "%%~K": %%L
set list=!list!%%~K;
)
)
I get a returned message like this, with the ! missing from the output.
ERROR: Files of type "C:\Test\My file name has an explanation point here.txt" not found.

Here's an example of something which may work for you:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "my_dir=C:\Test"
Set "my_ext=txt"
CD /D "%my_dir%" 2> NUL || GoTo :EOF
Set "list="
For %%G In ("*.%my_ext%") Do (Echo "%%~fG"^|%%~xG
If Not Defined list (Set "list=%%~fG") Else (
For /F "Tokens=1*Delims==" %%H In ('Set list'
) Do Set "list=%%I;%%~fG"))
SetLocal EnableDelayedExpansion
Echo(!list!
EndLocal
Pause
If you wanted each of your filepaths to be doublequoted, then a couple of small changes are all you need:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "my_dir=C:\Test"
Set "my_ext=txt"
CD /D "%my_dir%" 2> NUL || GoTo :EOF
Set "list="
For %%G In ("*.%my_ext%") Do (Echo "%%~fG"^|%%~xG
If Not Defined list (Set "list="%%~fG"") Else (
For /F "Tokens=1*Delims==" %%H In ('Set list'
) Do Set "list=%%I;"%%~fG""))
SetLocal EnableDelayedExpansion
Echo(!list!
EndLocal
Pause
It is important to note, especially because you're using full paths for each of them, that there is a limit to the size of a user defined environment variable of 32767 characters. This means that depending upon the number of matching files in %my_dir%, you could exceed that maximum. In both examples, you can obviously remove the Echo "%%~fG"^|%%~xG part, if you didn't really require it.

Related

Set An Unknown Number of Variables That areParsed From Each Line of a Text File

Apologies for my poorly worded question and my scatterbrain workings. Essentially I want to set an unknown number of variables that are parsed from each line of a text file.
I have written batch file to create symbolic links for network shares to a C:\Volumes folder.
#echo off
echo:
set /p dest=ENTER FOLDER PATH:
set dest="%dest%"
net use %dest%
if not exist "C:\Volumes" MD "C:\Volumes"
for %%i in (%dest%) do (set "fold=%%~ni")
mklink /d "c:\VOLUMES\%fold%" "%dest%"
pause
What I want to try is the same theory but have the script point at a text file mounts.txt with a list of folder paths and for a for loop to cycle through the list make a symbolic link for each path in the list. I have toyed with counters and cannot get it working correctly. I don't think I am going about it the right way at all.
Contents of mounts.txt
\\10.19.10.238\Masters\Removed bin\Work here
\\10.19.10.241\Scanning\WIP\to process
This does not work:
#echo off
setlocal enableDelayedExpansion
set i=1
:add
Set /a "i+=1"
for /F "tokens=*" %%A in (mounts.txt) do (set dest%i%=%%A)
if exist %dest%%i% goto:add
echo %dest%
echo %dest%%i%
echo !dest!
echo !dest!%i%
pause
Nor this:
#echo off
setlocal enableDelayedExpansion
set i=0
For /F "Tokens=1* Delims=] EOL=" %%A In ('Find /N /V ""^<"mounts.txt"') Do (
set /a i=i+1
set "dest!i!=%%B"
)
For /l %%a in (1,1,4) do echo _dest%%a is !dest%%a!
For /l %%a in (1,1,4) do set dest%%a=!dest%%a!
echo !dest!
pause
I did get something like this to work to an extent but cannot figure out how to use the dest[1], dest[2] as variables in other processes further down in the script.
#echo off
setlocal enabledelayedexpansion
set counter=0
for /f "tokens=*" %%a In (mounts.txt) do (
set /a counter+=1
set "dest[!counter!]=%%a"
)
set dest[
And the list could be added to with many more. If %dest%n variables can be set, the use the same theory to set different %fold% variables based on each %dest%n then maybe the links can be set using the same process as the original script.
Any help is appreciated. Thank you.
If your question actually describes what you want to do, you don't need to set variables. Just use the lines from the text file with a for /f loop:
for /f "delims=" %%i in (mount.txt) do mklink /d "C:\VOLUMES\%%~ni" "%%i"
It's safer to quote the filename, especially if you want/need to use the FQFN. Therefore you need the usebackq option, else a quoted string will be processed as a string, not a filename:
for /f "usebackq delims=" %%i in ("C:\full path\mount.txt") do mklink /d "C:\VOLUMES\%%~ni" "%%i"
You seemingly want to dynamically set the variables as dummy array's and return results based on the line numbers. If so, use the counter you created to become the max of the for /L loop, here is an example by just echoing the results:
#echo off & set cnt=0
setlocal enabledelayedexpansion
for /F "usebackq delims=" %%i in ("mounts.txt") do (
set /a cnt+=1
set "var[!cnt!]=%%i"
)
for /L %%a in (1,1,!cnt!) do echo !var[%%a]!
Edit
As highlighted by #Compo in the comments; after the initial code block where we increment the %cnt% variable, we no longer require using delayedexpansion on the %cnt% variable specifically and can therefore use %cnt% instead of !cnt!:
#echo off & set cnt=0
setlocal enabledelayedexpansion
for /F "usebackq delims=" %%i in ("mounts.txt") do (
set /a cnt+=1
set "var[!cnt!]=%%i"
)
for /L %%a in (1,1,%cnt%) do echo !var[%%a]!
This is a courtesy example, just to show you how you could have still used find.exe, as in one of your examples, and additionally includes a different method of iterating the variables with unknown increments:
#Echo Off
SetLocal EnableExtensions
Rem The next line ensures that there are no variables defined with names beginning _
For /F "Delims==" %%G In ('"(Set _) 2>NUL"') Do Set "%%G="
Rem The next lines parse the directory paths content file and defines the incrementing variables
For /F "Tokens=1,* Delims=]" %%G In (
'"%SystemRoot%\System32\find.exe /N "\" 0< "C:\Users\roar\My Directory\mounts.txt" 2>NUL"'
) Do (
Set "_Path%%G]=%%H"
Set "_Name%%G]=%%~nxH"
)
Rem The next lines iterate the defined variables beginning _Path[ and runs commands against them
For /F "Tokens=2 Delims=[]" %%G In ('"(Set _Path[) 2>NUL"') Do (
SetLocal EnableDelayedExpansion
MkLink /D "C:\Volumes\!_Name[%%G]!" "!_Path[%%G]!"
EndLocal
)
Rem The next line undefines any previously defined variables named beginning with _
For /F "Delims==" %%G In ('"(Set _) 2>NUL"') Do Set "%%G="
Please note that is likely you may need to run this script elevated, in order for the MkLink command to create the symbolic links.

How to process a list of file/folder names in a FOR loop which can contain also an exclamation mark?

I write batch script to find contents for file in folder. Contents are in text file and have special characters like exclamation mark.
How do I get FILENAME and FOLDERNAME which contain exclamation mark.
#ECHO off
SETLOCAL EnableDelayedExpansion
set /p SRC="Enter source folder link: "
set /p DST="Enter destination folder link: "
FOR /F "delims=" %%a IN ('DIR /b /s /a-d "%SRC%"') do (
Set "CODE=%%~na"
Set "EXT=%%~xa"
findstr /c:"!CODE!" "%SRC%\Content.txt">nul
IF "!errorlevel!" EQU "0" (
for /F "tokens=2,3" %%c in ('findstr /c:"!CODE!" "%SRC%\Content.txt"') do (
ECHO !CODE!
Set "NEWNAME=%%c"
Set "FOLDERNAME=%%d"
Set "NEWNAME=!NEWNAME:_= !"
Set "FOLDERNAME=!FOLDERNAME:_= !"
IF not exist "%DST%\!FOLDERNAME!" md "%DST%\!FOLDERNAME!"
mklink "%DST%\!FOLDERNAME!\!NEWNAME!!EXT!" "%%a"
)
)
)
Endlocal
Exit
PS: Source folder has many files.
One solution is using a subroutine to avoid usage of delayed environment variable expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
:GetSource
set "SRC="
set /P SRC="Enter source folder link: "
if not defined SRC goto GetSource
set "SRC=%SRC:"=%"
if not defined SRC goto GetSource
:GetDestination
set "DST="
set /P DST="Enter destination folder link: "
if not defined DST goto GetDestination
set "DST=%DST:"=%"
if not defined DST goto GetDestination
for /F "eol=| delims=" %%I in ('dir /A-D /B /S "%SRC%" 2^>nul') do (
if exist "%SRC%\Content.txt" for /F "tokens=2,3" %%A in ('%SystemRoot%\System32\findstr.exe /C:"%%~nI" "%SRC%\Content.txt" 2^>nul') do (
set "NEWNAME=%%~A"
set "FOLDERNAME=%%~B"
call :MakeLink "%%I"
)
)
endlocal
exit /B
:MakeLink
echo %~n1
set "NEWNAME=%NEWNAME:_= %"
set "FOLDERNAME=%FOLDERNAME:_= %"
if not exist "%DST%\%FOLDERNAME%" md "%DST%\%FOLDERNAME%"
mklink "%DST%\%FOLDERNAME%\%NEWNAME%%~x1" %1
goto :EOF
Open a command prompt window and run call /? for help explaining how to use the command CALL with enabled command extensions to run a block in same batch file like a subroutine. See also Where does GOTO :EOF return to?
I have not studied your code, but I'd assume that enabling the delayed expansion after setting the variable names would be more appropriate:
#Echo Off
SetLocal DisableDelayedExpansion
Set /P "SRC=Enter source folder link: "
Set /P "DST=Enter destination folder link: "
For /D /R %%A In (*) Do For /F "Tokens=2-3" %%B In (
'FindStr/C:"%%~nxA" "%SRC%\Content.txt" 2^>Nul') Do (Echo %%~nA
Set "NEW=%%B"
Set "FLD=%%C"
SetLocal EnableDelayedExpansion
If Not Exist "%DST%\!FLD:_= !\" MD "%DST%\!FLD:_= !" 2>Nul && (
MkLink "%DST%\!FLD:_= !\!NEW:_= !%%~xA" "%%A")
Endlocal)
Exit /B
I would strongly suggest you perform some proper verification of the user input prior to performing tasks using them, i.e. before the For loop.

Passing a Batch Variable Into a Nested Loop Correctly - Delayed Expansion of Variables

I'm attempting to make a script that creates a set of folders from list.txt (a list of file directory names separated by carriage returns), with each folder containing a particular number of subdirectories named by the user.
This script does exactly that and works correctly:
#echo off
setlocal enableextensions
set /P q="How many subdirectories would you like to add in each folder?[0/1/2/3]"
if /I "%q%" EQU "0" goto :somewhere0
if /I "%q%" EQU "1" goto :somewhere1
if /I "%q%" EQU "2" goto :somewhere2
if /I "%q%" EQU "3" goto :somewhere3
if errorlevel 1 goto :problem
:somewhere0
echo All done! Press any key to exit.
pause
exit
...(code cut for brevity)
:somewhere3
set /P c="What is the name of the first subdirectory?"
for /F "tokens=1 delims=," %%d IN (list.txt) DO md "%%c"\"%%d"
set /P c="What is the name of the second subdirectory?"
for /F "tokens=1 delims=," %%d IN (list.txt) DO md "%%c"\"%%d"
set /P c="What is the name of the third subdirectory?"
for /F "tokens=1 delims=," %%d IN (list.txt) DO if not exist %%d md "%%d" cd "%%d" md "%%c" cd ..
if errorlevel 1 goto :problem
goto :somewhere0
...
You get the picture. However, I'm attempting to do this in a loop instead of hard-coding each instance. The code I've developed looks like this:
#echo off
setlocal enableextensions
set "var=string"
set /P q="How many subdirectories would you like to add in each folder?"
for /l %%x in (1, 1, %q%) do (
set /P c="What is the name of subdirectory %%x?"
for /F "tokens=1 delims=," %%d IN (list.txt) DO md "%%d\%c%"
)
Trying to echo the variable %%d or %c% in the middle of the loop doesn't yield anything. Stranger, the script actually creates the top level directories - ie, this code runs
for /F "tokens=1 delims=," %%d IN (list.txt) DO md "%%d
but the subdirectories do not show up. Suggestions?
Note that I don't honestly know why the first working code demands this particular order of % signs, but that is the only way I could get it to work.
enabledelayedexpansion is what you want here.
#echo off
setlocal enableextensions enabledelayedexpansion
set "var=string"
set /P q="How many subdirectories would you like to add in each folder? "
for /l %%x in (1, 1, %q%) do (
set /P c="What is the name of subdirectory %%x? "
for /F "tokens=1 delims=," %%d IN (list.txt) DO md "%%d\!c!"
)
from cmd.exe if you run setlocal /? you will get some more help, but in short, enabledelayedexpansion enables delayed environment variable expansion. It will cause variables within a batch file to be expanded at execution time rather than at parse time. Can also be enabled as CMD /V:ON on commandline, and not in Batch.
You basically tell the command processor to delay the resolution of variables until it executes them. % is substituded with ! to show which variables are to be used in the expansion.

Move files into folders based on their names

I have some files in the form:
filename1 1.ext
filename1 2.ext
filename1 3.ext
...
filename2 1.ext
filename2 100.ext
...
filename20 1.ext
filename20 15.ext
(etc.)
...where filename can contain spaces.
And I want to move them to folder filename1, filename2, etc., respectively.
I know I can do a for loop for %%i in (*.ext) do and remove the extension with set folder=%%~ni. So what I am missing is how to remove everything after the space just before the number, and get only filename1, for example.
I also know I can split variable folder, but in this case I do not know by at which character I need to split, although I know it will be a space followed by a number.
So basically, I want something like this:
#echo off
set folder=
for %%i in (*.ext) do set folder=%%~ni & set folder=getfoldernamefromvariablefoldersomehow & mv %%i %folder%
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*.ext" '
) DO (
CALL :sub1 "%%a" %%a
)
GOTO :EOF
:sub1
SET "filename=%~1"
:subloop
SHIFT
SET "numname=%~1"
IF NOT "%~2"=="" GOTO subloop
CALL SET "dirname=%%filename: %numname%=%%
ECHO( MD "%sourcedir%\%dirname%" 2>nul
ECHO( MOVE "%sourcedir%\%filename%" "%sourcedir%\%dirname%\%numname%"
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories.
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
Perform a directory list of the required files in basic form without directorynames. Send the full fulename in quotes and without to the subroutine sub1.
In the subroutine, save the source filename in filename then shift each parameter supplied until there is no second parameter; the value in numname must then be the last or required filename.
Remove numname with a leading space from filename to get the required subdirectoryname, make that subdirectory and move the file.
[edit in the light of comment]
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*.ext" '
) DO (
CALL :sub1 "%%a" %%a
)
GOTO :EOF
:sub1
SET "filename=%~1"
SET "destdirname=%~2"
:subloop
SHIFT
SET "numname=%~1"
IF NOT "%~2"=="" GOTO subloop
CALL SET "dirname=%%filename: %numname%=%%
ECHO( MD "%sourcedir%\%destdirname%" 2>nul
ECHO( MOVE "%sourcedir%\%filename%" "%sourcedir%\%destdirname%\%numname%"
GOTO :eof
It's difficult to scry your intentions when you give no example.
destdirname is set to the second parameter on entering sub1 which will be the first group of characters before the first space.
the md does not need to be gated since the 2>nul will suppress the directory exists error message.
Here is a script that does what you want. It splits off the last SPACE followed by numerals from the file name and uses the remaining string as the name of the destination directory of the movement.
This approach handles all valid characters for file names properly, even ^, &, %, !, ( and ). It can even handle file names that contain SPACE plus numerals plus .ext again correctly.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."
for /F "eol=| delims=" %%F in ('
dir /B "%_SOURCE%\*.ext" ^| findstr /R /I /C:" [0123456789][0123456789]*\.ext$"
') do (
set "FILE=%%F"
call :SPLIT LAST REST "%%F"
setlocal EnableDelayedExpansion
2> nul mkdir "!_TARGET!\!REST!"
ECHO move /Y "!_SOURCE!\!FILE!" "!_TARGET!\!REST!"
endlocal
)
endlocal
exit /B
:SPLIT rtn_last rtn_rest val_string
setlocal DisableDelayedExpansion
set "RES=" & set "STR=%~3"
:LOOP
for /F "tokens=1,* delims= " %%I in ("%STR%") do (
if "%%J"=="" (
set "RES=%%I"
) else (
set "STR=%%J"
goto :LOOP
)
)
set "STR=%~3|"
call set "STR=%%STR: %RES%|=%%"
(
endlocal
set "%~1=%RES%"
set "%~2=%STR:^^=^%"
)
exit /B
After having tested the script, remove the upper-case ECHO command to actually move any files. Unless you remove the /Y option from the move command, files become overwritten without prompt. To suppress summary messages (like 1 file(s) moved.), add > nul to the move command line. Note that any prompt was also hidden then in case you removed the /Y option.
Thank to all of you for your comments. Finally, i was able to get a solution:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "_source=C:\Users\kurok_000\Downloads"
set "_target=C:\Users\kurok_000\YandexDisk\Mangas"
for /f "eol=| delims=" %%f in ('dir /b "%_source%\*.7z"') do (
call :fixNames "%%f" %_source%
)
for /f "eol=| delims=" %%f in ('dir /b "%_source%\*.7z" ^| findstr /r /i /c:" [0123456789][0123456789]*\.7z$"') do (
set "file=%%f"
call :split last rest "%%f"
setlocal EnableDelayedExpansion
2> nul mkdir "!_target!\!rest!"
move /y "!_source!\!file!" "!_target!\!rest!" >nul
echo moved %%f to !_target!\!rest!
endlocal
)
endlocal
exit /b
:split rtn_last rtn_rest val_string
setlocal DisableDelayedExpansion
set "res=" & set "str=%~3"
:loop
for /f "tokens=1,* delims= " %%i in ("%str%") do (
if "%%j"=="" (
set "res=%%i"
) else (
set "str=%%j"
goto :loop
)
)
:quit
set "str=%~3|"
call set "str=%%str: %res%|=%%"
(
endlocal
set "%~1=%res%"
set "%~2=%str:^^=^%"
)
exit /b
:fixNames _file _folder
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FILE=%1"
set "FILE=%file:~1,-1%"
set "folder=%2"
for /F "tokens=1,* delims=0123456789" %%A in ("%FILE%") do (
set filename=!FILE:%%B=!%%~xB
)
if not "%filename%"=="%FILE%" (rename "!folder!\!FILE!" "!filename!")

Batch script to replace specific string in multiple files

I don't have much experience with batch scripts, and my employer has asked me write a batch script that can be run to find and replace some text inside all matching files in the directory.
I've tried searching this and there's a tonne of resource, which I've used to get me this far:
#echo off
setlocal enableextensions disabledelayedexpansion
set "search=<employeeloginid>0"
set "replace=<employeeloginid>"
set "textFile=TimeTEQ20170103T085714L.XML"
for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:%search%=%replace%!"
>>"%textFile%" echo(!line!
endlocal
)
This will find all occurrences of <employeeloginid>0 and replace it with <employeeloginid> inside a set file - in this case TimeTEQ20170103T085714L.XML.
I now need to tweak this to run on all files that start with TimeTEQ and end with .xml
I found this answer which shows how to do all files in a directory, but I don't know how I would tweak it to suit my needs here.
Can anyone help me please?
Simply wrap around a standard for loop, like this:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "search=<employeeloginid>0"
set "replace=<employeeloginid>"
set "textFile=TimeTEQ*.xml"
set "rootDir=."
for %%j in ("%rootDir%\%textFile%") do (
for /f "delims=" %%i in ('type "%%~j" ^& break ^> "%%~j"') do (
set "line=%%i"
setlocal EnableDelayedExpansion
set "line=!line:%search%=%replace%!"
>>"%%~j" echo(!line!
endlocal
)
)
endlocal
If you want to process matching files also in the sub-folders, use a for /R loop:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "search=<employeeloginid>0"
set "replace=<employeeloginid>"
set "textFile=TimeTEQ*.xml"
set "rootDir=."
for /R "%rootDir%" %%j in ("%textFile%") do (
for /f "delims=" %%i in ('type "%%~j" ^& break ^> "%%~j"') do (
set "line=%%i"
setlocal EnableDelayedExpansion
set "line=!line:%search%=%replace%!"
>>"%%~j" echo(!line!
endlocal
)
)
endlocal
You can surround the loop you got currently with another one going through all files in the directory that match your pattern like this:
for /f "delims=" %%n in ('dir /b ^| findstr /r "TIMETEQ.*\.xml') do (
and change the header of your current loop to:
for /f "delims=" %%i in ('type "%%~n" ^& break ^> "%%~n" ') do (
and >>"%textFile%" echo(!line! to
>>"%%~n" echo(!line!
So in total the new script should look like this:
#echo off
setlocal enableextensions disabledelayedexpansion
set "search=<employeeloginid>0"
set "replace=<employeeloginid>"
set "textFile=TimeTEQ20170103T085714L.XML"
for /f "delims=" %%n in ('dir /a-d /b ^| findstr /r "TIMETEQ.*\.xml') do (
for /f "delims=" %%i in ('type "%%~n" ^& break ^> "%%~n" ') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:%search%=%replace%!"
>>"%%~n" echo(!line!
endlocal
)
)
Explanation:
The new added outer loop goes through the output of the command dir /b ^| findstr /r "TIMETEQ.*\.xml which itself contains the result of a regex search over the output of the dir command with /b switch to show only the filenames and /a-d to execlude directories from the search.
Then I changed %filename% to the parameter of the loop -> %%~n with ~ to remove potential surrounding double quotes.
for people still searching this on google, the easy way is to open all files containing the text needed to be replaced in notepad++, press ctrl+F, click replace, type in what you want to find and replace and click replace all, then save all.

Resources