Quoting Windows batch arguments that may end in backslash - windows

I need to quote arbitrary arguments in a Windows 10 batch file, and I'm having problems when they end in a backslash.
Let's say I want to call Robocopy to copy *.foo files from A:\ to B:\ like this:
robocopy A:\ B:\ *.foo
But really I'm getting A:\ as an argument (let's say I use %~1, and I don't know if it contains spaces, so I quote it:
robocopy "%SOURCE%" B:\ *.foo
Unfortunately if %SOURCE% ends with a backslash, the last \ is considered an escape character, escaping ".
robocopy "A:\" B:\ *.foo
So Windows thinks the first argument is "A:" B:\ *.foo.
How can I turn off interpretation of \" as an escape sequence?

It's simple! You need to escape the escape character! When you use \\ instead of \, the first backslash escapes the second, and nothing further is escaped.
So, while running your command, you should use this:
<command> "this\is\a\path\\" "\this\is\another\path\\"
It is also worth nothing that robocopy works even if you don't end the paths with backslashes. So you can also totally just use this:
robocopy "this\is\a\path" "this\is\another\path" <args>
If you don't know whether or not the path ends with a backslash (for example if you're taking it from a user), you can use this command to escape all backslashes in the path:
%SOURCE%=%SOURCE:\=\\%
After which you can run robocopy normally using %SOURCE% as an argument, even if it ends with a backslash!

I suggest using a batch file like this commented one:
#echo off
if not exist %SystemRoot%\System32\robocopy.exe goto BatchHelp
if "%~1" == "" goto BatchHelp
if "%~1" == "/?" goto BatchHelp
if "%~2" == "" goto BatchHelp
call :CleanPath SOURCE %1
call :CleanPath DESTINATION %2
%SystemRoot%\System32\robocopy.exe "%SOURCE%" "%DESTINATION%" *.foo
rem Delete the used environment variables.
set "SOURCE="
set "DESTINATION="
rem Avoid a fall through to the subroutine CleanPath.
goto :EOF
rem The subroutine CleanPath must be called with two arguments.
rem The first one must be the environment variable which should hold the
rem cleaned folder path for usage with ROBOCOPY. The second argument must
rem be the folder path which should be cleaned for usage with ROBOCOPY.
:CleanPath
rem Get full absolute path of folder path. This reduces a lot of variants
rem which could occur on batch file called with various relative paths.
set "FolderPath=%~f2"
rem Does the folder path not end with a backslash?
if not "%FolderPath:~-1%" == "\" goto HavePath
rem Does the folder path have just three characters and is ending with
rem a colon and a backslash? Yes, append an escaping backslash at end.
rem Otherwise the folder path of a folder not being the root folder
rem of a drive ends with a backslash which must be removed to get
rem the folder path correct interpreted by ROBOCOPY.
if "%FolderPath:~1%" == ":\" (
set "FolderPath=%FolderPath%\"
) else (
set "FolderPath=%FolderPath:~0,-1%"
)
:HavePath
set "%~1=%FolderPath%"
set "FolderPath="
rem Return to calling routine.
goto :EOF
:BatchHelp
cls
echo Usage: %~nx0 source destination
echo/
echo source ... source directory
echo destination ... destination directory
echo/
echo This batch file requires ROBOCOPY in Windows system directory.
echo/
pause
To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
call /?
cls /?
echo /?
goto /?
if /?
pause /?
rem /?
robocopy /?
set /?

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.

Does the current directory exist in %PATH%?

I tried so many possibilities to achieve this but I keep on getting errors.
I dont know why I can't pass %CD% and %PATH% to FINDSTR.
#echo off
findstr %cd% %path%
echo %errorlevel%
pause
The result is findstr can't take value from %path% because it is not a file so I tried to echo it to file.
#echo off
echo %path% > path.txt
findstr %cd% path.txt
echo %errorlevel%
pause
For now findstr could open path.txt but couldn't get the string to compare. The %cd% didn't appear to work so I tried to put it manually like this:
#echo off
echo %path% > path.txt
findstr c:\foo path.txt
echo %errorlevel%
pause
It works!
So how can I get the current directory value and pass the value to findstr? or more plainly, how do I detect if the current directory exists within %PATH% variable.
for %%a in (echo "%path:;=" "%") do if /i "%cd%"=="%%a" echo "found it"
should do this for most situations but there are exceptions.
path may contain relative paths (. or ..) which will not be detected.
path may contain "some;directory" which will not play nicely
and there is no requirement that the drivename appears in path.
So - use with caution.
Run the following small example script which uses FIND and FINDSTR with conditionals and also an IF/ELSE:
#Echo Off
Echo(%PATH%|Find/I "%CD%;">Nul&&(Echo(Found)||Echo(Not Found
Timeout 2
Echo(%PATH%|FindStr/I "%CD%;">Nul&&(Echo(Found)||Echo(Not Found
Timeout 2
SetLocal EnableDelayedExpansion
If /I "!PATH:%CD%;=!"=="!PATH!" (Echo(Not Found) Else Echo(Found
EndLocal
Timeout -1
So that is three different attempts at the same task, how do they work for you?
There is not any universal, simple, direct way to check if the current folder is included in the path variable using findstr because for each referenced folder inside path: it can be an absolute or relative reference, it can include or not an ending backslash, it can be or not quoted, it can include or not special characters, it can include (if quoted) semicolons, ...
In top of that, in order to use findstr to do the check you will need to handle problems with the backslash characters as they are used as escape characters in regular expressions but also in literals when preceding a non alphanumeric character. Try
echo x:\_uno\ | findstr /L /c:"x:\_uno" && echo Yes || echo No
So, you will need to process each value inside the path variable dealing with quoted semicolons, special characters, backslashes, ...
Fortunately this was solved by Jeb and dbenham in the 'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell. Using their code to enumerate the elements it the path variable, and the approach in the Magoo's answer in this question, we can write somenthing like
#echo off
setlocal enableextensions disabledelayedexpansion
rem Flag variable. Assume current folder is not present in path variable
set "present="
rem This code uses:
rem Q: Pretty print %path% https://stackoverflow.com/q/5471556
rem A: Jeb answer https://stackoverflow.com/a/5472168
rem A: dbenham enhancement https://stackoverflow.com/a/7940444
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
rem Get a reference to current folder (%%c) and check against each
rem delimited value inside the processed variable
for %%c in (.) do for %%a in ("!var:"S"S=";"!") do (
if "!!"=="" endlocal
if %%a neq "" for %%b in ("%%~fa.") do (
if /i "%%~fb"=="%%~fc" set "present=1"
)
)
if defined present (
echo Current folder is INCLUDED in path variable
) else (
echo Current folder is NOT included in path variable
)
For each element in the path, resolve it to the full qualified path and check against the full qualified path of the current folder.

No "short" replacement on windows batch parameters

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

Batch decrypt folders while keeping directory structure?

I have thousands of encrypted files in the directory D:\Data. They are of different file extensions. For example D:\Data\a.txt and D:\Data\Sub\b.jpg.
I also have a decrypt.exe. The usage is:
decrypt <input file> [output file=input] [option]
The option I use is -o.
Now I need to decrypt all the files to D:\Data_2 with the original folder structure.
Can someone tell me how this task could be done with a Windows batch file?
I know little about Windows batch file coding and therefore don't know where to start.
Here is a commented batch code for this task:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "SourcePath=D:\Data"
set "TargetPath=D:\Data_2"
rem If the batch file was started with a string as
rem parameter, interpret this string as source path.
if not "%~1" == "" set "SourcePath=%~1"
rem If the batch file was started with one more string
rem as parameter, interpret this string as target path.
if not "%~2" == "" set "TargetPath=%~2"
rem Remove backslash at end of source and target path
rem in case of being specified with a backslash at end.
if "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath:~0,-1%"
if "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath:~0,-1%"
rem Determine length of source path by finding out at which
rem position in source path there is no more character.
set "PathLength=1"
:GetPathLength
if not "!SourcePath:~%PathLength%,1!" == "" (
set /A PathLength+=1
goto GetPathLength
)
rem Process each file not having hidden or system attribute set and
rem decrypt it to the target path relative to source path. The relative
rem path is determined by removing from full path of current file the
rem first PathLength characters and the last character which is the
rem directory separator (backslash).
for /R "%SourcePath%" %%I in (*) do (
set "RelativePath=%%~dpI"
set "RelativePath=!RelativePath:~%PathLength%,-1!"
md "%TargetPath%!RelativePath!" 2>nul
decrypt.exe "%%I" "%TargetPath%!RelativePath!\%%~nxI" -o
)
endlocal
It is possible to test this batch file with a dry run by inserting left of md and decrypt.exe the command echo to just output the lines which really modify something on storage media.
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.
call /? ... for an explanation of %~1 and %~2.
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
rem /?
set /?
setlocal /?
And read also the Microsoft article about Using command redirection operators for an explanation of 2>nul used to redirect the error message output by command md to handle STDERR on directory already existing to the device NUL to suppress it.
See also Microsoft's command-line reference and SS64's A-Z Index of the Windows CMD command line.

How would you write a .bat or .cmd file to remove an element from the PATH?

Related:
How to list the elements of the path in a batch file?
How does FOR work?
How would you write a batch file or CMD file to remove an element from the path? It should handle gracefully:
differences in case
shortnames and long names
I've done this using tr.exe but it's slow and complicated and uses temporary files, which makes it even more complicated.
I think the answer is something like this:
setlocal
set tpath=""
set _path="%PATH:;=" "%"
for %%p in (%_path%) do (
call :KeepIfNotEqual %%p %elementToRemove%
)
endlocal & set path=%tpath%
...where %elementToRemove% is the path element to remove. KeepIfUnique would have to be a subroutine that takes two arguments - directory names, normalizes them, and appends the first argument to tpath if it is not equal to the 2nd argument (elementToRemove).
As I said, I can do this with tr.exe, but can I do it with just built-in commands in the windows cmd.exe shell?
EDIT: I guess when you get right down to it, the question is, how to do case-conversion in cmd.exe?
This is what I came up with, using Igor's hint.
#echo off
goto START
-------------------------------------------------------
rmpath.bat
remove a path element from path
Created Tue Sep 15 21:33:54 2009
-------------------------------------------------------
:START
SETLOCAL ENABLEDELAYEDEXPANSION
#REM require one argument (the path element to remove)
if _%1==_ goto USAGE
#REM ~fs = remove quotes, full path, short names
set fqElement=%~fs1
#REM convert path to a list of quote-delimited strings, separated by spaces
set fpath="%PATH:;=" "%"
#REM iterate through those path elements
for %%p in (%fpath%) do (
#REM ~fs = remove quotes, full path, short names
set p2=%%~fsp
#REM is this element NOT the one we want to remove?
if /i NOT "!p2!"=="%fqElement%" (
if _!tpath!==_ (set tpath=%%~p) else (set tpath=!tpath!;%%~p)
)
)
set path=!tpath!
#call :LISTPATH
goto ALL_DONE
-------------------------------------------------------
--------------------------------------------
:LISTPATH
echo.
set _path="%PATH:;=" "%"
for %%p in (%_path%) do if not "%%~p"=="" echo %%~p
echo.
goto :EOF
--------------------------------------------
--------------------------------------------
:USAGE
echo usage: rmpath ^<arg^>
echo removes a path element from the path.
goto ALL_DONE
--------------------------------------------
:ALL_DONE
ENDLOCAL & set path=%tpath%
if command has ignore case option
if /I "Blah"=="blah" (echo true) else (echo false)
See if /? for more help on using it. I generally found these commands helpful in determining how to do some cmd line trickery:
if /?
call /?
for /?
set /?
I want to add something to Cheeso's answer... but I don't have enough rep to add a comment.
If you want the script he provided to work even after the batch file is finished, then change the last line like this this:
#echo off
. . .
. . .
SETLOCAL ENABLEDELAYEDEXPANSION
. . .
. . .
endlocal & set path=%tpath%
This employs a neat trick: the 'tpath' variable is available when that line is read-in and parsed by cmd.exe and expanded prior to getting deleted by 'endlocal'.
This allows the changes to the 'PATH' variable to persist even after the batch file exits.

Resources