Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
516 views
in Technique[技术] by (71.8m points)

batch file - How do I get Windows cmd FOR to 'play-nice' with drag-n-drop

I had a very straight forward little batch script. The design outline is simple enough ... I want to touch every file 'dropped'.

  1. There is a Windows short-cut on the desktop that calls a batch (.cmd) script.
  2. Select some files with Windows Explorer
  3. Drag-n-Drop the selection onto either the short-cut icon or the command file directly.
  4. The script loops over the list of files dropped onto the icon.
  5. 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:$$$ase.tmp
  set /p base= < z:$$$ase.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:mp"Z:mpC++ 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:

  1. What is the appropriate FOR and loop-variable expansion to use for this?

As well, since it has become a mystery ....

  1. How does the infinite loop occur and is there a way to prevent it?
  2. Does someone have a link for documentation and/or examples of FOR with a drag-n-drop with Window?
  3. 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_0__zip (on D).lnk" G:\_xfer_#trash.lnk]

Press any key to continue . . .

    RAW:      ["G:\_xfer_0__zip (on D).lnk"]
    each -->  [G:\_xfer_0__zip (on D).lnk]
    each -->  [G:\_xfer_0__zip (on D).lnk]
A            G:\_xfer_0__zip (on D).lnk
  all ... G:\_xfer_0__zip (on D).lnk
  one ... G:\_xfer_0__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:$$$ase.tmp
set /p base= < z:$$$ase.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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...