I came across this article on how to run lua scripts against redis. But this article is geared towards those running on *nix. How can I execute a redis lua script from a windows environment?
Read First:
After doing this and fighting with this script for nearly a week, I decided to try and use one of the java libraries to do the scripting instead. I've created a public repo with that project in it. The benefits are that you are not limited to a ~8000 character input variable and it runs much much faster. I'm going to leave the batch script here for people who absolutely need to do it this way, but I would highly recommend using the java code instead:
Redis Scripting Project
Actual Answer:
Using a batch file I was able to replicate the bash script from that article.
#echo off
setlocal EnableDelayedExpansion
echo Starting removal of keys from redis.
echo KeyMatch: %1
echo Field: %2
echo Script: %3
echo Host: %4
echo Port: %5
REM set the cursor to 0 to begin iterating over matching keys
set cursor=0
:loop
REM call redis scan and output the result to temp.txt
call redis-cli -h %4 -p %5 scan !cursor! match %1 count 180 > temp.txt
REM set the first line of the temp file to the new cursor variable
set /p cursor=<temp.txt
REM outer loop variables
set /A i=0
set keyString=
REM loop through the text file to build the key string
for /F "usebackq delims=" %%a in ("temp.txt") do (
set /A i+=1
REM if we are not on the first line save the key to a space delimted string
if NOT !i! == 1 (
call set keyString=!keyString! %%a
)
)
rem if there is only one line in the file skip the script execution
if !i! LEQ 1 (
goto :checkCursor
)
rem check that the length of keyString will not likely violate the 8192 character limit to command line calls
ECHO !keyString!> strlength.txt
FOR %%? IN (strlength.txt) DO ( SET /A strlength=%%~z? - 2 )
if !strlength! GTR 8000 (
echo.
echo.
echo ****Error processing script. Key string is too long. Reduce the count in call to scan.****
echo.
echo.
GOTO :end
)
REM call the script with the keys from the scan task, output to result.txt to prevent writing to the command line each iteration.
call redis-cli -h %4 -p %5 --eval %3 !keyString:~1! , %2 > result.txt
REM output '.' to the commandline to signify progress
<nul set /p=.
:checkCursor
if not !cursor!==0 (
goto :loop
)
:end
set fileToDelete=temp.txt
if exist !fileToDelete! del /F !fileToDelete!
set fileToDelete=result.txt
if exist !fileToDelete! del /F !fileToDelete!
set fileToDelete=strlength.txt
if exist !fileToDelete! del /F !fileToDelete!
echo Completed script execution
endlocal
you can call this script from the command line like:
batchScriptName keyMatch field luaScriptName.lua host port
batchScriptName myKey* us luaScriptName.lua localhost 6379
If your batch script is not on your path then you will have to call the command from the directory where your file is located. Also with the lua scripts you will need to give the full file path reference or call the batch script from the directory where the lua script is located.
This script is set up to work with hashed values in redis. If you need to alter that, you will likely want to change this line:
call redis-cli -h %4 -p %5 --eval %3 !keyString:~1! , %2 > result.txt
The '%2' passes in the field value to the ARGV array in the lua script and you can remove this if you do not need it. You can also add additional ARGV parameters as needed.
Related
I'm trying to write a Windows batch file that uses ffmpeg to convert whole folders with old *.flv videos into *.mp4 videos.
The batch file more or less works, but I want to do some test before deleting the source file. One of these test is that the output file should be at least 2/3 of the original file, but I can't get it to work.
Here's my bat file (with all the debugging echo lines included):
#echo off
setlocal EnableExtensions EnableDelayedExpansion
::-------------------------------------------------------------------------------------
:: get options and folder path
set opzione=%~1%
set cartella=%~2%
:: who's who?
if "%opzione:~3,1%"=="" (
echo.
) else (
if "%opzione:~0,1%"=="/" (
echo.
) else (
set opzione=%~2%
set cartella=%~1%
)
)
::echo.
::echo Cartella = %cartella%
::echo Opzione = %opzione%
::echo.
::-------------------------------------------------------------------------------------
:Check_path
set FLV_FOLDER="%cartella%"
if %FLV_FOLDER% == "" (
echo ... Invalid
goto :uscita
) else (
echo ... OK.
)
::-------------------------------------------------------------------------------------
:Check_Options (STILL W.I.P.)
set Lista=0
set Convert=0
set Delete=0
if "%opzione%"=="/c" (set Convert=1)
if "%opzione%"=="/l" (set Lista=1)
if "%opzione%"=="/d" (set Delete=1)
::echo Lista = %Lista%
::-------------------------------------------------------------------------------------
:Loop_path
#cls
echo Looping all .flv files in %FLV_FOLDER%...
for /R %FLV_FOLDER% %%a IN (*.flv) do call :Converting_Function "%%a"
goto :uscita
::-------------------------------------------------------------------------------------
:Converting_Function
set infile="%~1"
set outfile="%~dpn1.mp4"
set outsize=0
set insize=0
set minsize=0
if not %Lista%==0 goto :just_list
echo Converting %infile% to %outfile%
ffmpeg -v error -i %infile% -c copy -copyts %outfile%
::....................CHECKS........................................................
echo Errors from ffmpeg?
if errorlevel 1 goto :error_ffmpeg
echo Do the outfile exist?
if not exist %outfile% goto :error_exist
echo Is outfile big enough?
:: (say yes if outfile size > infile size*2/3)
for /f %%S in (%outfile%) do set "outsize=%%~zS"
echo %outfile% size is %outsize%
for /f %%S in (%infile%) do set insize=%%~zS
echo %infile% size is %insize%
set /A "minsize=(%insize%*3)/2"
echo minsize is %minsize%
if not %outsize% GTR %minsize% goto :error_size
ren "%~1" "%~n1.todelete"
:: del /q %infile%
goto :eof
:error_ffmpeg
echo Convertion error
pause
if exist %outfile% del /q %outfile%
goto :eof
:error_exist
echo %outfile% does not exist
pause
goto :eof
:error_size
echo Size of %outfile% is 0
pause
goto :eof
:just_list
echo %infile%
goto :eof
:uscita
pause
This is the output:
Converting "T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.flv" to "T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.mp4"
[flv # 0000000000577320] Packet mismatch 107347968 1638 1638
Errors from ffmpeg?
Do the outfile exist?
Is outfile big enough?
"T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.mp4" size is
"T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.flv" size is
Operando mancante.
minsize is 0
0 non atteso.
D:\ffmpeg-20170204-b1e2192-win64-static\bin>
Operando mancante means Missing Operand, 0 non atteso means Unexpected 0
Why do I not have the file size in the variables? What is the missing operand?
The environment variables infile and outfile are defined with file name being enclosed in double quotes. That is not recommended as explained in answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? But it is valid and works here as expected.
The command line to get file size of output file
for /f %%S in (%outfile%) do set "outsize=%%~zS"
is processed before execution by Windows command interpreter for example to
for /f %S in ("C:\Path\File Name.mp4") do set "outsize=%~zS"
It can be read on executing in a command prompt window for /? that for /F interprets the set (string between round brackets) as string to process if enclosed in double quotes except the option usebackq is used which is not done here. For that reason FOR splits up the string C:\Path\File Name.mp4 into tokens using space/tab as delimiters and assigns the first token to loop variable S. So assigned to S for the example is C:\Path\File. The file size for this file can't be determined by Windows command interpreter as this file does not exist.
The solution is using FOR without option /F:
for %%S in (%outfile%) do set "outsize=%%~zS"
And the command line to get file size of input file
for /f %%S in (%infile%) do set insize=%%~zS
can be replaced by
set "insize=%~z1"
The help output on running in a command prompt window call /? explains this argument modifier for getting the size of a file passed as first argument to the batch file on calling it.
The help output on running set /? explains that in arithmetic expressions the current values of environment variables can be referenced by specifying the environment variables with just their names without using % or !. This works even within a command block beginning with ( and ending with matching ).
The command line with the arithmetic expression
set /A "minsize=(%insize%*3)/2"
can result on insize not being defined in execution of
set /A "minsize=(*3)/2"
This explains the error message because there is indeed missing the left operand for the multiplication.
The solution is using the arithmetic expression as recommended by help of command SET.
set /A "minsize=(insize*3)/2"
This arithmetic expression never fails on evaluation. In case of environment variable insize is not defined, it is replaced by 0 on evaluation of the arithmetic expression as explained by the help.
See also Debugging a batch file.
And please note that Windows command interpreter supports only arithmetic expressions with 32-bit signed integer values. So video files with a file size of 2 GiB or more cannot be correct processed by your batch code.
How can I execute set of batch files from single batch file in parallel and get the exit code from each. When I use start it executes the batch file in parallel (new cmd window) but don't return the exit code from each. And while using call, I can get the exit code but the batch file execution happens sequentially. I have following code:
ECHO ON
setlocal EnableDelayedExpansion
sqlcmd -S server_name -E -i select_code.sql >\path\output.txt
for /f "skip=2 tokens=1-3 delims= " %%a in ('findstr /v /c:"-" \path\output.txt') do (
echo $$src=%%a>\path1\%%c.prm
echo $$trg=%%b>>\path1\%%c.prm
set param_name=%%c
start cmd \k \path\exec_pmcmd_ctrm.bat workflow_name %%param_name%%
ping 1.1.1.1 -n 1 -w 5000 > nul
set exitcode="%V_EXITCODE%"
echo %exitcode%>>\path\exitcode.txt
)
This executes the exec_pmcmd_ctrm.bat 3 times with different variable in parallel , but I am unable to get the exit code from each execution. I tried using call but then I miss the parallel execution of bat file. Any help in this regard?
First of all, the "exit code" from another Batch file (that ends with exit /B value command) is taken via %ERRORLEVEL% variable (not %V_EXITCODE%). Also, if such a value changes inside a FOR loop, it must be taken via Delayed Expansion instead: !ERRORLEVEL! (and EnableDelayedExpansion at beginning of your program). However, these points don't solve your problem because there is a misconception here...
When START command is used (without the /WAIT switch), a parallel cmd.exe process start execution. This means that there is not a direct way that the first Batch file could know in which moment the parallel Batch ends in order to get its ERRORLEVEL at that point! There is not a "wait for a started Batch file" command, but even if it would exist, it don't solve the problem of have several concurrent Batch files. In other words, your problem can not be solved via direct commands, so a work around is necessary.
The simplest solution is that the parallel Batch files store their ERRORLEVEL values in a file that could be later read by the original Batch file. Doing that imply a synchronization problem in order to avoid simultaneous write access to the same file, but that is another story...
Here is a pure batch-file solution. Basically, this script executes all batch files located in the same directory simultaneously, where the exit code (ErrorLevel) of each one is written to an individual log file (with the same name as the batch file and extension .log); these files are checked for existence; as soon as such a log file is found, the stored exit code is read and copied into a summary log file, together with the respective batch file name; as soon as all log files have been processed, this script is terminated; the exit code of this script is zero only if all the exit codes of the executed batch files are zero too. So here is the code -- see all the explanatory rem remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Collect available scripts in an array:
set /A "INDEX=0"
for %%J in ("%~dp0*.bat" "%~dp0*.cmd") do (
if /I not "%%~nxJ"=="%~nx0" (
set "ITEM=%%~fJ"
call set "$BATCH[%%INDEX%%]=%%ITEM%%"
set /A "INDEX+=1"
)
)
rem // Execute scripts simultaneously, write exit codes to individual log files:
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
start "" /MIN cmd /C rem/ ^& "%%~fJ" ^& ^> "%%~dpnJ.log" call echo %%^^ErrorLevel%%
)
rem // Deplete summary log file:
> "%~dpn0.log" rem/
rem // Polling loop to check whether individual log files are available:
:POLLING
rem // Give processor some idle time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished script,
rem so its log file is availabe, the related array element becomes deleted,
rem so finally, there should not be any more array elements defined: */
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
rem // Suppress error message in case log file is not yet available:
2> nul (
rem // Read exid code from log file:
set "ERRLEV=" & set "FILE="
< "%%~dpnJ.log" set /P ERRLEV=""
if defined ERRLEV (
rem // Copy the read exit code to the summary log file:
set "NAME=%%~nxJ"
>> "%~dpn0.log" call echo(%%ERRLEV%% "%%NAME%%"
rem // Undefine related array element:
set "%%I="
rem // Store log file path for later deletion:
set "FILE=%%~dpnJ.log"
)
rem // Delete individual log file finally:
if defined FILE call del "%%FILE%%"
)
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set $BATCH[ && goto :POLLING
rem // Check individual exit codes and return first non-zero value, if any:
set "ERRALL="
for /F "usebackq" %%I in ("%~dpn0.log") do (
if not defined ERRALL if %%I neq 0 set "ERRALL=%%I"
)
if not defined ERRALL set "ERRALL=0"
endlocal & exit /B %ERRALL%
This approach is quite similar to the one I used in the following Improving Batch File for loop with start subcommand, but there the outputs of simultaneously executed commands are collected.
I've no knowledge regarding Windows batch programming syntax. I have a text file containing user IDs which I need to delete using curl command and for that I need to extract first character of every user ID and then pass to the curl command. I know the curl command which will require two variables:
'UserID' - Read from the text file.
'firstCharacter' - Extracting first character from the User ID.
Below is the code to fetch user IDs from users.txt file:
#echo off
for /f "tokens=*" %%a in (users.txt) do call :processline %%a
pause
goto :eof
:processline
echo %*
goto :eof
:eof
Please help me with extracting the first character from the read User IDs.
Thanks.
The cmd.exe can do a limited amount of string parsing. JosefZ gave you a good place to start.
C:>echo %PROCESSOR_ARCHITECTURE%
AMD64
C:>echo %PROCESSOR_ARCHITECTURE:~0,1%
A
In a batch / command file I was always needing the first x characters or a string and ended up with many functions all doing the same thing but different names, i.e. getFirstChar, getFirstTwoChars, etc.. - so decided to make a generic function where I could pass in the number of characters I needed:
::-- getFirstXChars
:getFirstXChars
set sValIn=%1
set /a iNo=%2
set vWorkVal=%%sValIn:~0,%iNo%%%
call:getFirstX %vWorkVal% vWorkVal2
set %3=%vWorkVal2%
:getFirstX
set %2=%~1
goto:eof
to use the syntax would be
set varToTrim=ABCDEFG
call:getFirstXChars %varToTrim% 2 varToTrimAfter
#echo 1 %varToTrim%
#echo 2 %varToTrimAfter%
pause
result from command file:
1 ABCDEFG
2 AB
I just added to set variable "id" to be %%a, then I used substring notation to get the first character.
Substrings are processed by using :~start,length after the variable name and before the last % in the variable.
#echo off
for /f "tokens=*" %%a in (output.txt) do set id=%%a & call :processline %%a
pause
goto :eof
:processline
echo %id:~0,1%
goto :eof
:eof
I'm trying to create a Windows version of a simple bash script I have, but I cannot seem to find a Windows version of the Unix 'select' command. Is there one?
Here's the script:
#!/bin/bash
echo "Enter the number of the file you want to select:"
select THE_FILE in someDir/*;
do
echo "You picked $THE_FILE ($REPLY)"
# Will do some stuff here.
break;
done
It was very easy to find this Linux example, so I am a bit perplexed that I cannot seem to find a Windows equivalent.
Edit: The select command prompts the user to select a file from a given directory (that's an oversimplification). To clarify, the script from above will produce the following output, assuming there is a subdirectory 'someDir' with only those three text files in it.
Enter the number of the file you want to select:
1) someDir/somefile1.txt
2) someDir/somefile2.txt
3) someDir/somefile3.txt
#? 2
You picked someDir/somefile2.txt (2)
Windows batch commands does not include any equivalent to the select command. So you will have to build your own version.
You will need:
call command. Create a subroutine and reuse it
for command to iterate over the files, or for /f to iterate over the output of another command returning the list of files
For short lists, choice command is more friendly to the user as it is not needed to press enter, but for longer lists or if you don't know the number of files to select, set /p is a better option
Here, just a sample
#echo off
setlocal enableextensions disabledelayedexpansion
call :select "someDir\*" THE_FILE
echo You picked %THE_FILE%
goto :eof
:select mask returnVar
setlocal enableextensions disabledelayedexpansion
rem Configure internal variables
set "fileNumber="
set "maxFiles=-1"
for /f "delims==" %%a in ('2^>nul set f[') do set "%%a="
echo Enter the number of the file you want to select:
rem Search files, show list and create array with file list
rem Using xcopy to get the list of files because it will show
rem relative paths when a relative mask is used as input
for /f "tokens=1,* delims=:" %%a in ('
xcopy "%~1" "%temp%" /l ^| findstr /n /r /c:"[\\\.:]"
') do (
echo %%a^) %%b
set "f[%%a]=%%~fb"
set "maxFiles=%%a"
)
rem Prompt
:select.ask
set /p "fileNumber=#? "
rem Validate input
set /a "fileNumber=fileNumber+0" 2>nul
if %fileNumber% gtr %maxFiles% set "fileNumber=-1"
if %fileNumber% lss 1 set "fileNumber="
if not defined fileNumber (
echo Wrong selection
goto :select.ask
)
rem Retrieve file from array
setlocal enabledelayedexpansion
for %%a in ("!f[%fileNumber%]!") do (
endlocal
set "selectedFile=%%~a"
)
rem Return selection to caller
endlocal & set "%~2=%selectedFile%"
goto :eof
See Choice /? or set /?.
choice /c:yn
If errorelevel 1 if not errorlevel 2 echo Y was chosen
Although a bash select equivalent is not included in Windows Batch, it is very easy to write your own. It may be a subroutine with the exact same parameters of Linux select so you don't need to learn something new in order to use it; this way, the "in" word in second parameter will not be used in the Batch code.
#echo off
call :select THE_FILE in someDir/*
echo You picked %THE_FILE% (%errorlevel%)
goto :EOF
rem Subroutine that emulates Linux's select
:select returnVar in directory
setlocal EnableDelayedExpansion
echo Enter the number of the file you want to select:
rem Show the files and create an array with them
set n=0
for %%a in (%3) do (
set /A n+=1
set file[!n!]=%%a
echo !n!^) %%a
)
rem Get the number of the desired file
:getNumber
set /P "number=#? "
if not defined file[%number%] goto getNumber
rem Return selected file to caller
for /F "delims=" %%a in ("!file[%number%]!") do endlocal & set "%1=%%a" & exit /B %number%
Previous code is straigthforward, but post a comment here if you have any doubt about it. Perhaps the most complex part is the for /F command at last line, that is required to save the value of the !filename[%number%]! in the %%a FOR parameter before execute the endlocal and the assignment to the first parameter, that is the way to return that value to the calling program. If endlocal would be executed first, the !delayed expansion! of the variable will no longer work...
Although this code does not return the path of the selected file, it is very easy to add such feature, but the code will complicate a little.
While a batch-file solution is desired by the OP, it is worth presenting a PowerShell solution that is much more concise:
($files = get-childitem -file someDir\) | % { $i=0 } { ++$i; "$i) $_" }
$reply = read-host -p "Enter the number of the file you want to select"
"You picked $($files[$reply-1]) ($reply)"
$files = get-childitem -file someDir\ collects all files in directory someDir in variable $files
Note that the -file option for restricting child items to files requires PowerShell 3.0 or higher; on earlier versions, pipe to ? { -not $_.PSIsContainer } instead.
% { $i=0 } { ++$i; "$i) $_" } outputs each filename prefixed with its 1-based index.
% is shorthand for the ForEach-Object object cmdlet, which processes a block of code for each input object
{ $i=0 } initializes the index variable (executed once, before iteration)
{ ++$i; "$i) $_" } is executed for each input object, $_; ++$i increments the index, "$i) $_" prints the index followed by ) and a space, followed by the input object's default property, which is the filename in this case. (If you wanted to print the full path instead, for instance, you'd use "$i) $($_.fullname)").
Note how no explicit output (print) command is needed - the results are output to the terminal by default.
$reply = read-host -p "Enter the number of the file you want to select" reads a single line from the terminal with the specified prompt and stores the user's input in variable $reply.
"You picked $($files[$reply-1]) ($reply)" outputs the result; again, no explicit output command is required.
I wrote a batch file to use PngCrush to optimize a .png image when I drag and drop it onto the batch file.
In the what's next section, I wrote about what I thought would be a good upgrade to the batch file.
My question is: is it possible to create a batch file like I did in the post, but capable of optimizing multiple images at once? Drag and drop multiple .png files on it? (and have the output be something like new.png, new(1).png, new(2).png, etc...
Yes, of course this is possible. When dragging multiple files on a batch file you get the list of dropped files as a space-separated list. You can verify this with the simple following batch:
#echo %*
#pause
Now you have two options:
PngCrush can already handle multiple file names given to it on the command line. In this case all you'd have to do would be to pass %* to PngCrush instead of just %1 (as you probably do now):
#pngcrush %*
%* contains all arguments to the batch file, so this is a convenient way to pass all arguments to another program. Careful with files named like PngCrush options, though. UNIX geeks will know that problem :-)
After reading your post describing your technique, however, this won't work properly as you are writing the compressed file to new.png. A bad idea if you're handling multiple files at once as there can be only one new.png :-). But I just tried out that PngCrush handles multiple files just well, so if you don't mind an in-place update of the files then putting
#pngcrush -reduce -brute %*
into your batch will do the job (following your original article).
PngCrush will not handle multiple files or you want to write each image to a new file after compression. In this case you stick with your "one file at a time" routine but you loop over the input arguments. In this case, it's easiest to just build a little loop and shift the arguments each time you process one:
#echo off
if [%1]==[] goto :eof
:loop
pngcrush -reduce -brute %1 "%~dpn1_new%~x1"
shift
if not [%1]==[] goto loop
What we're doing here is simple: First we skip the entire batch if it is run without arguments, then we define a label to jump to: loop. Inside we simply run PngCrush on the first argument, giving the compressed file a new name. You may want to read up on the path dissection syntax I used here in help call. Basically what I'm doing here is name the file exactly as before; I just stick "_new" to the end of the file name (before the extension). %~dpn1 expands to drive, path and file name (without extension), while %~x1 expands to the extension, including the dot.
ETA: Eep, I just read your desired output with new.png, new(1).png, etc. In this case we don't need any fancy path dissections but we have other problems to care about.
The easiest way would probably be to just start a counter at 0 before we process the first file and increment it each time we process another one:
#echo off
if [%1]==[] goto :eof
set n=0
:loop
if %n%==0 (
pngcrush -reduce -brute %1 new.png
) else (
pngcrush -reduce -brute %1 new^(%n%^).png
)
shift
set /a n+=1
if not [%1]==[] goto loop
%n% is our counter here and we handle the case where n is 0 by writing the result to new.png, instead of new(0).png.
This approach has problems, though. If there are already files named new.png or new(x).png then you will probably clobber them. Not nice. So we have to do something different and check whether we can actually use the file names:
rem check for new.png
if exist new.png (set n=1) else (set n=0 & goto loop)
rem check for numbered new(x).png
:checkloop
if not exist new^(%n%^).png goto loop
set /a n+=1
goto checkloop
The rest of the program stays the same, including the normal loop. But now we start at the first unused file name and avoid overwriting files that are already there.
Feel free to adapt as needed.
To do Drag & Drop in a secure way, isn't so simple with batch.
Dealing with %1, shift or %* could fail, because the explorer is not very smart, while quoting the filenames, only filenames with spaces are quoted.
But files like Cool&stuff.png are not quoted by the explorer so you get a cmdline like
pngCr.bat Cool&stuff.png
So in %1 is only Cool even in %* is only Cool, but after the batch ends, cmd.exe tries to execute a stuff.png (and will fail).
To handle this you could access the parameters with !cmdcmdline! instead of %1 .. %n,
and to bypass a potential error at the end of execution, a simple exit could help.
#echo off
setlocal ENABLEDELAYEDEXPANSION
rem Take the cmd-line, remove all until the first parameter
set "params=!cmdcmdline:~0,-1!"
set "params=!params:*" =!"
set count=0
rem Split the parameters on spaces but respect the quotes
for %%G IN (!params!) do (
set /a count+=1
set "item_!count!=%%~G"
rem echo !count! %%~G
)
rem list the parameters
for /L %%n in (1,1,!count!) DO (
echo %%n #!item_%%n!#
)
pause
REM ** The exit is important, so the cmd.ex doesn't try to execute commands after ampersands
exit
Btw. there is a line limit for drag&drop operations of ~2048 characters, in spite of the "standard" batch line limit of ~8192 characters.
As for each file the complete path is passed, this limit can be reached with few files.
FOR %%A IN (%*) DO (
REM Now your batch file handles %%A instead of %1
REM No need to use SHIFT anymore.
ECHO %%A
)
And to differentiate between dropped files and folders, you can use this:
FOR %%I IN (%*) DO (
ECHO.%%~aI | FIND "d" >NUL
IF ERRORLEVEL 1 (
REM Processing Dropped Files
CALL :_jobF "%%~fI"
) ELSE (
REM Processing Dropped Folders
CALL :_jobD "%%~fI"
)
)
This is a very late answer, Actually I was not aware of this old question and prepared an answer for this similar one where there was a discussion about handling file names with special characters because explorer only quotes file names that contain space(s). Then in the comments on that question I saw a reference to this thread, after that and not to my sureprise I realized that jeb have already covered and explained this matter very well, which is expected of him.
So without any further explanations I will contribute my solution with the main focus to cover more special cases in file names with this ,;!^ characters and also to provide a mechanism to guess if the batch file is directly launched by explorer or not, so the old fashion logic for handling batch file arguments could be used in all cases.
#echo off
setlocal DisableDelayedExpansion
if "%~1" EQU "/DontCheckDrapDrop" (
shift
) else (
call :IsDragDrop && (
call "%~f0" /DontCheckDrapDrop %%#*%%
exit
)
)
:: Process batch file arguments as you normally do
setlocal EnableDelayedExpansion
echo cmdcmdline=!cmdcmdline!
endlocal
echo,
echo %%*=%*
echo,
if defined #* echo #*=%#*%
echo,
echo %%1="%~1"
echo %%2="%~2"
echo %%3="%~3"
echo %%4="%~4"
echo %%5="%~5"
echo %%6="%~6"
echo %%7="%~7"
echo %%8="%~8"
echo %%9="%~9"
pause
exit /b
:: IsDragDrop routine
:: Checks if the batch file is directly lanched through Windows Explorer
:: then Processes batch file arguments which are passed by Drag'n'Drop,
:: rebuilds a safe variant of the arguments list suitable to be passed and processed
:: in a batch script and returns the processed args in the environment variable
:: that is specified by the caller or uses #* as default variable if non is specified.
:: ErrorLevel: 0 - If launched through explorer. 1 - Otherwise (Will not parse arguments)
:IsDragDrop [retVar=#*]
setlocal
set "Esc="
set "ParentDelayIsOff=!"
setlocal DisableDelayedExpansion
if "%~1"=="" (set "ret=#*") else set "ret=%~1"
set "Args="
set "qsub=?"
:: Used for emphasis purposes
set "SPACE= "
setlocal EnableDelayedExpansion
set "cmdline=!cmdcmdline!"
set ^"ExplorerCheck=!cmdline:%SystemRoot%\system32\cmd.exe /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" (
set ^"ExplorerCheck=!cmdline:"%SystemRoot%\system32\cmd.exe" /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" exit /b 1
)
set "ExplorerCheck="
set ^"cmdline=!cmdline:*"%~f0"=!^"
set "cmdline=!cmdline:~0,-1!"
if defined cmdline (
if not defined ParentDelayIsOff (
if "!cmdline!" NEQ "!cmdline:*!=!" set "Esc=1"
)
set ^"cmdline=!cmdline:"=%qsub%!"
)
(
endlocal & set "Esc=%Esc%"
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if not defined cmdline endlocal & endlocal & set "%ret%=" & exit /b 0
:IsDragDrop.ParseArgs
if "%cmdline:~0,1%"=="%qsub%" (set "dlm=%qsub%") else set "dlm= "
:: Using '%%?' as FOR /F variable to not mess with the file names that contain '%'
for /F "delims=%dlm%" %%? in ("%cmdline%") do (
set ^"Args=%Args% "%%?"^"
setlocal EnableDelayedExpansion
set "cmdline=!cmdline:*%dlm: =%%%?%dlm: =%=!"
)
(
endlocal
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if defined cmdline goto :IsDragDrop.ParseArgs
if defined Esc (
set ^"Args=%Args:^=^^%^"
)
if defined Esc (
set ^"Args=%Args:!=^!%^"
)
(
endlocal & endlocal
set ^"%ret%=%Args%^"
exit /b 0
)
OUTPUT with sample files dragged and dropped onto the batch file:
cmdcmdline=C:\Windows\system32\cmd.exe /c ""Q:\DragDrop\DragDrop.cmd" Q:\DragDrop\ab.txt "Q:\DragDrop\c d.txt" Q:\DragDrop\!ab!c.txt "Q:\DragDrop\a b.txt" Q:\DragDrop\a!b.txt Q:\DragDrop\a&b.txt Q:\DragDrop\a(b&^)).txt Q:\DragDrop\a,b;c!d&e^f!!.txt Q:\DragDrop\a;b.txt"
%*=/DontCheckDrapDrop "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
#*= "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
%1="Q:\DragDrop\ab.txt"
%2="Q:\DragDrop\c d.txt"
%3="Q:\DragDrop\!ab!c.txt"
%4="Q:\DragDrop\a b.txt"
%5="Q:\DragDrop\a!b.txt"
%6="Q:\DragDrop\a&b.txt"
%7="Q:\DragDrop\a(b&^)).txt"
%8="Q:\DragDrop\a,b;c!d&e^f!!.txt"
%9="Q:\DragDrop\a;b.txt"
In :IsDragDrop routine I specially tried to minimize the assumptions about command line format and spacing between the arguments. The detection (guess) for explorer launch is based on this command line signature %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments"
So it is very possible to fool the code into thinking it has launched by double click from explorer or by drag'n'drop and that's not an issue and the batch file will function normally.
But with this particular signature it is not possible to intentionally launch batch file this way: %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments & SomeOtherCommand" and expect that the SomeOtherCommand to be executed, instead it will be merged into the batch file arguments.
You don't need a batch script to optimize multiple PNGs, all you need is the wildcard:
pngcrush -d "crushed" *.png
That will pngcrush all PNGs in the current dir and move them to a sub-dir named "crushed". I would add the -brute flag to likely shave off a few more bytes.
pngcrush -d "crushed" -brute *.png
I'm posting this because it doesn't seem to be well documented or widely known, and because it may be easier for you in the long run than writing and maintaining a drag and drop batch file.