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
390 views
in Technique[技术] by (71.8m points)

windows - "Droplet" batch script - filenames containing ampersands

I'm trying to create a batch file that can have other files dropped onto it. Specifically, I'm using ffmpeg to edit audio files produced by a handheld voice recorder. The problem is when using filenames with ampersands (&). Even when quoting the input, anything after the & is dropped off, but only when files are dropped onto it; if the filename input is typed on the command line, the script works fine. Before the cmd window closes, I briefly see the rest of the filename with an error saying it is not recognized as a valid command.

Here's my script:

rem Change to drive and directory of input file
%~d1
cd %~p1

rem ffmpeg: mix to one channel, double the volume
%HOMEDRIVE%%HOMEPATH%ffmpeg.exe -i "%~nx1" -ac 1 -vol 1024 "%~n1 fixed%~x1"

pause

Here's what appears on the command line, after dropping "ch17&18.mp3":

C:UserscomputergeeksjwDesktop>C:Userscomputergeeksjwffmpeg.exe -i "ch17" -ac 1 -vol 1024 "ch17 fixed"
[...]
ch17: No such file or directory

In case it matters: I'm using the Windows 8 Developer Preview. Is this causing my problem? Does the same error occur on Windows 7 or earlier?

See Question&Answers more detail:os

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

1 Reply

0 votes
by (71.8m points)

There is a long-standing bug in Windows drag and drop functionality regarding file paths that contain & or ^ but don't contain a <space>.

If a file path contains at least one <space>, then Windows automatically encloses the path in quotes so that it gets parsed properly. Windows should do the same thing if the file path contains & or ^, but it does not.

If you create the following simple batch file and drag files onto it, you can see the problem.

@echo off
setlocal enableDelayedExpansion
echo cmd=!cmdcmdline!
echo %%1="%~1"
pause
exit

The !cmdcmdline! variable contains the actual command that launched the batch file. The batch file prints out the command line and the first parameter.

If you drag and drop a file named "a.txt" you get

cmd=cmd /c ""C:estdrag.bat" C:esta.txt"
%1=C:esta.txt
Press any key to continue . . .

If you disregard the quotes around the entire command you see that there are no quotes around the file argument. There are no special characters, so there is no problem.

Now drag and drop "a b.txt" and you get

cmd=cmd /c ""C:estdrag.bat" "C:esta b.txt""
%1="C:esta b.txt"
Press any key to continue . . .

You can see how Windows detects the space in the name and encloses the file in quotes. Again there is no problem.

Now drag and drop "a&b.txt" and you get

cmd=cmd /c ""C:estdrag.bat" C:esta&b.txt"
%1=C:esta
Press any key to continue . . .

Windows doesn't find a space in the name, so it does not enclose it in quotes. Big problem! Windows passes "C:esta" to the batch file and treats "b.txt" as a second file to be executed after the batch file completes. The hard EXIT command in the batch file prevents any split filenames from executing after the batch. Of course b.txt could never execute. But if the file were named "a&b.bat" and "b.bat" existed, then that could be trouble if the hard EXIT were not in the batch file.

It is possible to drag multiple files onto a batch file, and each one should be passed as a parameter.

The !cmdcmdline! is the only way to reliably access drag and drop arguments. But that will not work if files are passed as normal arguments in a normal call to the batch file.

Below is a batch file that can detect if it was called using drag and drop versus a normal call. (It is not bullet proof, but I think it should work in most situations) It will process each file argument, one at a time, regardless of the type of call. (The process simply echos the file name, but you can substitute whatever processing you want.) If the batch was called using drag and drop then it will do a hard exit to protect against split file names.

@echo off
setlocal disableDelayedExpansion
::
:: first assume normal call, get args from %*
set args=%*
set "dragDrop="
::
:: Now check if drag&drop situation by looking for %0 in !cmdcmdline!
:: if found then set drag&drop flag and get args from !cmdcmdline!
setlocal enableDelayedExpansion
set "cmd=!cmdcmdline!"
set "cmd2=!cmd:*%~f0=!"
if "!cmd2!" neq "!cmd!" (
  set dragDrop=1
  set "args=!cmd2:~0,-1! "
  set "args=!args:* =!"
)
::
:: Process the args
for %%F in (!args!) do (
  if "!!"=="" endlocal & set "dragDrop=%dragDrop%"
  rem ------------------------------------------------
  rem - Your file processing starts here.
  rem - Each file will be processed one at a time
  rem - The file path will be in %%F
  rem -
  echo Process file "%%~F"
  rem -
  rem - Your file processing ends here
  rem -------------------------------------------------
)
::
:: If drag&drop then must do a hard exit to prevent unwanted execution
:: of any split drag&drop filename argument
if defined dragDrop (
  pause
  exit
)

It looks like your existing batch is only designed to handle one file. I can't tell if you need to make modifications to the calls to support multiple files. I modified the above batch to only process the first argument, and substituted your process into the argument processing loop. This is untested, but I think it should work for you.

@echo off
setlocal disableDelayedExpansion
::
:: first assume normal call, get args from %*
set args=%*
set "dragDrop="
::
:: Now check if drag&drop situation by looking for %0 in !cmdcmdline!
:: if found then set drag&drop flag and get args from !cmdcmdline!
setlocal enableDelayedExpansion
set "cmd=!cmdcmdline!"
set "cmd2=!cmd:*%~f0=!"
if "!cmd2!" neq "!cmd!" (
  set dragDrop=1
  set "args=!cmd2:~0,-1! "
  set "args=!args:* =!"
)
::
:: Process the first argument only
for %%F in (!args!) do (
  if "!!"=="" endlocal & set "dragDrop=%dragDrop%"
  rem ------------------------------------------------
  rem - Your file processing starts here.
  rem - Use %%F wherever you would normally use %1
  rem
  rem Change to drive and directory of input file
  %%~dF
  cd %%~pF
  rem ffmpeg: mix to one channel, double the volume
  %HOMEDRIVE%%HOMEPATH%ffmpeg.exe -i "%%~nxF" -ac 1 -vol 1024 "%%~nF fixed%%~xF"
  rem
  rem - Your file processing ends here
  rem -------------------------------------------------
  goto :continue
)
:continue
if defined dragDrop (
  pause
  exit
)

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

...