I am trying to rename 200+ .txt files in a directory with first line of their contents. The files contains IP address in format such as 12.345.678.90. I have found a batch-file which does exactly that, except that the duplicates don't get renamed at all.
I have edited (to my needs), and tested following script on Server2016 and Windows10 Which renames the files but doesn't do anything for duplicates.
#echo off
setlocal EnableDelayedExpansion
rem Multi-thread file rename program
if "%1" equ "Thread" goto ProcessBlock
rem Create the list of file names and count they
cd C:\Renamed_Files
set numFiles=0
(for %%f in (*.txt) do (
echo %%f
set /A numFiles+=1
)) > fileNames.tmp
rem Get number of threads and size of each block
set numThreads=%1
if not defined numThreads (
set /A numThreads=1, blockSize=numFiles
) else (
set /A blockSize=numFiles/numThreads
)
rem Create asynchronous threads to process block number 2 up to numThreads
if exist thread.* del thread.*
for /L %%t in (2,1,%numThreads%) do (
echo %time% > thread.%%t
start "" /B "%~F0" Thread %%t
)
rem Process block number 1
set count=0
for /F "delims=" %%f in (fileNames.tmp) do (
set /p line1=<%%f
ren "%%f" "!line1:~0,40!.txt"
set /A count+=1
if !count! equ %blockSize% goto endFirstBlock
)
:endFirstBlock
rem Wait for all asynchronous threads to end
if exist thread.* goto endFirstBlock
rem Delete the auxiliary file and end
del fileNames.tmp
goto :EOF
rem Process blocks 2 and up (asynchronous thread)
:ProcessBlock
set /A skip=(%2-1)*blockSize, count=0
for /F "skip=%skip% delims=" %%f in (fileNames.tmp) do (
set /p line1=<%%f
ren "%%f" "!line1:~0,40!.txt"
set /A count+=1
if !count! equ %blockSize% goto endBlock
)
:endBlock
del thread.%2
exit
I am hoping to rename .txt files and add () with a number of duplication so the duplicates can still get renamed with the same batch file, (by editing it of course), or a new batch would be needed? any suggestion? or new code is welcome.
Eventually duplicate files can be merged in to one, (as they would be containing the same ip addresses anyway), and then renamed the file to their first line of the content.
With multiple threads, you might have a race condition leading to a collision. The way I would start this is to use the thread number to avoid collisions. I am showing untested example code. Due to the difficulties with parentheses in DOS, I am instead going to use periods and recommend the same to you:
...
:ProcessBlock
set /A skip=(%2-1)*blockSize, count=0
for /F "skip=%skip% delims=" %%f in (fileNames.tmp) do call :ProcessBlockLoop "%%~f" %2
goto :eof
:ProcessBlockLoop
set /p line1=<"%~1"
set "filename=!line1:~0,40!"
:: Check for an existing file.
if not exist "%filename%.txt" goto :ProcessBlockLoopContinue
:: if we get here then there is an existing file.
set DupCnt=1
:ProcessBlockFilenameLoop
if not exist "%filename%.%2.%DupCnt%.txt" (
set filename=%filename%.%2.%DupCnt%
goto :ProcessBlockLoopContinue
)
:: increment our duplicate counter and try again
set /a DupCnt += 1
goto :ProcessBlockFilenameLoop
:ProcessBlockLoopContinue
:: Try the rename. If the rename fails, then reset the variables and loop again.
:: A retry counter should be added to avoid an infinite loop.
ren "%%f" "%filename%.txt" || set DupCnt=1&&set "filename=!line1:~0,40!"&&goto :ProcessBlockFilenameLoop
:: If we're here, the rename should have worked. You could double check again if desired.
set /A count+=1
if %count% equ %blockSize% (
del thread.%2
exit
)
goto :eof
Related
In the below batch file, I am trying to check a directory, which will only contain text files (no sub-directories), for new files added. The script below executes but always displays New file detected. Eventually I will add it to the startup menu so that the directory is checked upon login. There probably is a better way, but I am not too familiar with batchfiles. Thank you :).
Batch
#echo off
:START
cls
set /a Old = 0
set /a New = 0
echo Checking for new annotated files...
for /f "tokens=*" %%P IN ('dir "path/to/directory" /A /b') do (set /a Old += 1)
set Old
echo Checking for new files..
for /f "tokens=*" %%P IN ('dir "path/to/directory" /A /b') do (set /a New += 1)
set New
goto COMPARE
:COMPARE
if %New% gtr %Old% goto NEWF (
goto NEWF
)
else (
goto OLDF
)
:NEWF
echo New File Detected.
echo.
pause
:OLDF
echo Nothing New.
echo.
pause
Since you are looking to see if any new files where created in a directory, you can use xcopy to check for files before x date. Since you did not post a date or time on your post, I will assume it's current date.
You do not need to check for files younger then the date given as you don't need to compare them. All you need to do is check if the files returned as "New" are less then 1. This can be done with the following:
if %New% gtr 0 (goto NEWF) else (goto OLDF)
If you wish to search sub-directoies in the future, you can use the /S switch with xcopy /L /S /D:.
For the pause statement's you had, you would have ran into problem's if the script was to continue (As it had no where to go). To fix this you can simply exit the script using goto :eof. Use echo( instead of echo.
#ECHO OFF
set /a New = 0
:: Gather & edit the date for xcopy.
SET CurrentDate=%date%
SET CurrentDate=%CurrentDate:/=-%
SET CurrentDate=%CurrentDate:* =%
:: Check for files created today.
for /f "delims=" %%i in ('xcopy "path/to/directory" /L /I /D:%CurrentDate%') do (set /a New+=1)
set /a New-=1
goto COMPARE
:COMPARE
if %New% gtr 0 (goto NEWF) else (goto OLDF)
:NEWF
echo New File Detected.
echo(
pause
goto :eof
:OLDF
echo Nothing New.
echo(
pause
goto :eof
store the value in a file old.txt. If this file exist get the value inside and loop true all .txt files and test the value. Here the comented code :
#echo off&cls
setlocal enabledelayedexpansion
:: Default value
set "New=0"
set "old=0"
:: If exist old.txt get the real value
if exist old.txt set /p Old=<old.txt
echo Checking for new annotated files...
::Count of the txt files
for /f %%$ IN ('dir *.txt') do set /a New+=1
:: Update the value of the old.txt for the next check
echo !New!>old.txt
:: Testing the 2 values
if !New! gtr %Old% (goto NEWF) else (goto OLDF)
:NEWF
echo New File Detected.
echo.
pause
exit/b
:OLDF
echo Nothing New.
echo.
pause
exit/b
I have a folder with files and a .txt file with a list of file names and number of copies that I need to copy from one folder to another.
The script is copying the files but if the .txt file has two files of the same name it overwrites the old file.
In the list I have:
file1.txt 1
file2.txt 1
file1.txt 3
file2.txt 2
I want the achieve the following:
file1.txt
file2.txt
file1(1).txt
file1(2).txt
file1(3).txt
file2(1).txt
This is the code I have so far:
#echo off
set Source=C:\Users\siddique.gaffar\Desktop\Artworks
set Target=C:\Users\siddique.gaffar\Desktop\Artworks Copy
set FileList=C:\Users\siddique.gaffar\Desktop\Artwork TXT File\Book1.txt
echo.
if not exist "%Source%" echo Source folder "%Source%" not found & goto Exit
if not exist "%FileList%" echo File list "%FileList%" not found & goto Exit
if not exist "%Target%" md "%Target%"
for /F "delims=" %%a in ('type "%FileList%"') do copy "%Source%\%%a" "%Target%"
:Exit
echo.
echo press the Space Bar to close this window.
pause > nul
The following should do the trick:
#echo off
set Source=C:\Users\siddique.gaffar\Desktop\Artworks
set Target=C:\Users\siddique.gaffar\Desktop\Artworks Copy
set FileList=C:\Users\siddique.gaffar\Desktop\Artwork TXT File\Book1.txt
echo.
if not exist "%Source%" echo Source folder "%Source%" not found & goto Exit
if not exist "%FileList%" echo File list "%FileList%" not found & goto Exit
if not exist "%Target%" md "%Target%"
for /F "usebackq tokens=1-2" %%a in ("%FileList%") do call :CopyFile "%%a" %%b
:Exit
echo.
echo press the Space Bar to close this window.
pause > nul
exit /b 0
:CopyFile
:: first argument = filename
:: second argument = number of copies
REM A little trick that will put limit on 0 if second argument is empty or not a number
set secondarg=%~2
set /a limit=secondarg
REM if limit is invalid (not strict positive), exit the function
IF %limit% LEQ 0 (
echo Invalid number of copies
exit /b 1
)
IF NOT EXIST "%Target%\%~1" (
copy "%Source%\%~1" "%Target%"
IF %limit% LEQ 1 exit /b 0
set /a limit-=1
)
REM File already exists: search correct index for filename
set index=0
set "targetfile=%target%\%~n1"
set file_ext=%~x1
:following
set /a index+=1
Rem if file with index %index% already exists, go back to get following index
IF exist "%targetfile%(%index%).%file_ext%" goto :following
Rem we have the correct index, now we can copy
set /a limit=index+limit-1
FOR /L %%g IN (%index%,1,%limit%) DO copy "%Source%\%~1" "%targetfile%(%%g).%file_ext%"
exit /b 0
Another option if you have long filenames is the use of usebackq and surrounding the path with double quotes in the for f loop instead of analyzing the output of the type command.
The function :CopyFile checks the existence of the file with an IF EXIST and uses a counter to find the next index for the filename of the new file. It uses path manipulation to construct a new filename with the index.
EDIT: I've added the possibility to read the number of copies needed from the textfile and specify that number as second argument to the :CopyFile function. If no number is given or the number is not strict positive (greater than 0), it won't make a copy.
PS: the "little trick" I used that will set %limit% to 0 in case the second argument is empty works because set with the /a flag will replace empty variables with 0. This won't work if you use argument variables directly though:
set /a limit=%~2
will throw an error if the second argument is empty because the cmd parser will substitute %~2 with an empty string and it will execute set /a limit= which is an invalid assignement using the /a flag. But if you use an extra variable as transit:
set var=%~2
set /a limit=var
you'll let set handle the variable expansion and not the cmd interpreter. The set will see that the var variable is empty (in the case %2 is empty) and will replace it with 0.
#echo off
set "Source=C:\Users\siddique.gaffar\Desktop\Artworks"
set "Target=C:\Users\siddique.gaffar\Desktop\Artworks Copy"
set "FileList=C:\Users\siddique.gaffar\Desktop\Artwork TXT File\Book1.txt"
setlocal EnableDelayedExpansion
for /f "delims=" %%a in ('type "%FileList%"') do (
if not defined count set "count=1"
cd "%Target%"
if exist %%a (
set "curfile=%%a"
set "curfile=!curfile:~0,-4!"
set "curfile=!curfile!(!count!).txt"
if exist !curfile! (
set /a "count=!count! + 1"
set "curfile=%%a"
set "curfile=!curfile:~0,-4"
set "curfile=!curfile!(!count!).txt"
)
cd "%Source%"
ren "%Source%\%%a" "!curfile!"
xcopy "%Source%\!curfile!" "%Target%"
ren "%source%\!curfile!" "%%a"
) else (
xcopy "%Source%\%%a" "%Target%"
)
)
pause
replace all your code by this code. put this batch file in the same directory wher your files to copy is. p. s.: for now it works only with .txt files.
I was looking for a long time now for an answer to this, learned nice tricks from http://www.dostips.com/DtTutoPersistency.php and http://ss64.com/nt/for_cmd.html sites, but still - don't have a solution to the problem I've encountered in:
I have a BATCH file where I test the existence of specific folder (SendTo folder). In case I couldn't find it by the script - I want the user to enter the path to that folder - and keep the result in the BATCH file.
My narrowed BATCH file ("Some file.bat") looks something like:
#echo off
REM SomeNonsense
:: Win7/Vista
IF EXIST %APPDATA%\Microsoft\Windows\SendTo\NUL (
REM Do something
GOTO :EOF
)
:: WinXP
IF EXIST %USERPROFILE%\SendTo\NUL (
REM Do something
GOTO :EOF
)
:: Else
SET SendPath=
SET /P SendP="Please enter the path to the SendTo Folder:> "
IF EXIST %TMP%\SendPath.txt DEL %TMP%\SendPath.txt
FOR /F "usebackq TOKENS=* DELIMS=" %%A in ("%~0") DO (
ECHO %%A>>%TMP%\SendPath.txt
REM Later I want to change the value of SendPath with SendP,
REM And swap the file back to the original name
)
My problem right now is that the lines of the file actually being interpreted, when I want only to copy the text itself to a temp file (without using COPY, because I want to copy line by line in order to change SendPath value).
Another thing is that empty lines aren't copied.
Any solution?
This do what you want:
#echo off
rem Your previous Win7/Vista, WinXP testings here...
:: Else
call :defineSendPath
if defined SendPath goto continue
SET /P "SendPath=Please enter the path to the SendTo Folder:> "
rem Store the SendPath given into this Batch file:
echo set "SendPath=%SendPath%" >> "%~F0"
:continue
rem Place the rest of the Batch file here...
goto :EOF
rem Be sure that the following line is the last one in this file
:defineSendPath
As a proof of concept
#echo off
setlocal enableextensions disabledelayedexpansion
call :persist.read
if not defined savedValue (
set /p "savedValue=Value to save:" && ( call :persist.write savedValue ) || (
echo Value not set, process will end
exit /b 1
)
)
echo Saved value = [%savedValue%]
goto :eof
:persist.read
for /f "tokens=1,* delims=:" %%a in ('
findstr /l /b /c:":::persist:::" "%~f0"
') do set "%%~b"
goto :eof
:persist.write varName
if "%~1"=="" goto :eof
for %%a in ("%temp%\%~nx0.%random%%random%%random%.tmp") do (
findstr /l /v /b /c:":::persist::: %~1=" "%~f0" > "%%~fa"
>"%~f0" (
type "%%~fa"
echo(
setlocal enabledelayedexpansion
echo(:::persist::: %~1=!%~1!
endlocal
)
del /q "%%~fa"
)
goto :eof
The problem with a batch file that edits itself while running is that it keeps pointers to the character position in the file where the commands are being executed. You can only make changes in lines after the current executing one and this can also generate other problems. So, the safest (not the more elegant nor the fastest) generic approach could be to write the data as comments at the end of the file.
I need batch script to copy all files from one directory to another and rename them all to a default name (ex. NAME54.pdf) and continue counting from destination`s maximum number in name.
I wrote some script but it seems not working:
#echo on
D:
set count=0
for %%a in (scans1\*.*) do (
set /a count+=1
)
set count1=0
for %%b in (scans\*.*) do (
set /a count1+=1
)
for /l %%c in (1,1,%count1%) do (
set /a count+=1
copy D:\scans\*.* D:\scans\NAME%count%.pdf
)
pause
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET /a count=0
for %%c in (%sourcedir%\*.*) do (
CALL :select
ECHO copy "%%c" "%destdir%\NAME!count!.pdf"
)
GOTO :EOF
:select
SET /a count+=1
IF EXIST "%destdir%\NAME%count%.pdf" GOTO select
GOTO :eof
I've set up distinct source and destination directories. You would need to change these names to suit your circumstances.
I've chosen to simply ECHO the required copy command so that you can see the resultant commands. You'd need to change ECHO copy to copy to actually execute the commands.
If you append >nul to the copy command, the 1 file(s) copied response will be suppressed.
A slightly archaic question I'm afraid but here goes:
I have a program which produces some .RAW files in a sequence eg.
Example_1.RAW
Example_2.RAW
This then adds extra significant figures to the number as necessary, eg.
Example_10.RAW
Example_200.RAW
I have a need to convert these file names into numbers for running through a batch processor that then produces more files as outputs. I've written a batch file that does the renaming and stores the old and new filenames in a text file, my code is:
#echo off
set i=1
echo Running Batch Rename
for %%f in (*.RAW) do (set file=%%f) & CALL :rename
:rename
echo %i% >>Names.txt
echo %file% >>Names.txt
echo. >>Names.txt
ren %file% %i%.RAW
set /A i+=1
This then renames all my .RAWs as 1.RAW, 2.RAW etc and creates Names.txt with the old and new filenames. Thanks to the way in which DOS processes numbers this does mean that I get a slightly wonky numbering process, ie it will process Ex_1, Ex_10, Ex_100, Ex_101 as 1, 2, 3, 4 which would lead to more work in the post processing of these results in order to get the right results in the right places.
Would it be possible to write another batch file that takes the Names.txt and reverses the process for the output files? So it will take a folder with 1.raw, 1.something, 1.something else, refer to the Names.txt and rename them to Example_1.raw etc?
This should do the job
#echo off
set cnt=0
del Names.txt > nul 2>&1
echo Running Batch Rename
for %%f in (*.RAW) do (
set "file=%%f"
CALL :renameToNumber
)
echo .. Do the jobs ...
rem ** As sample: copy the file
copy *.raw *.some
call :renameBack
exit /b
:renameToNumber
set /A cnt+=1
set "number=00000%cnt%"
set "number=%number:~-4%"
(echo %number% %file%) >> Names.txt
ren "%file%" %number%.RAW
exit /b
:renameBack
for /F "tokens=1,*" %%A in (names.txt) DO (
set "number=%%A"
set "filename=%%~nB"
call ren %%number%%.* "%%filename%%_%%number%%.*"
call echo ren %%number%%.* "%%filename%%_%%number%%.*"
)
exit /b
I was able to adapt the above code, to function within an existing batch routine, by testing for the last file in the directory - using 'if errorlevel' - so that the routine did not exit after the last file was reached but instead returned to my routine.
:: *** Renumber .JPG files ***
setlocal EnableDelayedExpansion
set dir=C:%homepath%\Desktop\Temp
:: Create variable and set to zero
set cnt=0
:: For each file in specified class
for %%f in (%dir%\*.jpg) do (
:: Get name of file
set "file=%%f"
:: Run specified Subroutine
CALL :renameToNumber
)
:renameToNumber
:: Increment variable by 1
set /A cnt+=1
:: Preceed value of variable with 00000
set "number=00000%cnt%"
:: Delete all but final 2 digits
set "number=%number:~-2%"
:: Store new and original name of file
(echo %number% %file%) >> Names.txt
:: Rename file
ren "%file%" %number%.jpg
:: Quit Subroutine if no further files
if errorlevel==1 goto :Continue
:: Loop back to start of Subroutine
exit /b
:Continue
:: *** Zip the image files into an RAR file ***
SET rar=C:\Program Files (x86)\WinRAR\RAR.exe
"%rar%" a -ep -m0 temp.rar "%dir%\*.*"
You can prepare two batch files, one to rename RAW files to 1.RAW, 2.RAW, etc, and a second one to reverse this process back.
Rename script stores original names of RAW files in corresponding txt files:
#echo OFF
#setlocal ENABLEDELAYEDEXPANSION
set I=1
for %%G in (*.RAW) do (
set ORIGINAL_NAME=%%~nG
(
REM Try to rename file
ren "%%G" "!I!.RAW"
) && (
REM Renaming was successful
> "!I!.txt" echo !ORIGINAL_NAME!
set /A I+=1
) || (
REM Renaming was a failure
echo Cannot rename [!ORIGINAL_NAME!.RAW] file.
)
)
#endlocal
And the RenameBack script uses that information to restore names of all corresponding files:
#echo OFF
#setlocal ENABLEDELAYEDEXPANSION
for %%F in (*.txt) do (
set BASENAME=%%~nF
REM Read original name from txt file
for /F %%G in (%%F) do (
REM iterate over all corresponding files
for %%H in (!BASENAME!.*) do (
set EXTENSION=%%~xH
REM Remove dot from extension string
set EXTENSION=!EXTENSION:~1!
if not "!EXTENSION!" == "txt" (
REM Process files
(
REM try to rename corresponding file to old name
ren "!BASENAME!.!EXTENSION!" "%%G.!EXTENSION!"
) && (
REM Operation was successful - remove TXT file
del /F /Q "%%F"
) || (
REM Something went wrong
echo Cannot restore old name for file [!BASENAME!.!EXTENSION!].
)
)
)
)
)
#endlocal