batch file calling to function fail - windows

been trying to figure how to make the random string generation as a function, then call to function to return new random for each for loop.
but unable to make it work...
blank of idea, batch programming seems lot more complicated than web..
#echo off
GOTO :MAIN
:TestFunc
set orig=%1
set %~2=%random%
goto :eof
:MAIN
for %%a in (C:\folder\*.png) do (
set /a count+=1
set "fname=%%~a"
setlocal enabledelayedexpansion
set param_to_function=LetItBeA
call :TestFunc %param_to_function% return_value
set random=%return_value%
echo !random!
echo !fname!
ren "!fname!" img_%date:~10,4%-%date:~4,2%-%date:~7,2%_%HR%%time:~3,2%-!random!.png
endlocal
)
goto :eof

Original question answered (before edit). Explanation:
Use for /F against dir /b as unlike for /F, for starts parsing files immediately so it could get a renamed file again and again...
File renaming treated in a subroutine with test on file existence before ren.
Note endlocal&set "%1=%_RndAlphaNum%"&goto :eof tricky part how-to return a value to a variable (parameter) passed by reference.
ren command echoed merely for debugging purposes.
Resources (required reading):
An A-Z Index of the Windows CMD command line
Windows CMD Shell Command Line Syntax
The script:
#ECHO OFF
SETLOCAL enableextensions disabledelayedexpansion
Set /A "_RNDLength=6"
Set "_Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
set "_folder=C:\folder"
for /F "delims=" %%a in ('dir /B "%_folder%\*.png"') do (
set /a count+=1
set "fname=%%~nxa"
call :renameFile
)
ENDLOCAL
goto :eof
:renameFile
call :getRandomString _RndANum
set "newFileName=img_%date:~10,4%-%date:~4,2%-%date:~7,2%_%HR%%time:~3,2%-%_RndANum%.png"
if exist "%_folder%\%newFileName%" goto :renameFile
echo ren "%_folder%\%fname%" "%newFileName%"
goto :eof
:getRandomString
rem usage
rem call :getRandomString varname
rem %1 = a variable name (pass by reference)
Setlocal EnableDelayedExpansion
Set "_Str=%_Alphanumeric%987654321"
:_LenLoop
IF NOT "%_Str:~18%"=="" (
SET _Str=%_Str:~9%
SET /A "_Len+=9"
GOTO :_LenLoop
)
SET _tmp=%_Str:~9,1%
SET /A _Len=_Len+_tmp
Set _count=0
SET _RndAlphaNum=
:_loop
Set /a _count+=1
SET _RND=%Random%
Set /A _RND=_RND%%%_Len%
SET _RndAlphaNum=!_RndAlphaNum!!_Alphanumeric:~%_RND%,1!
If !_count! lss %_RNDLength% goto _loop
rem do not split next line
endlocal&set "%1=%_RndAlphaNum%"&goto :eof

Related

Move files into folders based on their names

I have some files in the form:
filename1 1.ext
filename1 2.ext
filename1 3.ext
...
filename2 1.ext
filename2 100.ext
...
filename20 1.ext
filename20 15.ext
(etc.)
...where filename can contain spaces.
And I want to move them to folder filename1, filename2, etc., respectively.
I know I can do a for loop for %%i in (*.ext) do and remove the extension with set folder=%%~ni. So what I am missing is how to remove everything after the space just before the number, and get only filename1, for example.
I also know I can split variable folder, but in this case I do not know by at which character I need to split, although I know it will be a space followed by a number.
So basically, I want something like this:
#echo off
set folder=
for %%i in (*.ext) do set folder=%%~ni & set folder=getfoldernamefromvariablefoldersomehow & mv %%i %folder%
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*.ext" '
) DO (
CALL :sub1 "%%a" %%a
)
GOTO :EOF
:sub1
SET "filename=%~1"
:subloop
SHIFT
SET "numname=%~1"
IF NOT "%~2"=="" GOTO subloop
CALL SET "dirname=%%filename: %numname%=%%
ECHO( MD "%sourcedir%\%dirname%" 2>nul
ECHO( MOVE "%sourcedir%\%filename%" "%sourcedir%\%dirname%\%numname%"
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories.
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
Perform a directory list of the required files in basic form without directorynames. Send the full fulename in quotes and without to the subroutine sub1.
In the subroutine, save the source filename in filename then shift each parameter supplied until there is no second parameter; the value in numname must then be the last or required filename.
Remove numname with a leading space from filename to get the required subdirectoryname, make that subdirectory and move the file.
[edit in the light of comment]
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*.ext" '
) DO (
CALL :sub1 "%%a" %%a
)
GOTO :EOF
:sub1
SET "filename=%~1"
SET "destdirname=%~2"
:subloop
SHIFT
SET "numname=%~1"
IF NOT "%~2"=="" GOTO subloop
CALL SET "dirname=%%filename: %numname%=%%
ECHO( MD "%sourcedir%\%destdirname%" 2>nul
ECHO( MOVE "%sourcedir%\%filename%" "%sourcedir%\%destdirname%\%numname%"
GOTO :eof
It's difficult to scry your intentions when you give no example.
destdirname is set to the second parameter on entering sub1 which will be the first group of characters before the first space.
the md does not need to be gated since the 2>nul will suppress the directory exists error message.
Here is a script that does what you want. It splits off the last SPACE followed by numerals from the file name and uses the remaining string as the name of the destination directory of the movement.
This approach handles all valid characters for file names properly, even ^, &, %, !, ( and ). It can even handle file names that contain SPACE plus numerals plus .ext again correctly.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."
for /F "eol=| delims=" %%F in ('
dir /B "%_SOURCE%\*.ext" ^| findstr /R /I /C:" [0123456789][0123456789]*\.ext$"
') do (
set "FILE=%%F"
call :SPLIT LAST REST "%%F"
setlocal EnableDelayedExpansion
2> nul mkdir "!_TARGET!\!REST!"
ECHO move /Y "!_SOURCE!\!FILE!" "!_TARGET!\!REST!"
endlocal
)
endlocal
exit /B
:SPLIT rtn_last rtn_rest val_string
setlocal DisableDelayedExpansion
set "RES=" & set "STR=%~3"
:LOOP
for /F "tokens=1,* delims= " %%I in ("%STR%") do (
if "%%J"=="" (
set "RES=%%I"
) else (
set "STR=%%J"
goto :LOOP
)
)
set "STR=%~3|"
call set "STR=%%STR: %RES%|=%%"
(
endlocal
set "%~1=%RES%"
set "%~2=%STR:^^=^%"
)
exit /B
After having tested the script, remove the upper-case ECHO command to actually move any files. Unless you remove the /Y option from the move command, files become overwritten without prompt. To suppress summary messages (like 1 file(s) moved.), add > nul to the move command line. Note that any prompt was also hidden then in case you removed the /Y option.
Thank to all of you for your comments. Finally, i was able to get a solution:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "_source=C:\Users\kurok_000\Downloads"
set "_target=C:\Users\kurok_000\YandexDisk\Mangas"
for /f "eol=| delims=" %%f in ('dir /b "%_source%\*.7z"') do (
call :fixNames "%%f" %_source%
)
for /f "eol=| delims=" %%f in ('dir /b "%_source%\*.7z" ^| findstr /r /i /c:" [0123456789][0123456789]*\.7z$"') do (
set "file=%%f"
call :split last rest "%%f"
setlocal EnableDelayedExpansion
2> nul mkdir "!_target!\!rest!"
move /y "!_source!\!file!" "!_target!\!rest!" >nul
echo moved %%f to !_target!\!rest!
endlocal
)
endlocal
exit /b
:split rtn_last rtn_rest val_string
setlocal DisableDelayedExpansion
set "res=" & set "str=%~3"
:loop
for /f "tokens=1,* delims= " %%i in ("%str%") do (
if "%%j"=="" (
set "res=%%i"
) else (
set "str=%%j"
goto :loop
)
)
:quit
set "str=%~3|"
call set "str=%%str: %res%|=%%"
(
endlocal
set "%~1=%res%"
set "%~2=%str:^^=^%"
)
exit /b
:fixNames _file _folder
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FILE=%1"
set "FILE=%file:~1,-1%"
set "folder=%2"
for /F "tokens=1,* delims=0123456789" %%A in ("%FILE%") do (
set filename=!FILE:%%B=!%%~xB
)
if not "%filename%"=="%FILE%" (rename "!folder!\!FILE!" "!filename!")

Second latest folder in a Directory

I want a batch file which will find out which is the second latest folder created/modified in a directory.
I found this article but no matter how much i tried i could not understand how it works
#echo off
set "root_dir=c:\somewhere"
pushd "%root_dir%"
set "bl1="
set "bl2="
setlocal enableDelayedExpansion
for /f "tokens=* delims=" %%# in ('dir /b /a:-d /o:d') do (
set "bl2=!bl1!"
set "bl1=%%#"
)
echo %bl2%
endlocal
If i use it as it is then i can get the second latest folder but this script is supposedly able to get which ever latest folder you need , be it 1st or nth.
Could someone please tell me what modifications need to be done to the script to accomplish that. Also how exactly this script works
In your approach, the latest folder is already available in variable bl1; add echo %bl1% at the end before endlocal to display it. Retrieving the nth folder is simply not possible in a flexible way with that script as you would need to define another variable (say bl3, bl4,..., bln) within the loop.
However, you could reverse the sort order of the output of the dir command by changing the /O option, so it returns the latest (most recent) item first. Then let an index number count the iterations of the loop, and if that index equals the predefined number n, store the currently iterated item:
#echo off
setlocal EnableDelayedExpansion
rem // Define N here to get Nth-latest folder:
set /A LATEST=2
set /A INDEX=0
for /F "eol=| delims=" %%# in ('dir /B /A:D /O:-D "C:\somewhere"') do (
set /A INDEX+=1
if !INDEX! EQU !LATEST! (
set "ITEM=%%#"
)
)
if defined ITEM echo %LATEST%th-latest folder: %ITEM%
endlocal
exit /B
Update
Here is a modified script with the following improvements:
Exclamation marks ! in folder names are no longer lost due to toggling delayed expansion;
the target directory can be provided as the first command line argument; if omitted, the current directory is used;
the number n can be given as the second command line argument; if omitted, the user is prompted for it (this addresses elzooilogico's comment); n defaults to 1 for empty input;
the display output is improved to avoid something weird like 1th-latest, 2th-latest and 3th-latest; instead, The latest, 2nd-latest and 3rd-latest is returned, respectively;
So this is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem /* define location path and folder pattern as 1st command line argument;
rem /* Define number N as 2nd command line argument to get Nth-latest folder. */
set "LATEST=%~2"
set /A LATEST+=0
if %LATEST% LEQ 0 (set /P LATEST="Enter N [1]: " & set /A LATEST+=0)
if %LATEST% LEQ 0 set /A LATEST=1
set /A INDEX=0
for /F "eol=| delims=" %%# in ('dir /B /A:D /O:-D "%~1"') do (
set /A INDEX+=1
setlocal EnableDelayedExpansion
if !INDEX! EQU !LATEST! (
endlocal
set "ITEM=%%#"
goto :SKIP & rem // break loop after having retrieved Nth-latest folder;
) else endlocal
)
:SKIP
setlocal EnableDelayedExpansion
if defined ITEM (
if %LATEST% EQU 1 (echo The latest file: !ITEM!) else (
if %LATEST% EQU 2 (echo 2nd-latest file: !ITEM!) else (
if %LATEST% EQU 3 (echo 3rd-latest file: !ITEM!) else (
echo %LATEST%th-latest file: !ITEM!)))
)
endlocal
endlocal
exit /B
To achieve a similar result as with the simple script on top of this answer, you need to call this script by the following command line, supposing it has been saved as Nth-latest.bat:
Nth-latest.bat "C:\somewhere" 2

Windows batch - nested variables and delayed expansion

newbie here who would appreciate some help. I'm trying to generate to generate random file names (i.e. for each file in the source directory, a different random alphanumeric figure should be generated)
The function that generates the random alphanumeric name works but when the value is returned to the main routine, it remains the same across all the files. Not sure where the problem is but I suspect it has something to do with delayed expansion.
#Echo Off
Setlocal EnableDelayedExpansion
dir "D:/Source" /b > List.txt
FOR /F %%i in (List.txt) DO (
CALL:Alpha Numb
echo !Numb!
md D:\Output\%%~ni
rar a -v50M -hpabc123 -m0 -ep "D:\Output\%%~ni\!Numb!.rar" "D:\Source\%%i"
)
goto:eof
:Alpha
Setlocal EnableDelayedExpansion
Set _RNDLength=40
Set _Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
Set _Str=%_Alphanumeric%987654321
:_LenLoop
IF NOT "%_Str:~18%"=="" SET _Str=%_Str:~9%& SET /A _Len+=9& GOTO :_LenLoop
SET _tmp=%_Str:~9,1%
SET /A _Len=_Len+_tmp
Set _count=0
SET _RndAlphaNum=
:_loop
Set /a _count+=1
SET _RND=%Random%
Set /A _RND=_RND%%%_Len%
SET _RndAlphaNum=!_RndAlphaNum!!_Alphanumeric:~%_RND%,1!
If !_count! lss %_RNDLength% goto _loop
REM Echo %_RndAlphaNum%
ENDLOCAL & SET "%~1=%_RndAlphaNum%"
Exit /b

Mass renaming of files?

I have a folder with 46 different text files, from 001.txt to 046.txt and I need to add another file to, say, spot 30. Is there a ways to rename all the files from 030.txt until 046.txt up by one number, so there is an empty spot for the new 030.txt? (Operating on Windows 7)
You can use PowerShell, which is build-in in Windows 7:
46..30|Rename-Item -Path {'{0:000}.txt'-f$_} -NewName {'{0:000}.txt'-f($_+1)}
#ECHO Off
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
SET "insertat="
SET /p "insertat=Insert at which number ? "
IF NOT DEFINED insertat GOTO :EOF
SET /a insertat1=1%insertat%
SET /a howmany=1
SET /p "howmany=Insert How many ? [%howmany%]"
IF "%howmany%"=="0" GOTO :EOF
FOR /f "delims=" %%a IN (
'dir /b /o-n /a-d "%sourcedir%\*.txt" '
) DO (
CALL :isnum %%~na
IF NOT DEFINED notnumber SET /a maxnum=1%%~na&GOTO insert
)
ECHO maxnum NOT found
GOTO :eof
:insert
SET /a newnum=maxnum + howmany
IF EXIST "%sourcedir%\%maxnum:~1%.txt" ECHO(REN "%sourcedir%\%maxnum:~1%.txt" %newnum:~1%.txt
SET /a maxnum -=1
IF %maxnum% GEQ 1%insertat% GOTO insert
GOTO :EOF
:: Determine whether %* is purely numeric
:isnum
SET "notnumber=%~2"
IF DEFINED notnumber GOTO :EOF
SET "notnumber=9%~1"
FOR /l %%z IN (0,1,9) DO CALL SET "notnumber=%%notnumber:%%z=%%"
GOTO :eof
I may as well post this regardless.
It automatically locates the highest filenumber and allows any number of slots to be inserted (default of 1 for Enter)
You would need to change the setting of sourcedir to suit your circumstances.
The required REN commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(REN to REN to actually rename the files.
Here is a simple pure batch solution
#echo off
setlocal enableDelayedExpansion
set increment=1
set "start=030"
for /f %%F in (
'dir /b /a-d /o-n ???.txt^|findstr /rx "[0-9][0-9][0-9]\.txt"'
) do if "%%~nF" geq "%start%" (
set /a new=1%%~nF+increment
ren %%F !new:~1!%%~xF
)

return variable from subroutine, not working... why?

In order to store multiple lines inside a variable from a text file, I am using the answer from this question: https://stackoverflow.com/a/10624240/1513458
Using this script inline and parsing large files gave me a maximum setlocal error. So to counter this, I put the script in a subroutine, and called it from the line(s) i needed it in.
An echo at the bottom of the subroutine before it terminates would output the expected result fine... but back at the top where it was originally called, the variable is empty. Why is that?
Here's a shortened version of the script:
setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile %sourcefile%
echo !insert!
goto :EOF
:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ %SOURCELOC%\%1.txt"`) do (
set "line=%%a"
SETLOCAL EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /F "delims=" %%p in ("!insert!#L!line!") do (
ENDLOCAL
set "insert=%%p"
)
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
goto :EOF
Thanks so much for all your help. Batch is clearly not meant for me, but i need to tough this one through.
Your :readfile routine has SETLOCAL at the top that localizes environment changes. When a routine ends, there is an implicit ENDLOCAL executed for every active SETLOCAL that was instantiated within the routine. So the environment is restored to the state that existed prior to the call.
There is no need for a called subroutine. Your earlier code must have had SETLOCAL without an ENDLOCAL within the FOR loop. That would cause the max SETLOCAL error. Now your FOR loop has paired SETLOCAL/ENDLOCAL, so no more problem.
It is possible to transport any value accross the ENDLOCAL barrier, but it uses an advanced technique.
Some simple restructuring avoids the need to do that. But be careful - no batch variable can contain more than 8192 characters.
setlocal disableDelayedExpansion
set sourcefile=cc1
set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /f "delims=" %%p in ("!insert!#L!line!") do (
endlocal
set "insert=%%p"
)
)
setlocal EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
echo !insert!
EDIT
Here is a solution that demonstrates returning any value across the ENDLOCAL border (except it does not support carriage return. There is a simple extension that supports carriage return). The routine will give the correct result, regardless whether it was called with delayed expansion enabled or disabled. The technique was developed by DosTips user jeb here:http://www.dostips.com/forum/viewtopic.php?f=3&t=1839, and here: http://www.dostips.com/forum/viewtopic.php?p=6930#p6930. There you will find some explanation as to how it works, but it is not for the faint of heart.
#echo off
setlocal enableDelayedExpansion
set sourceloc=.
set sourcefile=test
set ^"LF=^
^"
call :readFile "%sourcefile%"
echo(!insert!
exit /b
:readFile
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:%%=%%J!"
set "line=!line:*:=!"
for /f "delims=" %%p in ("!insert!%%~L!line!") do (
endlocal
set "insert=%%p"
)
)
setlocal enableDelayedExpansion
if defined insert (
set "insert=!insert:"=%%~K!"
if not defined notDelayed set "insert=!insert:^=^^^^!"
)
if defined insert if not defined notDelayed set "insert=%insert:!=^^^!%" Do not remove!
set "replace=%% """"
for %%L in ("!LF!") do for /f "tokens=1,2" %%J in ("!replace!") do (
endlocal
endlocal
endlocal
set "insert=%insert%" Do not remove!
)
exit /b
try this:
setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile insert
echo %insert%
goto :EOF
:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%1.txt"') do (
set "line=%%a"
SETLOCAL EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /F "delims=" %%p in ("!insert!#L!line!") do (
ENDLOCAL
set "insert=%%p"
)
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
SET "%1=!insert!"
ENDLOCAL
goto :EOF

Resources