The error is being emitted by FOR, which has even stricter escaping requirements for special characters than the rules for filenames.
You can't quote the %* because the interpreter doesn't consider %* as an list it can iterate over, but as a single string comprised of the numbered arguments, as they were passed to the script. The interpreter is substituting the string literally, and so FOR is seeing
for %%i in (fig(1).jpg) do move "%%~i" "somewhere\%%~nxi"
^ ^
The closing-bracket in the filename is actually closing what FOR thinks is the list, leaving an extra .jpg) at the end, which the interpreter doesn't understand.
Thankfully, the string-manipulation features that you are using in your FOR variables are also available to numbered args (%1, %2, etc.) so instead of looping over a badly-parsed list, you can instead use a real list (of arguments) and iterate using a :label and a GOTO:
@ECHO OFF
:loop_args
IF NOT "{%~1}"=="{}" (
MOVE "%~1" "somewhere\%~nx1"
SHIFT
GOTO :loop_args
)
Essentially, all the time the first argument %~1 is not empty, we perform three actions:
- move the file to its string-manipulated path
- pop the first argument off the list (moving the second to the first, third to second, etc)
- return to the label
When there are no more arguments, the first argument becomes empty, so the interpreter moves on to the next statement following the closing-bracket ), or quits if there are no more statements.
Note that you can write this short-hand, if it reads better for you, by separating the three commands with a &:
:loop_args
IF NOT "{%~1}"=="{}" ( MOVE "%~1" "somewhere\%~nx1" & SHIFT & GOTO :loop_args )
If your action is more complicated than a simple move, put your action in its own "function" using another label:
@ECHO OFF
SETLOCAL
:::: MAIN LOGIC ::::
ECHO Starting with arguments: %*
ECHO.
:loop_args
IF NOT "{%~1}"=="{}" ( CALL :do_something "%~1" & SHIFT & GOTO :loop_args )
ECHO.
ECHO Finished processing %NUM_CALLS% argument(s)
ENDLOCAL
GOTO :EOF
:::: FUNCTIONS ::::
:do_something
IF DEFINED NUM_CALLS (
ECHO.
ECHO.*******
ECHO.
SET /a NUM_CALLS += 1
) ELSE (
SET NUM_CALLS=1
)
ECHO You passed the value ^[ %1 ^]
ECHO Unquoted, this reads ^[ %~1 ^]
ECHO This resolves to the file: %~f1
ECHO Here is just the filename: %~n1
ECHO Here is it's extension: %~x1
ECHO How about a size, too^?: %~z1
IF EXIST "%~f1" (
ECHO Here is where it lives...
DIR "%~dp1"
)
GOTO :EOF
When you CALL a function, you can pass arguments to it, which the interpreter will store in its own set of numbered variables, and you can manipulate them as you can the FOR variables. When the function finishes, you get back the numbered variables that you had in the caller.
Unlike the single IF statement, you can manipulate environment variables inside a function. These changes are global to the script unless you call SETLOCAL at the beginning of the function and ENDLOCAL at the end.
Note the use of GOTO :EOF which is a special label which marks the end of the function or the end of the script, if you are not currently running a function. In particular, note the one between the main logic and the function -- this stops the interpreter running the code inside the function after it finishes the main routine.
For both approaches, also note that you can only iterate over an argument list once, as once each argument has been SHIFTed out of the list, it is gone. You would need to employ a different array technique if you wanted to retain them, but that is a problem for another question.