3

I write a batch script:

@echo off
set "zip=C:\Program Files\7-Zip\7z.exe"
for %%f in (%*) do (
    if exist "%%~f\" (
        "%zip%" a -tzip "%%~f.zip" "%%~f\*" -mx0
    ) else (
        "%zip%" a -tzip "%%~f.zip" %%f -mx0
    )
)

When the user selects multiple files/folders and drag them on to the script file, each file/folder dragged is packed into a zip file.

It works fine in most cases. However, if the files being dragged are located in a directory whose filename contains parentheses, such as "myfolder(large)", the script fails out.

Can anyone tell what causes this issue and how to solve it?

Danny Lin
  • 2,050
  • 1
  • 20
  • 34
  • 3
    It most-likely fails because parenthesis are an operator in batch files. As you see in your `do` statement? It's likely trying to find another operation to do when you use your parenthesized files. Is it absolutely necessary to use the parenthesis? If so, then you need to look into a way to set the values using a `textA = "text(name)"` etc. (or similar code) to make sure the parenthesis are read as a variable, and not another function. This may not be the issue that's happening, but that's the best guess I have. – Ethan Moore May 02 '16 at 17:55
  • 1
    Try replacing `dir /a %%f` with `dir /a "%%~f"`, and in the last zip line, replace `%%f -mx0` with `"%%~f" -mx0`. – rojo May 02 '16 at 18:01
  • @rojo It has been tried and doesn't work. – Danny Lin May 02 '16 at 18:10
  • Please [edit] your question and show calling line to see what the `%*` resolve to. – JosefZ May 02 '16 at 19:56

1 Answers1

6

For this problem I see the following options:

  1. Escaping the parenthesis by ^ using an interim variable:

    @echo off
    set "ZIP=C:\Program Files\7-Zip\7z.exe"
    set "ARGS=%*"
    set "ARGS=%ARGS:^=^^%"
    set "ARGS=%ARGS:&=^&%"
    set "ARGS=%ARGS:(=^(%"
    set "ARGS=%ARGS:)=^)%"
    for %%F in (%ARGS%) do (
        dir /A "%%~fF" | > nul 2>&1 findstr "DIR" && (
            "%ZIP%" a -tzip "%%~F.zip" "%%~F\*" -mx0
        ) || (
            "%ZIP%" a -tzip "%%~F.zip" "%%~fF" -mx0
        )
    )
    

    This method resolves the issue with parentheses ( and ) as well as & and ^, but it is not robust against all characters that have special meanings for batch scripts, like <, > and |.

  2. Enabling and applying delayed expansion for the problematic string:

    @echo off
    set "ZIP=C:\Program Files\7-Zip\7z.exe"
    set "ARGS=%*"
    setlocal EnableDelayedExpansion
    for %%F in (!ARGS!) do (
        endlocal
        dir /A "%%~fF" | > nul 2>&1 findstr "DIR" && (
            "%ZIP%" a -tzip "%%~F.zip" "%%~F\*" -mx0
        ) || (
            "%ZIP%" a -tzip "%%~F.zip" "%%~fF" -mx0
        )
        setlocal
    )
    endlocal
    

    I prefer this method as it solves issues with all special characters. The toggling of delayed expansion inside of the loop (relying on the Windows default where it is disabled) is done to have it disabled during expansion of the %%F variable instances as otherwise problems arise in case the strings contain exclamation marks !. The problem here is that delayed expansion consumes ! encountered on the command line. A for variable like %%F is expanded to its value before delayed expansion is performed; so if the %%F value contains a ! it disappears - unless delayed expansion is disabled, where the ! has no particular meaning.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • Thank you, it seems to work. Could you explain why adding `endlocal` and `setlocal` in the for loop? – Danny Lin May 04 '16 at 12:09
  • This is to avoid loss of exclamation marks, which would occur when expansion of the `%%F` instances happens during delayed expansion was enabled; meanwhile I extended the explanation in the [answer](http://stackoverflow.com/revisions/36990712/4)... – aschipfl May 05 '16 at 01:35
  • 1
    Thank your for the detail explanation. BTW, the `dir /A "%%~fF" | > nul 2>&1 findstr "DIR" && (...) || (...)` if meant to detect whether `%%F` is a folder, and I found a simpler way for it: `if exist "%%~F\" (...) else (...)` ([ref](http://stackoverflow.com/questions/138981/how-do-i-test-if-a-file-is-a-directory-in-a-batch-script)). – Danny Lin May 05 '16 at 16:46
  • Unfortunately, this seems to be broken if a file is `something^with caret and space.txt`. Replace `set "ARGS=%*"` with `set ARGS=%*` will get it work right. But for `something^with^caret.txt` (no space contained in the full path), both ways fail. – Danny Lin Nov 30 '17 at 04:45
  • How do you pass such arguments to the batch script? such arguments must be quoted (`""`) as otherwise the command line parser gets confused... – aschipfl Nov 30 '17 at 10:09
  • As the original question has mentioned, it's a batch file intentionally designed to run by dragging files into it (or by selecting files and run with it), the arguments are the absolute file paths generated by Windows. It seems that a pair of double quotes is not auto-generated for a file argument whose full path contains no space char. – Danny Lin Nov 30 '17 at 12:33
  • Yes, when dragging and dropping files onto a batch script, the command interpreter puts auto-quotes only in case there are spaces, as far as I know; to me this appears as a terrible design flaw; unfortunately, this cannot be solved in the batch file as it receives the paths already in a wrong way... – aschipfl Nov 30 '17 at 13:05
  • Can this issue be bypassed if we use PowerShell or VBScript? (Of course, it needs to support drag&drop execution like this batch file does.) – Danny Lin Nov 30 '17 at 14:28
  • I don't know exactly as I never tried, but I believe both options should be able to handle that, because they came up on Windows, whereas batch scripting has got its roots in MS-DOS times where drag-and-drop was science-fiction and there were 8.3 file names only anyway... – aschipfl Nov 30 '17 at 19:06