'Batch" Issue with file names having &'s in them - batch-file

Not quite sure how to title my issue that I'm running into (tried as best I could), but what I'm having issues with is when I'm trying to read in file names and then use them in a backup script I've wrote. I had originally tested it on files without &'s in the name (didn't remember that I had any with them in it, and didn't realize that there would be an issue until now).
Here is part of the code that is being used upto the call in the below example:
:backup2
if %DEBUG%=="t" echo Begin backup part 2
for %%? in ("!FILEREADIN!") do (
SET "FILENAME=%%~n?%%~x?"
SET "BACKUPFQP=%%~f?"
SET "BACKUPLAST=%%~t?"
call :getlength FILELENGTH "!FILENAME!"
Anyway the part that I'm running into the issue with when I'm working with the file name in my code to get the length of the file name (used in a separate section of script).
:getlength
SETLOCAL enabledelayedexpansion
if %DEBUG%=="t" echo %2 parsed... %%2 delayed... !%2!
SET "LENGTH=!%1!"
SET "STRING=%2"
REM need to correct the string for the "" that get added from passing in %2
REM Issue arises with this part below when working on a file name with a &
SET "STRING=!STRING:~1,-1!"
:getlengthwhile
if %DEBUG%=="t" echo Length !LENGTH!
if %DEBUG%=="t" echo String left: !STRING!
SET /a "LENGTH+=1"
REM Issue here too when working with file names with &'s
SET "STRING=%STRING:~1%"
if %DEBUG%=="t" echo Length now !LENGTH!
if %DEBUG%=="t" echo String now left: !STRING!
if %DEBUG%=="t" pause
if not ["%STRING%"]==[""] (
if %DEBUG%=="t" echo Continuing source length calculation
Goto :getlengthwhile
) else (
if %DEBUG%=="t" echo Length calculated)
ENDLOCAL & SET TEMPNUM=%LENGTH%
SET "%~1=%TEMPNUM%"
if %DEBUG%=="t" echo !%1!
if %DEBUG%=="t" pause
goto :eof
I know that there is escaping &'s to normally not have the error of the batch script trying to use the stuff right of the & as a command, but when reading in a file with one (or more) in its name how do I get it to work properly?
Here's an example of a file name that I'm having issue with and what happens when I'm running my script:
File "E:\Projects\.\Abilities&Events.docx"
Press any key to continue . . .
Begin backup part 2
"Abilities&Events.docx" parsed... %2 delayed...
'Events.docx""' is not recognized as an internal or external command, operable program or batch file.

As said, you should use more quotes and more delayed expansion.
Btw. Your code add the length to the variable in %1, perhaps this was intended, else you should change SET "LENGTH=!%1!" to set LENGTH=0
:getlength
SETLOCAL Enabledelayedexpansion
SET "LENGTH=!%1!"
SET "STRING=%~2"
:getlengthwhile
if defined STRING (
set /a LENGTH+=1
set "string=!string:~0,-1!"
goto :getlengthwhile
)
echo !LENGTH!
(
ENDLOCAL
SET "%~1=%LENGTH%"
)
goto :eof
Another problem of your code is this line
call :getlength FILELENGTH "!FILENAME!"
It fails with filenames containing ^, as they are doubled by the CALL.
So it's better to use
call :getlength FILELENGTH FILENAME
...
:getlength
SETLOCAL Enabledelayedexpansion
SET "LENGTH=!%1!"
SET "STRING=!%2!"
For much faster strlen functions you could look at SO: How do you get the string length in a batch file?

I found an answer to my problem. Thanks all for the help.
My solution was after finding out that I can replace parts of a variable (which I can use to delete parts of them even) from this site: http://ss64.com/nt/syntax-replace.html
I didn't know that replacing could be done (I knew that getting a part of a variable was possible (site for reference: http://ss64.com/nt/syntax-substring.html"))
I ended up ditching my getlength function (since it was no longer needed). and ended up with something like this:
:backup2
if %DEBUG%=="t" echo Begin backup part 2
for %%? in ("!FILEREADIN!") do (
SET "FILENAME=%%~n?%%~x?"
SET "BACKUPFQP=%%~f?"
SET "BACKUPLAST=%%~t?"
if %DEBUG%=="t" echo Filename before "!FILENAME!"
SET FILETESTPART=!FILEREADIN!
if %DEBUG%=="t" echo !FILETESTPART!
SET "FILETESTPART=!FILETESTPART:%SOURCE%=!"
if %DEBUG%=="t" echo Filename after "!FILETESTPART!"
SET "FILETESTPART=!FILETESTPART:~3!"
if %DEBUG%=="t" echo Filename after "!FILETESTPART!"

Related

Batch file - can not read a variable

Making batch which generate previews (everything is fine with this part of code) and also rename files deleting everything after "_" in filename. For example ABAB_abab.png > ABAB.png
My code does not see a variable yy in the string: set zz=!xx:yy=! Perceives it like just two letters yy, not a variable. How to fix that?
Here is the script
setlocal enabledelayedexpansion
for %%a in ("*.png") do (
set xx=%%~na
set yy=_!xx:*_=!
set zz=!xx:yy=!
echo xx= !xx! #rem (okay, returns ABAB_abab)
echo yy= !yy! #rem (okay, returns _abab)
echo zz= !zz! #rem (wrong, returns ABAB_abab without any substitutions)
pause
)
endlocal
Thank you for help
Here's a quick example to show you a method of achieving another layer of expansion:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
For %%G In ("*.png") Do (
Set "xx=%%~nG"
SetLocal EnableDelayedExpansion
Set "yy=_!xx:*_=!"
For %%H In ("!yy!") Do Set "zz=!xx:%%~H=!"
Echo xx = "!xx!"
Echo yy = "!yy!"
Echo zz = "!zz!"
EndLocal
Pause
)
The doublequotes are included in the Echo commands only for better visualization should there be any spaces in your strings, they're not needed for any other purpose.
Please note, that this will not achieve your intention with any .png files whose basename begins with one or more underscores, _.

A script that counts and prints every ocurrence of not any file inside a common subfolder in a specific path

Although I'm really a newbie in this field, I want to accomplish a task in batch scripting: There is a determinate folder of company contracts in a determinate path, each of this folders (approx. 400) has a common folder (2016) where there might be a file indicating there has been an inspection in this year. What i want is to print every company folder that has not any file in the common 2016 folder and a count of the times this happens.
This is what i have (and does not work at all):
set c=0
for %i /d in (*) do
for %j in ($%i\2016\*) do
if (%j==NUL) then (#echo $%i c+=1 echo %c)`
If you just want to know if there is a file in the 2016 directory you can do this:
#echo off
SetLocal EnableDelayedExpansion
set count=0
for %%i /d in (*) do (
REM first unset variable
set files=
for %%j in (%%i\2016\*) do (
REM will set variable each time a file is encountered
set files=present
)
if not DEFINED files (
REM No files in directory 2016
echo %%i
set /a count+=1
echo !count!
)
)
EndLocal
exit /b 0
I don't see why you use $ before each %i. If you execute this code from the command line use one % for the loop variables i and j. But in a batch-script you'll have to use two of them (%%i, %%j).
Another thing, c+=1 won't work except if you use set /a.
I used delayed expansion because each block code ( between (...)) is parsed as one single command (as if it was all on one line with && between the commands inside the block) and you can't just assign a new value to a variable and read that new value in the same command. That's also the reason why I use !count! instead of %count% (which will give the value before the block). If you'd rather not use delayed expansion, remove the SetLocal EnableDelayedExpansion and replace echo !count! with call echo %%count%% (is another way to read a new value in the same command)
Also, be aware that each echo will end its output with a carriage retur and a newline. So each echo will result in a new line of output.

BATch file: Looping in an array, how to use a string from the array?

My task is to create a script to copy dated sub folders from within folders to another location. The base folders are named 16 hex characters, each has a folder for the year, inside a folder for the month, and inside that a folder for a date, which contains binary (dump) files I need to copy out. There are about 50 folders with around 5 days across two months that I care about (for now), so I want to capture something like the following:
C:\DUMPS\2401010153783FB6\2016\09\29*.*
Rather than doing a ton of copypaste script or manually trying to get all the folders I want (human error prone), I thought I should be able to create a BAT file to loop through an array of folder names and get at the files I care about. I found a very helpful piece in the answer from user DSS at the following:
How to loop through array in batch?
My problem is that his example shows how to use CALL ECHO instead of just ECHO, but I want to use the string value as part of a folder name, not just print to the console. Here's the work-in-progress code I've developed:
SET COPYPATH=C:\Dumps
SET DESTPATH=D:\NewDumps
SET FOLDERNAME[0]=2401010153783FB6
SET FOLDERNAME[1]=2401010753BBBBCE
SET "X=1" # array start from index 1
:Looping
IF DEFINED FOLDERNAME[%X%] (
CALL ECHO %%FOLDERNAME[%X%]%% REM Works great, I see the value of FOLDERNAME[0] on console.
SET TEMPVAR= CALL ECHO %%FOLDERNAME[%X%]%% REM No good! Result is CALL ECHO.
SET TEMPVAR= CALL %%FOLDERNAME[%X%]%% REM No good! Result is CALL X.
SET TEMPVAR= %FOLDERNAME[%X%]% REM No good! Result is literal FOLDERNAME[0]; I want the value of FOLDERNAME[0].
IF EXIST %TEMPVAR%\2016\09\28 (
COPY %TEMPVAR%\2016\09\28 %DESTPATH%\%FOLDERNAME[%X%]%%\2016\09\28
)
IF EXIST %COPYPATH%\%FOLDERNAME%\2016\09\29 (
COPY %COPYPATH%\%FOLDERNAME%\2016\09\29 %DESTPATH%\%FOLDERNAME%\2016\09\29
)
IF EXIST %COPYPATH%\%FOLDERNAME%\2016\09\30 (
COPY %COPYPATH%\%FOLDERNAME%\2016\09\30 %DESTPATH%\%FOLDERNAME%\2016\09\30
)
IF EXIST %COPYPATH%\%FOLDERNAME%\2016\10\01 (
COPY %COPYPATH%\%FOLDERNAME%\2016\10\01 %DESTPATH%\%FOLDERNAME%\2016\10\01
)
IF EXIST %COPYPATH%\%FOLDERNAME%\2016\10\02 (
COPY %COPYPATH%\%FOLDERNAME%\2016\10\02 %DESTPATH%\%FOLDERNAME%\2016\10\02
)
SET /A "X+=1"
GOTO :Looping
)
I would be fine with using a temporary variable for either path, or I could use the concatenated %COPYPATH%\%FOLDERNAME% syntax (which worked when I set this up for a one-off test, but now I want to loop it 50 times). Any advice is much appreciated!
how about...
#echo off
setlocal enabledelayedexpansion
SET "COPYPATH=C:\Dumps"
SET "DESTPATH=D:\NewDumps"
SET "FOLDERNAME[0]=2401010153783FB6"
SET "FOLDERNAME[1]=2401010753BBBBCE"
rem set start to first item index, stop to last
SET/a start=0, stop=1
SET "DAYS=2016\09\28,2016\09\30,2016\10\01"
for /L %%i in (%start%,1,%stop%) do (
echo !FOLDERNAME[%%i]!
set "TEMPVAR=%COPYPATH%\!FOLDERNAME[%%i]!"
set "DESTVAR=%DESTPATH%\!FOLDERNAME[%%i]!"
for %%d in (%DAYS%) do (
rem if it works, remove echo from line below
IF EXIST "!TEMPVAR!\%%d" echo COPY "!TEMPVAR!\%%d" "!DESTPATH!\%%d"
)
)
exit /B
how about
for /L %%a in (0,1,1) do (
for %%b in (2016) do (
for %%c in ("09 28" "09 29" "09 30" "10 01" "10 02") do (
call :sub %%a %%b %%~c
)
)
)
...whatever, etc...
goto :eof
:sub
call set "foldername=%%foldername[%1]%%"
IF EXIST %COPYPATH%\%FOLDERNAME%\%2\%3\%4 (
echo COPY %COPYPATH%\%FOLDERNAME%\%2\%3\%4 %DESTPATH%\%FOLDERNAME%\%2\%3\%4
)
goto :eof
where the echo is there to show you what is proposed, delete that echo keyword to execute.
Note that you offer no explanation of why you switch from tempname to copypath\foldername in your code, so I've assumed it's incomplete substitution.
I suppose all your:
SET TEMPVAR=....
were attempts to save the result of a command into a variable
Well I have to dissapoint you on that ... there is no possibility (yet) to assign directly the result of a command to a veriable in batch ( sorry ). Indirect ways do exist: for /f-loops, use of temporary file, ... but they could make your code more complex
The command you are looking for is
call set tempvar=%%foldername[%x%]%%
The call is used to execute the commands you gave him as parameter inside a batch-file.
So the command will look like (suppose x=0) set tempvar=%foldername[0]% and the call will just execute that (if x=0 off course...).
Good luck!
PS: consider #Magoo's answer to improve your batch script and #elzooilogico if you'd consider to use delayed epansion.

Writing to different lines with Batch

I'm trying to make a reminder system within batch in which there are different lines of reminders. My batch program will write to different lines in a .txt file, but it isn't working. Could you please help and try to find the issues?
#echo off
echo Enter slot # for reminder
set /p n=
cls
echo Please type in the assignment name
set /p a=
echo ----------------------------------
echo Please type in the class
set /p c=
echo ----------------------------------
echo Please type in the date due
set /p d=
cls
if %n%==1 goto l1
if %n%==2 goto l2
if %n%==3 goto l3
if %n%==4 goto l4
if %n%==5 goto l5
if %n%==6 goto l6
:l1
echo Reminder for %c% Homework! %a%,%d% > Reminder.txt
:end
:l2
echo Reminder for %c% Homework! %a%,%d% >> Reminder.txt
:end
:l3
echo Reminder for %c% Homework! %a%,%d% >>> Reminder.txt
:end
:l4
echo Reminder for %c% Homework! %a%,%d% >>>> Reminder.txt
:end
:l5
echo Reminder for %c% Homework! %a%,%d% >>>>> Reminder.txt
:end
:l6
echo Reminder for %c% Homework! %a%,%d% >>>>>> Reminder.txt
:end
Hints to fix what you've got:
The > character won't let you write to specific lines, and there's no native support in Windows batch to do such a thing.
There are two operators that use the > character: >, which redirects output to a file (replacing any existing content), and >>, which appends (adds to the end of) a file.
You've got multiple instances of :end, but that's invalid. :end is a label, which is a unique reference to that point in the code. When you add more than one, some get ignored and you get undefined behaviors, which is bad.
It looks like you're trying to use :end to exit. Use goto :EOF for that. It jumps to the built-in label :EOF, short for End Of File.
You need to handle the case where n is none of the predefined values. Currently if someone entered 7 for n, your program would get to the logic after :l1 and run it, which is wrong. Put a goto :EOF there just in case.
How to approach solving this type of issue with batch:
The only way I can think of off the top of my head to modify a specific line is to iterate through all lines using a for /f loop, rewriting each line (to a temporary file) until you encounter the one you want to change, then write your new content instead of the existing content. Then when you're done iterating, you can replace the original file with that temporary file.
You would have to do this each time you wanted to change a new line. Batch is a really simple language that doesn't have useful constructs like arrays, or the many external tools that a shell scripting language like Bash would have. It's also got some really unsophisticated runtime evaluation.
Here's a partial solution that you can combine with a few lines from your code above to achieve what you want. It prompts you for a line number, then puts the content of the newContent variable (replace with your implementation) into the file at the specified line:
REM suppresses the echo of the commands in the program
#ECHO OFF
REM sets a feature that overcomes some of the weak runtime evaluation limitations that batch has
setlocal ENABLEDELAYEDEXPANSION
REM The name of your file
set fname=file.txt
REM If our file doesn't already exist, make a new one with 6 empty lines since that's all we want for now.
if EXIST "%fname%" goto alreadyExists
for /l %%b in (1,1,6) do echo.>>"%fname%"
:alreadyExists
REM The name of a temp file
set tfile=f2.txt
REM A counter to track the line number
set counter=0
REM Input to get the line number you wish to replace
set /p replacementLine=Type the line number that should be replaced:
REM The content that goes on the replaced line
set newContent=New entry
REM Read the file, iterate through all lines.
for /f "tokens=*" %%a in (file.txt) do (
REM Add one to the counter
set /a counter=!counter!+1
REM Use the redirect '>' operator for the first line
if "!counter!"=="1" (
if "!counter!"=="%replacementLine%" (
REM We're on the line we wish to replace, so use the replacement line content
echo.%newContent% >f2.txt
) else (
REM We're NOT on the line we wish to replace, so use the original line content
echo.%%a >f2.txt
)
) else (
REM else for lines other than the first, use the append redirect '>>'
if "!counter!"=="%replacementLine%" (
REM We're on the line we wish to replace, so use the replacement line content
echo.%newContent% >>f2.txt
) else (
REM We're NOT on the line we wish to replace, so use the original line content
echo.%%a >>f2.txt
)
)
)
REM Delete the original file
del "%fname%"
REM Replace it with the modified copy
ren "%tfile%" "%fname%"
You can replace a few lines at the top get the functionality you want.
you can't write to a specific line in a file with batch. Instead you have to rewrite the complete file.
Steps: a) read the file. b) change the desired line. c) (over)write the file with the new data.
#echo off
setlocal enabledelayedexpansion
if not exist reminder.txt call :InitFile :: create the file, if it doesn't exist
set /p "n=Enter slot # for reminder: "
set /p "a=type in the assignment name: "
set /p "c=type in the class: "
set /p "d=type in the date due: "
cls
call :ReadFile
set "_Line[%n%]=Reminder for %c% Homework: %a%,%d%"
call :WriteFile
type Reminder.txt
goto :eof
:ReadFile
set x=0
for /f "delims=" %%i in (reminder.txt) do (
set /a x+=1
set "_Line[!x!]=%%i"
)
goto :eof
:WriteFile
set x=0
(for /f "tokens=2 delims==" %%i in ('set _Line[') do echo %%i)>Reminder.txt
goto :eof
:InitFile
(for /l %%i in (1,1,6) do echo Reminder %%i [empty])>Reminder.txt
goto :eof
(Note: this would make trouble with more than 9 lines because of the alphabetical sorting with set _line[, but as you need only 6 lines, this should not be a problem for you)
Note: your input shouldn't contain !

How to choose one of multiple actions based on file extension, in batch

I'm an amateur on the usage of the FOR command. I need a batch file that will run one of 5 file conversion tools based on a file's extension. I want to drop a file onto the batch file icon and have it converted.
Since my list is huge, I can't use nested IF's.
What I've tried so far:
#ECHO OFF
SET cadfile=.dwg .dxf .dwf
SET gsfile=.ps .eps .epi .epsp
SET xxxxxx=.xx .xx and goes on
FOR %%~x1 in (%cadfile%) do (
Do some action
FOR %%~x1 in (%gsfile%) do (
Do some other action
)
)
The %%~x1 variable is used for file extension of file, which dragged and dropped over the batch file.
(edited to make more clear)
FOR %%a in (%cadfile%) do (
if /i "%~x1"=="%%a" some_action "%~1"
)
... and follow the bouncing ball for the rest of the utilities/lists
I think this will work for you. It looks through all your groups of extensions in a single For loop and when the matching extension is found, calls a label where you can do the conversion and any related tasks. You'll need to finish the "groupN" variables and labels.
#echo off
SETLOCAL EnableDelayedExpansion
set file="%1"
set ext=%~x1
:: Set the 5 groups of extensions that have different converters
set group1=.dwg, .dxf, .dwf
set group2=.ps, .eps, .epi, .epsp
For %%A in (1 2 3 4 5) do (
set groupnum=group%%A
call set thisgroup=%%!groupnum!%%
:: Look for extension in this group
echo.!thisgroup!|findstr /i /C:"%ext%" >nul 2>&1
if not errorlevel 1 call :group%%A
:: else go loop next group
)
echo Extension not found in any group &pause &goto end
:group1
echo group1 file to convert is %file%
goto end
:group2
echo group2 file to convert is %file%
goto end
:end
pause
exit
The following method allows you to easily add and modify your list of extensions/applications. Please note that you just need to edit the values placed inside the first FOR command; the rest of the program is the solution you don't need to care of...
#echo off
setlocal EnableDelayedExpansion
rem Define the list of extensions per application:
rem (this is the only part that you must edit)
for %%a in ("cadfile=.dwg .dxf .dwf"
"gsfile=.ps .eps .epi .epsp"
"xxxxxx=.xx .xx1 .xx2") do (
rem The rest of the code is commented just to be clear,
rem but you may omit the reading of this part if you wish
rem Separate application from its extensions
rem and create a vector called "ext" with an element for each pair
for /F "tokens=1,2 delims==" %%b in (%%a) do (
rem For example: %%b=cadfile, %%c=.dwg .dxf .dwf
for %%d in (%%c) do set "ext[%%d]=%%b"
rem For example: set "ext[.dwg]=cadfile", set "ext[.dxf]=cadfile", set "ext[.dwf]=cadfile"
rem In the next line: set "ext[.ps]=gsfile", set "ext[.eps]=gsfile", etc...
)
)
rem Now process the extension of the file given in the parameter:
if defined ext[%~x1] goto !ext[%~x1]!
echo There is no registered conversion tool for %~x1 extension
goto :EOF
:cadfile
echo Execute cadfile on %1 file
rem cadfile %1
goto :EOF
:gsfile
echo Execute gsfile on %1 file
rem gsfile %1
goto :EOF
etc...
If each conversion tool is executed in the same way and don't require additional parameters (just the filename), then you may omit the individual sections and directly execute the conversion tools this way:
if defined ext[%~x1] !ext[%~x1]! %1
For further explanations on array concept, see this post.

Resources