问题
I had a very straight forward little batch script. The design outline is simple enough ... I want to touch every file 'dropped'.
- There is a Windows short-cut on the desktop that calls a batch (.cmd) script.
- Select some files with Windows Explorer
- Drag-n-Drop the selection onto either the short-cut icon or the command file directly.
- The script loops over the list of files dropped onto the icon.
- The script uses the FOR command to call the command one file at a time.
Anyway there are several circumstances where the result is strange (to say the least). The worst example is an infinite loop that crashes cmd.exe. Here is the command file. There are extra prints and pauses here so I can watch the behaviour.
@echo off
@title touch
@echo.
cd > z:\$$$\base.tmp
set /p base= < z:\$$$\base.tmp
@rem
@echo ^ folder: %base%
@echo ^ INPUT: [%*]
@echo.
@pause
@echo.
for /d %%x in (%*) DO (
@echo ^ RAW: [%%x]
@echo ^ each --^> [%%~x]
@echo ^ each --^> [%%~fx]
attrib "%%~fx"
touch "%%~fx"
echo.
@pause
echo.
)
@echo.
@echo ^ [done]
@echo.
@pause
The most problematic output is a source of confusion to me, see: example 1. I put the attrib command in because it is a 'tame' system .exe.
When you comment-out the "touch" command, the look works with just attrib most of the time. The only thing I can say for sure is that file names with spaces cause problems. For example in the echo-ed "each" lines, I've seen examples like:
each --> [Z:\tmp\"Z:\tmp\C++ Recipes.pdf"]
"G:\_xfer_\-m"
The first seems to be a problem with the FOR loop expansion and how it deals with quotes("). While the second appears to be a out of bounds error with the MSYS touch.exe command.
There's also something fish that goes on when you pass a link (short-cut) file to the script.
I've tried many different versions of both the FOR syntax and the loop-variable escaping to side-step the issue. After all my primary purpose is to have a GUI touch command. My first question for the brains trust is:
- What is the appropriate FOR and loop-variable expansion to use for this?
As well, since it has become a mystery ....
- How does the infinite loop occur and is there a way to prevent it?
- Does someone have a link for documentation and/or examples of FOR with a drag-n-drop with Window?
- How can the script just show:
Working folder ...
G:\_xfer_
When the string: "Working folder", which is NOT in the .cmd script. And, there was once a version that had the command:echo Working folder:
It it helps, I put a small script called, "drop.cmd" below as well. As a final point, I suspect there's a bug in the MSYS /GNU command-line handler for Windows:
- It seem that, When the
*args
that was passed contains a string with unbalanced quotes -- It can go off the reservation.
I get similar output to that in example 1, below from the MSYS touch and from tail commands.
I was about to sign-off. There is one more thing, to consider. Does anyone have a script or diagnostic I might use to 'unravel' this ball-of-wool?
example 1
folder: G:\_xfer_
INPUT: ["G:\_xfer_\00__zip (on D).lnk" G:\_xfer_\#trash.lnk]
Press any key to continue . . .
RAW: ["G:\_xfer_\00__zip (on D).lnk"]
each --> [G:\_xfer_\00__zip (on D).lnk]
each --> [G:\_xfer_\00__zip (on D).lnk]
A G:\_xfer_\00__zip (on D).lnk
all ... G:\_xfer_\00__zip (on D).lnk
one ... G:\_xfer_\00__zip
two ... (on
Working folder ...
G:\_xfer_
touch -m -c "G:\_xfer_\-m"
Working folder ...
G:\_xfer_
touch -m -c "G:\_xfer_\-m"
:
:
And eventually crashed with a stackoverflow!
drop.cmd
@echo off
@title %~1
@echo.
cd > z:\$$$\base.tmp
set /p base= < z:\$$$\base.tmp
@rem
@echo ^ drop folder: %base%
@echo ^ INPUT: [%*]
@echo.
@pause
rem
@echo.
for %%x in (%*) DO (
@echo ^ RAW: [%%x]
@echo ^ each --^> [%%~x]
@echo.
@pause
)
@echo.
@echo ^ [done]
@echo.
@pause
回答1:
Getting a batch file handle dropped files can be sometimes difficult. Any dragged file with spaces in its name will be quoted, but a file with special characters (&
) but without spaces will not be quoted. This will lead to problems with the parser handling the arguments to the file using %*
.
But taking the code from jeb' answer and polished by @dbenham to solve the problem of "Pretty print windows %path% variable" (thank you both), and retrieving the arguments from %*
only when called from command line (where arguments "should" be well formed) or from %cmdcmdline%
(that hold the command line used to start the current cmd
instance) when dropped, we can do something like
@echo off
setlocal enableextensions disabledelayedexpansion
set "var="
rem Determine call origin
setlocal enabledelayedexpansion
call :detectDrop !cmdcmdline!
endlocal
if not errorlevel 1 goto :dropped
:commandLine
rem Invoked from command line
set "dropped="
if "%~1"=="" goto done
set var=%*
set "var=%var:"=""%"
goto :process
:dropped
rem Invoked from explorer
set "dropped=1"
set "var=%cmdcmdline:"=""%"
set "var=%var:*/c """"=%"
set "var=%var:*"" =%"
set "var=%var:~0,-2%"
:process
if not defined var goto :done
rem Adapted from dbenham's answer at [https://stackoverflow.com/a/7940444/2861476]
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=!"
for %%a in ("!var:"S"S=" "!") do (
if "!!"=="" endlocal
rem Here we have a reference to the passed/dropped element
if %%a neq "" echo "%%~fa"
)
goto :done
:detectDrop cmdcmdline
if /i "%~1"=="%comspec%" if /i "%~2"=="/c" exit /b 0
exit /b 1
:done
if defined dropped ( pause & exit )
note: sorry, not thoroughly tested. Maybe there is some case that will make it fail.
来源:https://stackoverflow.com/questions/31343710/how-do-i-get-windows-cmd-for-to-play-nice-with-drag-n-drop