No "short" replacement on windows batch parameters - windows

I want to iterate through a folder and check if some file exist in its subfolders. Some of these subfolders may have braces so I used the short name replacement of batch parameters (see call /?):
#echo off
for /D %%D in (*.*) do (
call :showFile "%%D\nbproject\project.properties"
)
pause
goto :eof
:showFile
echo F: %1
echo S: %~sdp1%~nx1
goto :eof
This approach works just fine on most of our systems (all Windows 7 pro). On a "new" Laptop this stops to work. The "S:" line shows the full "real" Path instead of the short one.
Subsequent commands in the original batch file throws errors because of the extra braces from the folder names. It is not possible to use double quotes in this commands so I am stuck to use the short path.
What setting prevent this replacement to work propertly?

As the 8.3 short names can be disabled, you could build a solutions which is stable for all long names.
You should always quote your variables and or switch to delayed expansion to avoid problems with braces at all.
for /D %%D in (*.*) do (
set "filename=%%~D\nbproject\project.properties"
call :showFile
)
:showFile
setlocal EnableDelayedExpansion
if exist "!filename!" echo !filename! exists

Related

"^" stripped from filename in .bat script on Windows when file is dropped onto .bat script

I'm not much of a Windows user, but I have need to write a simple .bat script to automate building a folder from a file and a couple of other folders. I want to drag and drop a folder onto the .bat script to execute the script.
The problem is that many of the folder names will have the "^" sign in the name for the folder, and when I drag and drop such folders onto the .bat script the '%1" in the script has the folder name, but the '^' character is stripped out for some reason.
Is there a way to get the literal folder name without losing the '^' characters ?
I'm adding more info with the example. My .bat file is like:
#echo off
echo %~1
mkdir USB
xcopy /s radiantUSB USB
move "%~1" USB\
echo "FINISHED"
#pause
and the name of the folder I am dropping on the .bat file is:
Duck^Donald^Quack
and the path that it is extracting is:
C:\Users\sscotti\Desktop\DuckDonaldQuack
The '^' is removed and move "%~1" USB\ fails because it the path to the folder to move is incorrect.
You can't fetch a single caret ^ with %1 nor %*, if it isn't quoted.
That's because, cmd.exe use the caret as an escape character and remove it from the arguments.
But in the hidden variable cmdcmdline all characters are present.
This works with nearly all special characters.
Tested with Donald^Duck, Dagobert ^Duck, Cat&Dog
It only fails for filenames like Cat&dog().
To be bullet proof, you need an additional AutoRun batch file, that fixes the drag&drop handling.
#echo off
setlocal DisableDelayedExpansion
set index=0
setlocal EnableDelayedExpansion
rem *** Take the cmd-line, remove all until the first parameter
rem *** Copy cmdcmdline without any modifications, as cmdcmdline has some strange behaviour
set "params=!cmdcmdline!"
set "params=!params:~0,-1!"
set "params=!params:*" =!"
echo params: !params!
rem Split the parameters on spaces but respect the quotes
for %%G IN (!params!) do (
for %%# in (!index!) do (
endlocal
set /a index+=1
set "item_%%#=%%~G"
setlocal EnableDelayedExpansion
)
)
set /a max=index-1
rem list the parameters
for /L %%n in (0,1,!max!) DO (
echo %%n #!item_%%n!#
)
pause
REM ** The exit is important, so the cmd.exe doesn't try to execute commands after ampersands
exit
Use %~1, not %1.
Dealing with the special meaning of characters within your batch is another question. Since you don't show us your batch, just how long is a piece of string?
Here's my test batch
#ECHO OFF
ECHO ----%~nx0--%*
SETLOCAL
ECHO "%1"
ECHO "%~1"
ECHO "%*"
pause
GOTO :EOF
And the test filename was
U:\Test space^caret&ampersand!exclam%percent.bat
Here's the result
----qcifn.bat--"U:\Test space^caret&ampersand!exclam%percent.bat"
""U:\Test spacecaret
'ampersand!exclam%percent.bat""' is not recognized as an internal or external command,
operable program or batch file.
"U:\Test space^caret&ampersand!exclam%percent.bat"
""U:\Test spacecaret
'ampersand!exclam%percent.bat""' is not recognized as an internal or external command,
operable program or batch file.

Using FOR /R for recursive search only in a subset of folder hierarchy

I want to create a batch file able to apply some processing on each JPG file in a folder hierarchy. The following script file works very well for that case (here I only echo the name of each file, but this should be replaced by some more complex statements in the real application):
:VERSION 1
#echo off
set "basefolder=C:\Base"
for /r %basefolder% %%f in (*.jpg) do echo %%f
Actually, I don't want to explore all the folder hierarchy under %basefolder%, but only a given list of subfolders. This modified script is able to deal with that case :
:VERSION 2
#echo off
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
pushd %basefolder%\%%~s"
for /r %%f in (*.jpg) do echo %%f
popd
)
Is there a solution to remove the pushd/popd pair of statements, to get something closer to the initial script. I thought that one of the following scripts would do the job:
:VERSION 3
#echo off
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
for /r %basefolder%\%%~s" %%f in (*.jpg) do echo %%f
)
or, using delayed expansion:
:VERSION 4
#echo off
setlocal enabledelayedexpansion
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
set "folder=%basefolder%\%%~s"
echo !folder!
for /r !folder! %%f in (*.jpg) do echo %%f
)
but none of them is working. When running the second one, the echo !folder! command in the external loop shows C:\Base\A, C:\Base\B and C:\Base\C as expected, but the inner loop doesn't echo any JPG file, so I guess that the recursive for /r command does not run correctly.
What am I doing wrong ?
Final edit after answers :
Thanks to #aschipfl who provided a link to the answer posted by #jeb on another question, quoted below:
The options of FOR, IF and REM are only parsed up to the special character phase. Or better the commands are detected in the special character phase and a different parser is activated then. Therefore it's neither possible to use delayed expansion nor FOR meta-variables in these options.
In other words, my versions 3 and 4 do not work because when defining the root folder of the FOR /R command, neither the %%~s nor the !folder! are correctly expanded by the expression parser. There is no way to change that, as this is a parser limitation. As I said in a comment below: the root folder option in the FOR /R command is basically only syntactic sugar to avoid the use of pushd/popd before and after the command. As this syntactic sugar is incomplete, we have to stick to the original syntax for some specific use cases, as the one presented here. The alternatives proposed by #Gerhard (using a subroutine CALL) or by #Mofi (parsing the result of a DIR command) are working, but they are neither more readable nor more efficient than the simple pushd/popd version I proposed initially.
My Approach for this would be really straight forward:
#echo off
set "basedir=C:\Base"
set "subfolders="A","B","C""
for %%i in (%subfolders%) do for /R "%basedir%" %%a in ("%%~i\*.jpg") do echo %%~fa
The double quotes inside of the subfolders variable is important here, it will ensure that folder names with whitespace are not seen as separators for the folder names. For instance:
set "subfolders="Folder A","Folder B","Folder C""
Edit
#echo off
set "basedir=C:\Base"
set "subfolders="A","B","C""
for %%i in (%subfolders%) do call :work "%%~i"
goto :eof
:work
for /R "%basedir%\%~1" %%a in (*.jpg) do echo %%~fa
It is in general not advisable to assign the value of a loop variable to an environment variable and next use the environment variable unmodified without or with concatenation with other strings being coded in batch file or defined already above the FOR loop within body of a FOR loop. That causes just problems as it requires the usage of delayed expansion which results in files and folders with one or more ! are not correct processed anymore inside body of the FOR loop caused by double parsing of the command line before execution, or command call is used on some command lines, or a subroutine is used called with call which makes the processing of the batch file much slower.
I recommend to use this batch file for the task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "basefolder=C:\Base"
set "subfolders=A B C "Subfolder D" SubfolderE"
for %%I in (%subfolders%) do for /F "delims=" %%J in ('dir "%basefolder%\%%~I\*.jpg" /A-D /B /S 2^>nul') do echo %%J
endlocal
The inner FOR loop starts for each subfolder defined in subfolders in background one more command process with %ComSpec% /c and the DIR command line appended as additional arguments. So executed is with Windows installed to C:\Windows for example for the first subfolder:
C:\Windows\System32\cmd.exe /c dir "C:\Base\A\*.jpg" /A-D /B /S 2>nul
The command DIR searches
in specified directory C:\Base\A and all it subdirectories because of option /S
for files because of option /A-D (attribute not directory) including those with hidden attribute set
matching the pattern *.jpg in long or short file name
and outputs to handle STDOUT of background command process just the matching file names because of option /B (bare format)
with full path because of option /S.
The error message output by DIR on nothing found matching these criteria is redirecting from handle STDERR to device NUL to suppress it.
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 dir command line with using a separate command process started in background.
The output to handle STDOUT of background command process is captured by FOR respectively the command process which is processing the batch file. FOR processes the captured output line by line after started cmd.exe terminated itself. This is very often very important. The list of files to process is already in memory of command process before processing the first file name. This is not the case on using for /R as this results in accessing file system, getting first file name of a non-hidden file matching the wildcard pattern, run all commands in body of FOR and accessing the file system once again to get next file name. The for /R approach is problematic if the commands in body of FOR change a file to process like deleting, moving, modifying, copying it in same folder, or renaming a found file because of the entries in file system changes while for /R is iterating over these entries. That can easily result in some files are skipped or some files are processed more than once and it could result also an endless running loop, especially on FAT file system like FAT32 or exFAT. It is never good to iterate over a list of files on which the list changes on each iteration.
Command FOR on usage of /F ignores empty lines which do not occur here. A non-empty line is split up into substrings using a normal space and a horizontal tab as string delimiters by default. This line splitting behavior is not wanted here as there could be full qualified file names containing anywhere inside full name one or more spaces. For that reason delims= is used to define an empty list of delimiters which disables the line splitting behavior.
FOR with option /F would also ignore lines on which first substring starts with ; which is the default end of line character. This is no problem here because of command DIR was used with option /S and so each file name is output with full path which makes it impossible that any file name starts with ;. So the default eol=; can be kept.
FOR with option /F assigns by default just first substring to specified loop variable as tokens=1 is the default. This default can be kept here as splitting the lines (full file names) into substrings is disabled already with delims= and so there is always the full file name assigned to the loop variable.
This example uses just echo %%I to output the file names with full path. But it is now safe to replace this single command by a command block which does more with the JPEG files because of the list of JPEG files for each specified subfolder tree in base folder is always already completely in memory of command process processing the batch file.

String replacement within FOR /F into batch file

There are a handful of questions on SO that look similar, but I cannot figure out some behaviour and I am looking for help.
Below is a snippet from a batch file I am trying to write which will load in a set of directories and potentially replace letter substitutions with an expanded path, e.g. the properties file might look like:
location1=C:\Test
location2=[m]\Test
Where location1 points to C:\Test and location2 points to C:\Program Files(x86)\MODULE\Test, because [m] is a shorthand to C:\Program Files(x86)\MODULE.
The batch script, to this point, is simply trying to read in the list of file paths and expand/replace the [m].
SET build.dir=%~dp0%
SET progfiles=%PROGRAMFILES(X86)%
IF "%progfiles%"=="" SET progfiles=%ProgramFiles%
SET local.properties=%build.dir%local.properties
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "tokens=1* delims==" %%i IN (%local.properties%) DO (
SET local.dir=%%j
SET local.dir=!local.dir:[m]=%progfiles%\MODULE!
echo !local.dir!
)
ENDLOCAL
Running this kicks out an error:
\MODULE was unexpected at this time.
If I replace the FOR with the following instead:
set test="[m]\Proj\Dir"
set test=!test:[m]=%progfiles%\MODULE!
echo %test%
I get the desired C:\Program Files(x86)\MODULE\Proj\Dir printed out...so I'm confused why it works fine outside of the FOR loop.
My understanding about delayed expansion is that it 'expands' at runtime...which you get to happen using !! instead of %% wrapped around the variable. Furthermore, as I'm creating the local.dir variable inside the FOR loop scope, I must use delayed expansion in order to access it with the updated value for the iteration.
I feel like the problem is using %progfiles%, like there's some special syntax I need to use in order to make it work but nothing is adding up for me. When I echo %progfiles%, it prints out as C:\Program Files(x86 -- note the missing trailing ).
Any ideas? Thanks
Tested suggestion:
D:\Projects\Test\Build>test
*** "D:\Projects\Test\Build\local.properties"
*** "","C:\Program Files (x86)"
[m]=C:\Program Files (x86)\MODULE
Adding quotes around the whole expression makes it work -- can't use other characters for some reason (like []) -- and since I want to append to the path later, we can safely remove the quotes afterwards:
SET local.dir="!local.dir:[m]=%progfiles%\MODULE!"
SET local.dir=!local.dir:"=!
Test this to see if you can nut out the issue:
The double quotes are to provide robust handling in a system with long file/path names.
The () are unquoted which are a problem in a batch script, when inside a loop.
#echo off
SET "build.dir=%~dp0%"
SET "progfiles=%PROGRAMFILES(X86)%"
IF "%progfiles%"=="" "SET progfiles=%ProgramFiles%"
SET "local.properties=%build.dir%local.properties"
echo *** "%local.properties%"
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "usebackq tokens=1* delims==" %%i IN ("%local.properties%") DO (
SET "local.dir=%%j"
echo *** "!local.dir!","%progfiles%"
SET "local.dir=!local.dir:[m]=%progfiles%\MODULE!"
echo !local.dir!
)
ENDLOCAL
pause
It has to do with the () characters that end up in your progfiles string. If you take them out, the substitution seems to work fine.
My suggestion is to ditch command for this particular purpose and use one of the other standard tools that Windows comes with. While my personal preference would be Powershell (since it's so much more powerful and expressive), you may just need something quick that you can integrate into existing cmd.exe stuff.
In that case, try the following VBScript file, xlat.vbs:
set arg = wscript.arguments
wscript.echo Replace(arg(0),arg(1),arg(2))
Your batch file then becomes something like, noting the inner for /f which captures the output of the VBS script and assigns it to the variable:
#echo off
SET build.dir=%~dp0%
set progfiles=%PROGRAMFILES(X86)%
if "%progfiles%"=="" set progfiles=%ProgramFiles%
set local.properties=%build.dir%local.properties
setlocal enabledelayedexpansion
for /f "tokens=1* delims==" %%i in (%local.properties%) do (
set local.dir=%%j
for /f "delims=" %%x in ('cscript.exe //nologo xlat.vbs "!local.dir!" "[m]" "%progfiles%\MODULE"') do set local.dir=%%x
echo !local.dir!
)
endlocal
Running that, I get the output:
C:\Test
C:\Program Files (x86)\MODULE\Test
which I think is what you were after.

batch script variable unset in for loop has no effect

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

For loop in batch file reading a file of File Paths

I want to write a Windows batch file script that will loop through a text file of FILE PATHS, do some work using data from each file path, then ultimately delete the file.
I started by running the FORFILES command and sending its output (the #PATH parameter is the full path of any file it matches) to a text file (results.txt).
I end up with a results.txt file like this:
"C:/Windows/Dir1/fileA.log"
"C:/Windows/Dir1/fileA.log"
"C:/Windows/Dir2/fileC.log"
"C:/Windows/Dir3/fileB.log"
What I want to do is:
Use a FOR loop and read each line in the results.txt file
For each line (file path), strip out the directory name that the log file is sitting in (ie: Dir1, Dir2, etc..) and create a directory with that SAME name in a different location (ie. D:/Archive/Backups/Dir1, D:/Archive/Backups/Dir2, etc..) -- assuming the directory doesn't exist.
Move the actual .log file to a zip file in that directory [I have code to do this].
Delete the .log file from its original location. [Pretty straightforward]
I'm having trouble figuring out the best way to accomplish the first 2 steps. My FOR loop seems to stop after reading the very first line:
FOR /F "tokens=1,2,3,4,5,6,7,8,9,10 delims=\" %%G in ("results.txt") DO (
...
)
You don't want to parse the path with the tokens/delims options because you don't know how many directory levels you are dealing with. You want to preserve each line in its entirety. TO parse the path you want to use the FOR variable modifiers. (type HELP FOR from the command line and look at the last section of the output)
%%~pG gives the path (without the drive or file name). If we then strip off the last \, we can go through another FOR iteration and get the name (and possible extension) of the file's directory by using %%~nxA.
The toggling of delayed expansion is just to protect against a possible ! in the path. If you know that no path contains ! then you can simply enable delayed expansion at the top of the script and be done with it.
EDIT - this code has been modified significantly since Aacini pointed out that I misread the requirements. It should satisfy the requirements now.
for /f "usebackq delims=" %%G in ("results.txt") do (
set "myPath=%~pG"
setlocal enableDelayedExpansion
for /f "eol=: delims=" %%A in ("!myPath:~0,-1!") do (
endlocal
if not exist d:\Archive\Backups\%%~nxA md d:\Archive\Backups\%%~nxA
rem ::zip %%G into zip file in the D: location
rem ::you should be able to create the zip with the move option
rem ::so you don't have to del the file
)
)
I wrote this to timestamp files before offloading to SFTP.
Hope you find it useful.
The timestamp coding may seem irrelevant to your issue, but I left it because it's a good example of dissecting the filename itself.
I suggest you put an ECHO in front of the REN command for testing. Different shells may have different results.
In the end, the delayedexpansion command wasn't necessary. It was the sub-routine that fixed my issues with variables inside the loop. That could possibly be because of my OS ver. (Win 8.1) - It wouldn't hurt to leave it.
#echo off
cls
setlocal enabledelayedexpansion
if %time:~0,2% geq 10 set TIMESTAMP=%date:~10,4%%date:~4,2%%date:~7,2%_%time:~0,2%%time:~3,2%%time:~6,2%
if %time:~0,2% leq 9 set TIMESTAMP=%date:~10,4%%date:~4,2%%date:~7,2%_0%time:~1,1%%time:~3,2%%time:~6,2%
echo TimeStamp=%TIMESTAMP%
echo.
for %%G in (*.txt) do (
set OLDNAME=%%G
call :MXYZPTLK
)
dir *.txt
goto :EOF
:MXYZPTLK
echo OldName=%OLDNAME%
ren %OLDNAME% %OLDNAME:~0,-4%_%TIMESTAMP%%OLDNAME:~-4,4%
echo.
:END
You have two minor problems:
The path separator in the file is '/' but you use '\' in the for loop.
The quotes around "results.txt" stop it working.
This works. Don't write quotes to results.txt and you won't get a quote at the end of the filename.
#echo off
FOR /F "tokens=3,4 delims=/" %%I in (results.txt) DO (
REM Directory
echo %%I
REM File
echo %%J
)

Resources