Batch File Random Syntax Errors - windows

like I told in the question I get an unexplainable Syntax error from my Code.
I've bin searching for a program which automatically backups some saves every 5 Minutes. After I hadn't found anything which belongs to my purposes I decided to do it my own.. in Batch.
Here's the code:
#echo off
set name=Backup
for /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir1= (here comes the source directory)
set dir2= (here comes the target directory)
set countvar=1
:start
For /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir2=(here comes the target directory)\%backupname%
echo Backupordner: %backupname%
ROBOCOPY %dir1% %dir2%
if "%countvar%" == "1" (
set VarDir1=%dir2%
)
if "%countvar%" == "2" (
set VarDir2=%dir2%
)
if "%countvar%" == "3" (
set VarDir3=%dir2%
rmdir /S /Q "%VarDir1%"
set VarDir1=%VarDir2%
set VarDir2=%VarDir3%
set /a countvar=%countvar%-1
)
set /a countvar=%countvar%+1
#ping -n 30 localhost> nul
goto start
What it basically does is copying the files from the source directory into a folder, which is named after the date and time, in the target directory.
Caused by the high size of the backuped files I decided to add a feature, which deletes the third oldest save, so there are two remaining, newer save-files.
This is where the problem occurs: The first two "deletes" work properly, the third "delete" causes a syntax error. Everything runs normal after it.
Does anybody have an idea where the problem could be?

Yet another example of the delayedexpansion trap.
When vardir3 is established, it has no value, so vardir2 acquires nothing on the first occasion that count=3
On the second occasion, var1 acquires that value so on the third occasion, you get a syntax error as var1 is empty.
Solution: Forget var3 entirely. In count=3, set var2 to %dir2%.
Please search SO for the many, many articles on delayed expansion.
Also, you're better off using set "var=value" for a string assignment as it does not assign any trailing spaces that may be on the line.

Related

Batch - Inner For-Loop token parameter does not accept delayed Expanded variable within Outer For Loop [duplicate]

This question already has answers here:
Issue with *For loop* and *delayed expansion*
(2 answers)
Closed 4 months ago.
I have numerous mp4 files inside a folder with can take the form below
Dancing_2022-10-30_00-05-32_015_Paris.mp4
Eating_in_the_Restaurant_2022-07-03_04-21-25_497_London.mp4
My goal is to create two folders and move the respective files
I want the last underscore (_) delimited item (in this case Paris and London) extracted to create the first folders and then extract the date part (2022-10-30 and 2022-07-03) and create the subfolder. Then move the files accordingly.
My biggest challenge is to extract the tokens. Usually does allows tokens to be extracted from left to right but this assumes the file names are consistent. However in my case the files names are not consistent. There is some consistency from right to left with regards to the placement of the _ delimited parts I want to extract.
I have a draft batch script but the issue I have is that CMD does not allow delayed expanded variable to be passed to the inner For-Loop within the Outer For loop. Any clues to the solution. Thanks
This is my draft batch script.
#echo off
Setlocal EnableDelayedExpansion
for %%F in ("*.mp4") do (
set file=%%~nF
echo !file!
set files="!file:_=" "!"
echo file=!files!
set cnt=-4
for %%a in (!files!) do set /a cnt+=1
echo !cnt!
for /F "tokens=!cnt!* delims=_" %%a in ("!file!") do set output=%%b
echo Output=%output%
for /F "tokens=1-4 delims=_" %%a in ("!output!") do echo %%d
)
endlocal
pause
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following setting for the directory is a name
rem that I use for testing and deliberately includes spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
FOR %%e IN ("%sourcedir%\*.mp4") DO (
SET "fname=%%~ne"
SET "fname=!fname:_=,!"
SET "fname=!fname: =#!"
FOR %%y IN (!fname!) DO SET "fdate=!ftime!"&SET "ftime=!fmsec!"&SET "fmsec=!flocation!"&SET "flocation=%%y"
ECHO MD "!flocation:#= !\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fdate!\"
)
GOTO :EOF
Always verify against a test directory before applying to real data.
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. Append 2>nul to suppress error messages (eg. when the directory already exists)
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)
Relatively easy to follow. Foe each filename, take the name part. replace each _ with , and each Space with #
The result in fname would thus be a comma-separated list of elements, so process each one, cascading them through the variables.
Finally, replace the # with Space when creating the subdirectory and filename.
Note that the switch between Space and # is intended to take care of places like New York and Los Angeles
== Supplemental == in response to comment:
FOR %%e IN ("%sourcedir%\*.mp4") DO (
SET "fname=%%~ne"
SET "fname=!fname:_=,!"
SET "fname=!fname: =#!"
FOR %%y IN (!fname!) DO SET "fdate=!ftime!"&SET "ftime=!fmsec!"&SET "fmsec=!flocation!"&SET "flocation=%%y"
SET "fname=!fname:,=_!"
CALL SET "fname=%%fname:_!fdate!_!Ftime!_!fmsec!_!flocation!=%%
ECHO MD "!flocation:#= !\!fname!\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fname!\!fdate!\"
ECHO MD "!flocation:#= !\!fname:_= !\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fname:_= !\!fdate!\"
)
After locating the required parts, replace each , with _ in fname, then replace the part from the date onwards with nothing, yielding the activity in fname.
Two sets of reports generated now - one for activity with underscores, the second with spaces.

Process cannot access the file as it is used by another process

I am trying to create windows batch script to check the size of file after it is downloaded but it fails with this message.
The Process cannot access the file as it is used by another process
2048 was unexpected at this time.
Thanks in advance for any help.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set file="tst_file.xml"
set maxbytesize=2048
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
Consider this alteration.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set "file=tst_file.xml"
set maxbytesize=2048
if not exist "%file%" (
#echo '%file%' does not exist.
goto :SKIP_CHECK
)
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
:SKIP_CHECK
Notes
If the file doesn't exist, the FOR loop will not be able to get the file size, and the LSS comparison will blow up with no operand to compare.
If you had quoted each of the vars (e.g. if "%size%" LSS "%maxbytesize%" ...), it wouldn't have blown up. However, it would have performed a lexical comparison, which when compared to anything would evaluate to true.
In batch, if you use double quotes around only the value in a SET command, the quotes will be included in the value. Even though it looks unorthodox, put quotes around the whole <variable>=<value> expression.
If you skip tokenization and get the entire line (i.e. tokens=*), you'll be able to handle files with spaces in the name.

Windows Batch File Multiple labels back to back

In PHP or Javascript and other languages as well, there is the switch, case statement as a control flow tool. One of the neat features of that is it allows for multiple cases to be pointed to a single command group. For example:
switch(abc) {
case a:
case b:
case false:
console.log("hi")
break;
case c:
console.log("see ya")
break;
etc...
}
So that if abc is equal to a or b or is false, the "Hi" will be logged. Depending on the code, it can be a lot cleaner than calling from an object or tons of if else or if x || y || z statements.
I have a windows batch file where I'm doing the following:
GOTO %1
..... stuff
.... more stuff
REM =================LABELS BELOW==============
:-h
:--help
:-?
:--?
type help.txt
exit /b
It's more detailed than the above pseudocode, but that's the gist of it. It allows for aliases for the same argument And it works. If I execute mycmd -h or mycmd --help etc., I help the help file text displayed on the screen.
However, on the last line of my output, I get the error, THE SYSTEM CANNOT FIND THE BATCH LABEL SPECIFIED -.
The error might be caused by something else. I have some CALL commands and GOTO :EOF statements, so that certainly could be the source of the error.
But I've never seen the logic I applied above before used in a batch file, and I'm wondering if there are some other side effects that I might not be considering. Is it possible that I will encounter unpredictable side effects down the road? Is this bad practice for any reason?
Update:
I hadn't posted my code, because I think it's hard to read, and it's a work in progress. Basically, what you're seeing here is argument parsing. I'm parsing the passed values in to categories - imagine this example: node --file=d.js. I'm calling the --file the param, and the d.js the arg. And much of the code below is creating an array of each.
#echo off
SETLOCAL enabledelayedexpansion
#SET "T=%1"
IF /i NOT DEFINED T (GOTO -h)
SET /a counter=1
SET /a argCount=0
SET /a index=0
for %%x in (%*) do set /A argCount+=1
for /l %%x in (1,2,%argCount%) do (
call SET param[!index!]=%%!counter!
set /a counter+=2
SET /a index+=1
)
SET /A paramCount=!index!
SET /a counter=2
SET /a index=0
for /l %%x in (2,2,%argCount%) do (
call SET arg[!index!]="%%!counter!"
set /a counter+=2
SET /a index+=1
)
for /l %%i in (0,1,!paramCount!) do (
SET "arg=!ARG[%%i]!"
SET "p=!param[%%i]!"
CALL :!p! !arg!
)
GOTO END
:-h
:--help
:--?
:-?
type %~dp0\lib\help.txt
GOTO END
:--unzip
SET "a=%1"
IF /i NOT DEFINED a (
SET "MASK=\d+-[a-z]+.zip"
) ELSE if [%a%]==["all"] (
SET "MASK=\d+-[a-z]+.zip"
) else (
SET "MASK=!a!"
)
for /f "usebackq tokens=*" %%i in (`dir /a /b %dls%^|grep -iE "!MASK!"`) do (
#7z x %dls%\%%i -omypath\share\icons
)
GOTO :EOF
:END
#echo Done
ENDLOCAL
Update: I don't know if this will be helpful for anyone, but the problem was with SET /A paramCount=!index!. That let to always looking for a parameter with an argument no matter what, so that if my second parameter didn't have an argument, or neither did, it was causing problems. I 'solved' the problem by setting paramCount to !index!-1, but I think the upshot of this is that it's quite difficult to pass arbitrary, potentially optional, parameters to batch files, and probably should be parsed differently than I have done - or using a different coding language. That said, it's working fine now for what I need.

Batch ECHO %varname% just saying "Echo is on."

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

Loop over folder string and parse out last folder name

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.

Resources