How to stop after the script created a number of folders and choose files randomly? - batch-file

#Mofi helped me a lot on this answer of mine
How to organize files in folders?
Now I tried myself to add a feature, it's working, but for the respect of this wonderful job Mofi did, I think, there is a more professional way to do it.
I need the batch to stop after it created a number of folders.
Let's say, I have 100000 .mp4 files in the main directory. The script processes now them all together.
For example, I need to use only 1200 files and group them into 120 folders. I can do that now with FoldersInFolders=120 and FilesInFolder=10. The issue is that after finishing Canal-1 it continues with Canal-2 to Canal-10, etc. until all 100000 MP4 files are moved into directories and I don't want that. I want to stop after 120 folders overall and process only 1200 from 100000 MP4 files which I have in my main folder.
I added the following two lines:
set "MaxTotalFolders=120"
if "!VideosIndex!"=="%MaxTotalFolders%" exit /b 0
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FilesInFolder=10"
set "FoldersInFolders=120"
set "MaxTotalFolders=120"
for %%# in ("%USERPROFILE%\Videos" "D:\Videos" "F:\Temp\Videos" "\\MyNAS\Videos") do (
pushd %%# 2>nul
if not errorlevel 1 (
if not exist "*!*.mp4" (
setlocal EnableDelayedExpansion
set "FileCount=%FilesInFolder%"
set "CanalIndex=0"
set "VideosIndex=%FoldersInFolders%
for /F "eol=| delims=" %%I in ('dir *.mp4 /A-D /B /ON 2^>nul') do (
if !FileCount! == %FilesInFolder% (
set FileCount=0
if !VideosIndex! == %FoldersInFolders% (
set /A CanalIndex+=1
set VideosIndex=1
) else set /A VideosIndex+=1
set "TargetFolder=Canal-!CanalIndex!\Videos-!VideosIndex!"
md "!TargetFolder!" 2>nul
if "!VideosIndex!"=="%MaxTotalFolders%" exit /b 0
)
move /Y "%%I" "!TargetFolder!\" >nul
set /A FileCount+=1
)
endlocal
) else (
echo/
echo ERROR: Moving video files not possible because of file names with ! in name.
echo/
echo Please rename first the following files(s^) in: %%#
echo/
dir *!*.mp4 /A-D /B
echo/
pause
)
popd
) else (
echo/
echo ERROR: Failed to change to directory: %%#
echo/
pause
)
)
endlocal
Another feature that would be very helpful for me is to choose the MP4 files randomly every time I run the batch file.

The first batch file is without random selection of the MP4 files.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FilesInFolder=10"
set "FoldersInFolders=50"
set "MaxTotalFolders=120"
for %%# in ("%USERPROFILE%\Videos" "D:\Videos" "F:\Temp\Videos" "\\MyNAS\Videos") do call :MoveVideos %%#
goto EndBatch
:MoveVideos
pushd %1 2>nul
if errorlevel 1 (
echo/
echo ERROR: Failed to change to directory: %1
echo/
pause
goto :EOF
)
if exist "*!*.mp4" (
echo/
echo ERROR: Moving video files not possible because of file names with ! in name.
echo/
echo Please rename first the following files(s^) in: %1
echo/
dir *!*.mp4 /A-D /B
echo/
popd
pause
goto :EOF
)
setlocal EnableDelayedExpansion
set "FileCount=%FilesInFolder%"
set "CanalIndex=0"
set "FolderCount=-1"
set "VideosIndex=%FoldersInFolders%
for /F "eol=| delims=" %%I in ('dir *.mp4 /A-D /B /ON 2^>nul') do (
if !FileCount! == %FilesInFolder% (
set FileCount=0
if !VideosIndex! == %FoldersInFolders% (
set /A CanalIndex+=1
set VideosIndex=1
) else set /A VideosIndex+=1
set /A FolderCount+=1
if !FolderCount! == %MaxTotalFolders% goto EndMove
set "TargetFolder=Canal-!CanalIndex!\Videos-!VideosIndex!"
md "!TargetFolder!" 2>nul
)
move /Y "%%I" "!TargetFolder!\" >nul
set /A FileCount+=1
)
:EndMove
endlocal
popd
goto :EOF
:EndBatch
endlocal
There is used a subroutine with name MoveVideos to be able to use goto :EOF to stop moving files in the current directory into subdirectories on reaching the maximum number of total folders as defined with MaxTotalFolders and counted with FolderCount inside the second FOR loop.
This batch file creates with the three values defined at top for a directory with the MP4 files Video001.mp4 to Video1203.mp4 a directory structure with following files:
Canal-1
Videos-1
Video0001.mp4
:
Video0010.mp4
:
Videos-50
Video0491.mp4
:
Video0500.mp4
Canal-2
Videos-1
Video0501.mp4
:
Video0510.mp4
:
Videos-50
Video0991.mp4
:
Video1000.mp4
Canal-3
Videos-1
Video1001.mp4
:
Video1010.mp4
:
Videos-20
Video1191.mp4
:
Video1200.mp4
Video1201.mp4
Video1202.mp4
Video1203.mp4
This first batch file took 1.58 seconds for the file moving task on my PC.
The second batch file is with random selection of the MP4 files.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FilesInFolder=10"
set "FoldersInFolders=50"
set "MaxTotalFolders=120"
for %%# in ("%USERPROFILE%\Videos" "D:\Videos" "F:\Temp\Videos" "\\MyNAS\Videos") do call :MoveVideos %%#
goto EndBatch
:MoveVideos
pushd %1 2>nul
if errorlevel 1 (
echo/
echo ERROR: Failed to change to directory: %1
echo/
pause
goto :EOF
)
if exist "*!*.mp4" (
echo/
echo ERROR: Moving video files not possible because of file names with ! in name.
echo/
echo Please rename first the following files(s^) in: %1
echo/
dir *!*.mp4 /A-D /B
echo/
popd
pause
goto :EOF
)
setlocal EnableDelayedExpansion
set "FileCount=%FilesInFolder%"
set "CanalIndex=0"
set "FolderCount=-1"
set "VideosIndex=%FoldersInFolders%
set "TotalFileCount=0"
for /F "eol=| delims=" %%I in ('dir *.mp4 /A-D /B /ON 2^>nul') do set /A TotalFileCount+=1
:NextFile
if %TotalFileCount% == 0 goto EndMove
if %TotalFileCount% LEQ 32768 set /A "FileIndex=%RANDOM% %% TotalFileCount" & goto MoveFile
set /A FileGroups=TotalFileCount / 32768
set /A LastFileCount=TotalFileCount %% 32768
set "LastGroupIndex=%FileGroups%"
if not %LastFileCount% == 0 set /A FileGroups+=1
set /A GroupMultiplier=%RANDOM% %% FileGroups
if not %GroupMultiplier% == %LastGroupIndex% (set /A "FileIndex=GroupMultiplier * 32768 + %RANDOM%") else set /A "FileIndex=(FileGroups - 1) * 32768 + (%RANDOM% %% LastFileCount)"
:MoveFile
if not %FileIndex% == 0 (set "SkipValue=skip=%FileIndex% ") else set "SkipValue="
for /F "%SkipValue%eol=| delims=" %%I in ('dir *.mp4 /A-D /B 2^>nul') do (
if !FileCount! == %FilesInFolder% (
set FileCount=0
if !VideosIndex! == %FoldersInFolders% (
set /A CanalIndex+=1
set VideosIndex=1
) else set /A VideosIndex+=1
set /A FolderCount+=1
if !FolderCount! == %MaxTotalFolders% goto EndMove
set "TargetFolder=Canal-!CanalIndex!\Videos-!VideosIndex!"
md "!TargetFolder!" 2>nul
)
move /Y "%%I" "!TargetFolder!\" >nul
set /A FileCount+=1
set /A TotalFileCount-=1
goto NextFile
)
:EndMove
endlocal
popd
goto :EOF
:EndBatch
endlocal
This variant is much slower because of the randomization. It took 18.93 seconds to do the job on my PC.
It produces the same directory tree as the first batch file on current directory containing the MP4 files Video001.mp4 to Video1203.mp4, but which file is moved into which directory is really random.
Here is a third variant also with random selection of the next video file to move.
#echo off
echo %TIME%
setlocal EnableExtensions DisableDelayedExpansion
set "FilesInFolder=10"
set "FoldersInFolders=50"
set "MaxTotalFolders=120"
for /F "delims==" %%I in ('set # 2^>nul') do set "%%I="
for %%# in ("%USERPROFILE%\Videos" "D:\Videos" "F:\Temp\Videos" "\\MyNAS\Videos") do call :MoveVideos %%#
goto EndBatch
:MoveVideos
pushd %1 2>nul
if errorlevel 1 (
echo/
echo ERROR: Failed to change to directory: %1
echo/
pause
goto :EOF
)
if exist "*!*.mp4" (
echo/
echo ERROR: Moving video files not possible because of file names with ! in name.
echo/
echo Please rename first the following files(s^) in: %1
echo/
dir *!*.mp4 /A-D /B
echo/
popd
pause
goto :EOF
)
setlocal EnableDelayedExpansion
set "FileCount=%FilesInFolder%"
set "CanalIndex=0"
set "FolderCount=-1"
set "VideosIndex=%FoldersInFolders%
set "TotalFileCount=0"
for /F "eol=| delims=" %%I in ('dir *.mp4 /A-D /B /ON 2^>nul') do set "#!TotalFileCount!=%%I" & set /A TotalFileCount+=1
:NextFile
if %TotalFileCount% == 0 goto EndMove
if %TotalFileCount% LEQ 32768 set /A "FileIndex=%RANDOM% %% TotalFileCount" & goto MoveFile
set /A FileGroups=TotalFileCount / 32768
set /A LastFileCount=TotalFileCount %% 32768
set "LastGroupIndex=%FileGroups%"
if not %LastFileCount% == 0 set /A FileGroups+=1
set /A GroupMultiplier=%RANDOM% %% FileGroups
if not %GroupMultiplier% == %LastGroupIndex% (set /A "FileIndex=GroupMultiplier * 32768 + %RANDOM%") else set /A "FileIndex=(FileGroups - 1) * 32768 + (%RANDOM% %% LastFileCount)"
:MoveFile
if not %FileIndex% == 0 (set "SkipValue=skip=%FileIndex% ") else set "SkipValue="
for /F "%SkipValue%tokens=1* delims==" %%I in ('set #') do (
if !FileCount! == %FilesInFolder% (
set FileCount=0
if !VideosIndex! == %FoldersInFolders% (
set /A CanalIndex+=1
set VideosIndex=1
) else set /A VideosIndex+=1
set /A FolderCount+=1
if !FolderCount! == %MaxTotalFolders% goto EndMove
set "TargetFolder=Canal-!CanalIndex!\Videos-!VideosIndex!"
md "!TargetFolder!" 2>nul
)
set "%%I="
move /Y "%%J" "!TargetFolder!\" >nul
set /A FileCount+=1
set /A TotalFileCount-=1
goto NextFile
)
:EndMove
endlocal
popd
goto :EOF
:EndBatch
endlocal
It defines for each MP4 video file name an environment variable with a name beginning with # and a number incremented on each file and assigns the current file name to the environment variable. It processes this list of environment variables reduced by one environment variable after each file move.
It causes less file system accesses as the second batch file. But it took 20.54 seconds in my test on my PC to complete the video file movement task.
The main cause of the much longer time with random file selection is the fact that for each file to move one more cmd.exe must be started in background to output the new list of video file names in current directory, capture that file names list, skip all file names up to current random file index number and move the randomly selected file.
It would be much better if the list of environment variables with the video file names in current directory could be updated with removal of the environment variable of a just moved file and check if an environment variable with current randomly determined file index number still exists in the list to move that file and otherwise determine a new random file number index as often as needed. But such an approach can easily result in determining again and again randomly a file index number of video files which were moved already before. The result could be a nearly endless running loop on number of remaining files in environment variables list becomes small. The Windows Command Processor is not designed for such a task.
The randomization is tricky in case of the number of files in a directory is greater than 32768 because of %RANDOM% expands to a random decimal number between 0 and 32767. A cascaded randomized file index number determination is added for that reason to the code of the batch file.
The randomization of the selection of next video file to move causes also a lot of batch file open, seeking to next line to process, processing that line and perhaps read more lines and process them to, remember current position in batch file, batch file close. The first batch file is for that reason much more efficient. A more modern and powerful script interpreter like PowerShell would be much better for that task if randomized movement of video files is an important requirement.
To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
call /?
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
move /?
pause /?
popd /?
pushd /?
set /?
setlocal /?

Related

Batch File String Management in FOR Loop

Am trying to use substring manipulation within a FOR loop and I can't get it to work for love nor money. I've been told that we can't use substring manipulation on a loop variable (%%f etc), so you have to set another variable to equal the loop (set MyVariable=%%f) and then use a substring option to work on this decendent (set MyOtherVar=%MyVariable:~0,-3%). However when echoing these variables only the loop variable is set/non-null.
Here's the code I'm currently using.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref="373137#"
SET OutSuf1="#Window Clean POD "
SET OutSuf2="#1520755.pdf"
SET MatchStr=#mydomain.com
cd c:\Oscar\Scripts\FileNamer\Inbound\
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
c:\Oscar\Scripts\FileNamer\pdftotext.exe -q %%f
set InPdfVer=%%f
set InTxtVer=%InPdfVer:~0,-3%txt
echo Loop Val= %%f
echo InPdfVer= %InPdfVer%
echo InTxtVer= %InTxtVer%
pause
set InAddLine=findstr %MatchStr% %InTxtVer%
set stemp=%InAddLine%
set pos=0
:loop
set /a pos+=1
echo %stemp%|findstr /b /c:"%MatchStr%" >NUL
if errorlevel 1 (
set stemp=%stemp:~1%
if defined stemp GOTO loop
set pos=0
)
set /a pos-=3
call set StoreNo=%InAddLine:~%pos%,-25%
call:getvalue C:\Oscar\Scripts\FileNamer\StoreList.inf %StoreNum% StoreName
set OutFile=%OutPerf%%StoreNo%%OutSuf1%%StoreName%%OutSuf2%
move %%f c:\Oscar\Scripts\FileNamer\Outbound\%OutFile%
)
cd c:\Oscar\Scripts\FileNamer\
exit 0
:getvalue
rem This function reads a value from a file and stored it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to place search result
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof
Hope this makes sense, can try and explain it. Quite likely the bottom part doesnt work either but didnt get that far!
Thanks for any thoughts either way, as a general overview the script is supposed to take PDF files in the incoming folder, convert them to text, search for an email address in that file, look that email address in an external list and then move the PDF file (renaming the file with an aggreed convention in the process) and then move onto the next file, in a loop, until the end of the matching files.
Kind regards,
Oscar
OK so the rest of it seems to what what it should now but I still can't get this substring to set, I just end up with the whole string in the decendent variable. Here's the new code (please excuse the pauses and echos used for troubleshooting).
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref=373137#
SET OutSuf1=#Window Clean POD
SET OutSuf2=#1520755.pdf
SET MatchStr=#mydomain.com
cd c:\Oscar\Scripts\FileNamer\Inbound\
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
c:\Oscar\Scripts\FileNamer\pdftotext.exe -q %%f
set InPdfVer=%%f
call set InTxtVer=!InPdfVer:~0,-3!txt
for /f "tokens=*" %%x in ('findstr !MatchStr! !InTxtVer!') do set InAddLine=%%x
call:getpos
echo !pos!
pause
call set StoreNo=!InAddLine:~!pos!,-25!
call:getvalue C:\Oscar\Scripts\FileNamer\StoreList.inf !StoreNum! StoreName
echo OutPerf !OutPref!
echo StoreNo !StoreNo!
echo OutSuf1 !OutSuf1!
echo StoreName !StoreName!
echo Outsuf2 !OutSuf2!
set OutFile=!OutPerf!!StoreNo!!OutSuf1!!StoreName!!OutSuf2!
echo %%f !OutFile!
pause
REM move %%f c:\Oscar\Scripts\FileNamer\Outbound\!OutFile!
)
cd c:\Oscar\Scripts\FileNamer\
exit /b
:getpos
set stemp=!InAddLine!
set pos=0
:loop
set /a pos+=1
echo !stemp!|findstr /b /c:"!MatchStr!" >NUL
if errorlevel 1 (
set stemp=!stemp:~1!
if defined stemp GOTO loop
set pos=0
)
set /a pos-=3
goto:eof
:getvalue
rem This function reads a value from a file and stored it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to place search result
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof
Thanks all for your inputs, here's the finished script with a few more updates.
Makes use of pdftotext.exe as part of the freeware xpdf suite (please donate as it's a great utility) and in this case some lookup files that help resolve a site number to its description. In the format of
001=My Town
I totally failed to get a workable way to pad 2 digit site codes with a leading 0 to make all sites 3 digits but ended up doing the same thing with another lookup file!
I hope this is of some use to someone else!
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref=373137#
SET OutSuf1=#My Text
SET OutSuf2=#1520755.pdf
SET MatchStr=#mydomain.com
SET BaseDir=C:\myfolder\
SET LogFile=C:\myfolder\FileNamer.log
SET InFolder=C:\myfolder\Inbound\
SET OutFolder=C:\myfolder\Outbound\
SET StoreList=C:\myfolder\StoreList.inf
SET StoreNoConv=C:\myfolder\StoreNoConv.inf
echo Starting Run %TIME% %DATE% >> %LogFile%
echo Starting Run %TIME% %DATE%
cd %InFolder%
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
%BaseDir%pdftotext.exe -q %%f
set "InTxtVer=%%~nf.txt"
for /f "tokens=*" %%x in ('findstr !MatchStr! !InTxtVer!') do set InAddLine=%%x
call:getpos
call set StoreNo=%%InAddLine:~!pos!,-25%%
echo Now Renaming Store No !StoreNo!
call:getvalue %StoreList% !StoreNo! StoreName
call:getvalue %StoreNoConv% !StoreNo! ThreeDigitNo
set OutFile=!OutPref!Store!ThreeDigitNo!!OutSuf1!!StoreName!!OutSuf2!
echo %%f moved to !OutFile! >> %LogFile%
move "%%f" "%OutFolder%!OutFile!" >> %LogFile%
del !InTxtVer!
)
cd %BaseDir%
exit /b
:getpos
set stemp=!InAddLine!
set pos=0
:loop
set /a pos+=1
echo !stemp!|findstr /b /c:"!MatchStr!" >NUL
if errorlevel 1 (
set stemp=!stemp:~1!
if defined stemp GOTO loop
set pos=0
)
set /a pos-=4
goto:eof
:getvalue
rem This function reads a value from a file and stores it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to set search result under
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof

batch distribute files in a folder into some folders

I have a source folder, named m_all, and destination folders are named as m_1, m_2, m_3... m_8.
I want to copy files in source folder into destination folders,
in such a way that 2 files to m_1, 3 files to m_2, 2 files to m_3, 3 files to m_4..
There is no pattern among these target files.
How can I do that?
This will distribute files evenly between the folders and if there are any files remaining, they'll be copied to the last folder
The input folder will be received in %1. The number of folders is put in %totalfolder% variable. The folder names (m_1... m_8) will be split into 2 parts: prefix (m_) in %folderprefix% variable and the folder number which is counted in !foldernum! variable
#Echo off
If "%1"=="" (
Echo Usage: %0 '^<base folder^>'
Exit /b
)
SetLocal EnableDelayedExpansion
Rem Get number of files in the folder
For /f %%A in ('dir %1 ^| find "File(s)"') do set totalfiles=%%A
Rem Set your parameters here
Rem First part of your folder name
Set folderprefix=m_
Rem m_1 ... m_8 => 8 folders
Set totalfolder=8
Set /a files_per_folder=totalfiles/totalfolder
Set filenum=1
Set foldernum=1
For %%f in (%1\*) do (
Move "%%f" "%folderprefix%!foldernum!\"
Rem Move to next folder by increasing folder number "
If !filenum! EQU %files_per_folder% (
If !foldernum! LSS %totalfolder% (
Set /a foldernum+=1
Set filenum=0
)
)
Set /a filenum+=1
)
The script basically maintain a count of files in each folder and if the folder is filled completely it will move to the next folder
Here's a basic example, (I feel certain there'll be a cleverer way of doing this). It should copy the files using the pattern you gave, until there's none left to copy. (that means, for instance, the last file could be the only one copied to m_3)
#Echo Off
(Set SrcDir=C:\Users\Cointreau\Documents\m_all)
If /I Not "%CD%"=="%SrcDir%" PushD "%SrcDir%" 2>Nul||Exit/B
If "%~f0" Equ "%SrcDir%" Exit/B
For /L %%A In (1,1,8) Do If Not Exist "m_%%A" Exit/B
SetLocal EnableDelayedExpansion
Set "i=0"
For /F "Delims=" %%A In ('DIR/B/A-D-S') Do (Set/A "i+=1"
If !i! LEq 2 (Copy "%%A" "m_1") Else (If !i! LEq 5 (Copy "%%A" "m_2") Else (
If !i! LEq 7 (Copy "%%A" "m_3") Else (If !i! LEq 10 (
Copy "%%A" "m_4") Else (If !i! LEq 12 (Copy "%%A" "m_5"
) Else (If !i! LEq 15 (Copy "%%A" "m_6") Else (
If !i! LEq 17 (Copy "%%A" "m_7") Else (
If !i! LEq 20 (Copy "%%A" "m_8"
If !i! Equ 20 Set i=0)))))))))
There is no code included to cater for copying existing files, i.e overwrite.
Change the location between the parentheses on line 3 to suit your actual source directory path.
#echo off
setlocal enableextensions disabledelayedexpansion
rem Configure source folder
set "source=m_all"
rem Initialize targets array
set "targets=0"
for %%a in (
"m_1" "m_2" "m_3" "m_4" "m_5" "m_6" "m_7" "m_8"
) do (
set /a "targets+=1"
setlocal enabledelayedexpansion
for %%b in ("!targets!") do endlocal & set "target.%%~b=%%~a"
)
rem Determine the number of files to copy
for /f %%a in ('
dir /b /a-d "%source%\*" ^| find /c /v ""
') do set "nFiles=%%a"
rem Initialize progress variables
set /a "copied=0", "toCopy=0", "target=1", "processedFiles=0"
rem For each file in source
for %%a in ("%source%\*") do (
rem Enable delayed expansion on start of first loop
if not "!!"=="" setlocal enabledelayedexpansion
rem One more file is being processed
set /a "processedFiles+=1"
rem Determine if we need to reevaluate where to place the file
if !toCopy! lss 1 (
if !processedFiles! gtr !nFiles! (
rem More files have been added to the folder, deal with it
set /a "target=(processedFiles %% targets) + 1", "toCopy=1"
) else for /l %%t in (!target! 1 %targets%) do if !toCopy!==0 (
rem We are still handling the expected set of files
set /a "target=%%t", "toCopy=nFiles*target/targets-copied", "copied+=toCopy"
)
)
rem Prepare variables to be accessed without delayed expansion
for %%b in ("!target!") do for %%t in ("!target.%%~b!") do (
setlocal disabledelayedexpansion
rem DEBUG CODE - Remove echo prefix if the output is correct
echo copy "%%~fa" "%%~ft"
endlocal
)
rem One file processed in current target
set /a "toCopy-=1"
)

Extract date from XML file and move file to directory created from date

I have a directory that contains close to a million XML files. Needless to say, it takes forever to load (20+ minutes) So, I'm writing a script to divide the files into folders with the top level being year and having months under each year. There are 4 main filenames where the date can be extracted from the 3rd token and the rest from the second token. ie:
BA1253570001_BALMIS_20130617_TRC_0_109506738E.xml
BA1254260001_ACCTV21_20140430_AMR_0_1095611492.xml
BA1736330001_SWFTOUT_20140929_LIQ_1_MTBX553494.xml
BA1739240001_FEDOUT_20140904_LIQ_1_105633316M.xml
The rest are like this:
EODMESS_20140718_MTBX473286.xml
MSGCONF_20140410_109558667V.xml
I'm sure there is an easier way to do it, but here is my code so far:
#echo on
setlocal enabledelayedexpansion
Set "starttime=%time%"
pushd C:\temp\xmls
for /f %%a in ('dir /b/o:d *.xml') do (
call :ExtractDates %%a ret
echo %%a - !ret!
for /f "tokens=1" %%b in ("!ret!") do (
for /f "tokens=1-3 delims=/" %%c in ("%%b") do (
if not exist .\%%e md .\%%e
if not exist .\%%e\%%c md .\%%e\%%c
if %%b equ %%c/%%d/%%e (
echo moving %%~nxa to .\%%e\%%c
echo move %%~nxa .\%%e\%%c
pause
)
)
)
)
echo Start time: %starttime%
echo End time: %time%
popd
exit /b
:ExtractDates
#echo on
setlocal enabledelayedexpansion
Echo Starting ExtractDates
for %%a in (BALMIS ACCTV21 FEDOUT SWFTOUT) do (
if not errorlevel 1 (set t=3) else set t=2
Call :ExtractFunc %~1 %%a !t! ret
endlocal&set "%~2=!ret!"&exit /b 0
)
exit /b
:ExtractFunc
#echo on
setlocal
Echo Starting ExtractFunc
for /f "tokens=%3 delims=_" %%a in (
'echo %~1^|Findstr "%~2"'
) do (
if not errorlevel 1 (
endlocal&set "%~4=%%a"&exit /b 0
)
)
exit /b
The problem is that the variable token isn't returning the right number and I'm not sure why. Any suggestions appreciated.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
PUSHD "%sourcedir%"
FOR /f "tokens=1*delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*_*_*.xml" '
) DO SET "filename=%%a"&CALL :process
POPD
GOTO :EOF
:process
FOR /f "tokens=2,3,6delims=_" %%m IN ("%filename%") DO SET "date1=%%m"&SET "date2=%%n"&SET "whichdate=%%o"
IF DEFINED whichdate SET "date1=%date2%"
IF NOT DEFINED date2 GOTO :eof
ECHO(MD .\%date1:~0,4%\%date1:~4,2%
ECHO(MOVE "%filename%" .\%date1:~0,4%\%date1:~4,2%\
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. Append 2>nul to suppress error messages (eg. when the directory already exists)
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)
Simply extract the two possible datestrings and use the presence of the sixth token to signal which of the two positions to select for generation of the destination directory. Skip if there's no third token (fails to fit mask specified)
Then select the required field to date1 and do some substringing.

Get full path and long file name from short file name

I have an nice console file manager (eXtreme by Senh Liu), it passes short path/filenames as variables to a menu.bat.
How can I generate a full folder name + long file name?
Example:
input variable = "P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
target variable = "P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
I have tried the following:
SET my_file=%~2:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~fF:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~dpnxF:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
Simple solution: use PowerShell.
PS C:\> (Get-Item 'P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL').FullName
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
You can incorporate a PowerShell call in a batch file like this:
#echo off
setlocal
for /f "usebackq delims=" %%f in (
`powershell.exe -Command "(Get-Item '%~1').FullName"`
) do #set "var=%%~f"
echo %var%
Output:
C:\> test.cmd P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
PowerShell is available for all supported Windows versions:
Windows XP SP3 and Server 2003 SP2: PowerShell v2 available
Windows Vista and Server 2008: ship with PowerShell v1 (not installed by default), PowerShell v2 available
Windows 7 and Server 2008 R2: PowerShell v2 preinstalled, PowerShell v3 available (batteries not included)
Windows 8 and Server 2012: PowerShell v3 preinstalled
If PowerShell can't be used for some reason (e.g. administrative restrictions), I'd use VBScript instead:
name = WScript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(name) Then
Set f = fso.GetFile(name)
ElseIf fso.FolderExists(name) Then
Set f = fso.GetFolder(name)
If f.IsRootFolder Then
WScript.Echo f.Path
WScript.Quit 0
End If
Else
'path doesn't exist
WScript.Quit 1
End If
Set app = CreateObject("Shell.Application")
WScript.Echo app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path
A VBScript like the one above can be used in a batch file like this:
#echo off & setlocal
for /f "delims=" %%f in ('cscript //NoLogo script.vbs "%~1"') do #set "var=%%~f"
echo %var%
This does require an additional script file, though.
The following should work with any valid path, as long as it is not a UNC path. The path may be absolute or relative. It may use short file names or long names (or a mixture). The path may refer to a folder or a file.
The result will end with \ if it is a folder, no \ at end if it is a file.
The :getLongPath routine expects an inputPath variable name as the 1st argument, and an optional return variable name as the 2nd argument. The inputPath variable should contain a valid path. If the return variable is not speciied, then the result is ECHOed to the screen (enclosed in quotes). If the return variable is specified, then the result is returned in the variable (without quotes).
The routine should only be called when delayed expansion is disabled if you are returning a variable. If called with delayed expansion enabled, then the result will be corrupted if it contains the ! character.
Test cases (for my machine only) are at the top of the script, the actual routine at the bottom.
#echo off
setlocal
for %%F in (
"D:\test\AB2761~1\AZCFE4~1.TXT"
"AB2761~1\AZCFE4~1.TXT"
"D:\test\AB2761~1\ZZCE57~1\"
"D:\test\a b\a z.txt"
"D:\test\a b\z z"
"."
"\"
"x%%&BAN~1\test"
"x%% & bang!\test"
) do (
echo(
echo resolving %%F
set "shortPath=%%~F"
call :getLongPath shortPath longPath
set longPath
)
echo(
echo(
set "shortPath=D:\test\AB2761~1\AZCFE4~1.TXT"
set shortPath
echo Calling :getLongPath with with no return variable
call :getLongPath shortPath
exit /b
:getLongPath path [rtnVar]
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "eol=: delims=" %%: in ('dir /b /ad') do (
if /i "%%~snx:" equ "%folder%" (
set "rtn=%%:\%rtn%"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%
( endlocal
if "%~2" equ "" (echo "%rtn%") else set "%~2=%rtn%"
)
=== OUTPUT ===
resolving "D:\test\AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "D:\test\AB2761~1\ZZCE57~1\"
longPath=D:\test\a b\z z\
resolving "D:\test\a b\a z.txt"
longPath=D:\test\a b\a z.txt
resolving "D:\test\a b\z z"
longPath=D:\test\a b\z z\
resolving "."
longPath=D:\test\
resolving "\"
longPath=D:\
resolving "x%&BAN~1\test"
longPath=D:\test\x% & bang!\test\
resolving "x% & bang!\test"
longPath=D:\test\x% & bang!\test\
shortPath=D:\test\AB2761~1\AZCFE4~1.TXT
Calling :getLongPath with with no return variable
"D:\test\a b\a z.txt"
If you want to run the above code, then I suggest you completely delete all the test scenario code between #echo off and :getLongPath. Then you can simply call the script, passing any valid path as the first argument. The correct long path should be printed as a result.
I was amazed how difficult this was to solve using batch. I don't think it is much easier with JScript or VBS (Actually, Ansgar found a nice VBS solution). But I like Ansgar's simple PowerShell solution - so much easier.
Update
I found an obscure case where the above code fails if called from within a FOR loop, and the path happens to have the FOR variable within it. It also doesn't properly report a path with wild cards as an error, and it doesn't work with delayed expansion enabled when the path contains !.
So I created a modified version below. I'm pretty confident it should truly work in all situations, except for UNC paths and possibly not with unicode in the path. I packaged it up as an easy to call procedure, complete with built in documentation. It can be left as a stand-alone script, or incorporated into a larger script.
#echo off
:getLongPath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for /f "eol=: delims=" %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourcePath2=%%F"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "sourcePath3=%sourcePath2:**=%"
set "sourcePath3=%sourcePath3:?=%"
if "%sourcePath3%" neq "%sourcePath2%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "delims=: tokens=1,2" %%A in ("%folder%:%rtn%") do for /f "eol=: delims=" %%F in ('dir /b /ad') do (
if /i "%%~snxF" equ "%%A" (
set "rtn=%%F\%%B"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
I also adapted Ansgar's VBS into a hybrid JScript/batch script. It should provide the identical result as the pure batch script above, but the JScript is much simpler to follow.
#if (#X)==(#Y) #end /* harmless hybrid line that begins a JScrpt comment
#echo off
:getLongpath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
::************ Batch portion ***********
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "rtn="
for /f "delims=" %%A in ('cscript //E:JScript //nologo "%~f0" %*') do set "rtn=%%A"
if not defined rtn exit /b 1
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
exit /b 0
************ JScript portion ***********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var app=WScript.CreateObject("Shell.Application");
var inPath=env(WScript.Arguments.Item(0));
var folder="";
var f;
if (fso.FileExists(inPath)) {
f=fso.GetFile(inPath);
}
else if (fso.FolderExists(inPath)) {
folder="\\"
f=fso.GetFolder(inPath);
if (f.IsRootFolder) {
WScript.StdOut.WriteLine(f.Path);
WScript.Quit(0);
}
}
else {
WScript.StdErr.WriteLine('ERROR: Invalid path');
WScript.Quit(1);
}
WScript.StdOut.WriteLine( app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path + folder);
This returns the full long pathname, but depends on:
A) there not being too many files in the tree (due to time taken)
B) there is only one of the target (long) filename in the tree.
#echo off
for /f "delims=" %%a in (' dir /b "%~1" ') do set "file=%%a"
for /f "delims=~" %%a in ("%~dp1") do cd /d "%%a*"
for /f "delims=" %%a in ('dir /b /s /a-d "%file%" ') do set "var=%%a"
echo "%var%"
When called with mybat "d:\MYPROG~1\SHELLS\zBackup\REFSTO~1.BAL"
it returned this:
"d:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
And one unexpectedly simple solution:
echo lcd %some_path%|ftp
EDITED to show example: it isn't 100%
d:\>echo lcd C:\Files\Download\MYMUSI~1\iTunes\ALBUMA~1 |ftp
Local directory now C:\Files\Download\MYMUSI~1\iTunes\Album Artwork.
this is an ugly batch job and my code is not nice, but brut force :-)
#echo off &SETLOCAL
SET "short=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
SET "shorty=%short:\= %"
FOR %%a IN (%short%) DO SET "shortname=%%~nxa"
FOR %%a IN (%shorty%) DO (
IF DEFINED flag (
CALL :doit "%%~a"
) ELSE (
SET "longpath=%%~a"
SET flag=true
SET "first=\"
)
)
ECHO "%longpath%"
goto:eof
:doit
SET "last=%~1"
IF "%last%" neq "%shortname%" (SET "isDir=/ad") ELSE SET "isDir=/a-d"
FOR /f "delims=" %%b IN ('dir %isdir% %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X0=%%b"
FOR /f "delims=" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X1=%%b"
REM for European time format
IF "%X0: =%"=="%X1: =%" (SET /a token=3) ELSE SET /a token=4
REM for "AM/PM" time format
IF "%X0: =%"=="%X1: =%" (SET /a token=4) ELSE SET /a token=5
FOR /f "tokens=%token%*" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "longname=%%~c"
SET "longpath=%longpath%\%longname%"
SET "first="
goto:eof
Please set your time format in the doit function (delete as applicable format).This might maybe fail with special characters in path or file names like !%=&^.
#echo off
setlocal
rem this need to be a short name to avoid collisions with dir command bellow
cd C:\BALBAL~1\BLBALB~1\
set "curr_dir=%cd%"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do #dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo --%full_path%--
cd %curr_dir%
endlocal
The path is hardcoded at the beginning but you can change it or parameterizied it.As you can easy get the full name of a file here is only a solution for directories.
EDIT
now works for file and directory and a parameter can be passed:
#echo off
rem ---------------------------------------------
rem ---------------------- TESTS ----------------
rem ----------------------------------------------
md "c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes" >nul 2>&1
md "c:\test\1 b1\1\" >nul 2>&1
for %%t in ("c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes") do set t_dir=%%~st
for %%t in ("c:\test\1 b1\1\") do set t_dir2=%%~st
echo a>"%t_dir2%a"
echo a>"%t_dir2%a a.txt"
echo testing "%t_dir%\\"
call :get_full_name "%t_dir%\\"
echo(
echo testing "%t_dir2%a"
call :get_full_name "%t_dir2%a"
echo(
echo testing "%t_dir2%a a.txt" with return variable
call :get_full_name "%t_dir2%a a.txt" test_var
echo return variable : -- %test_var% --
goto :eof
rem -------------------------------------
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
set "curr_dir=%cd%"
for /f "delims=" %%n in ('dir /b /n "%~dps1\%~snx1"') do set "name=%%n"
cd "%~dps1"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do #dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%~f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo %full_path%%name%
cd %curr_dir%
endlocal & if "%~2" NEQ "" set "%~2=%full_path%%name%"
and the test output:
testing "c:\test\BLABLA~1\BLABLA~1\NONONO~1\YESYES~1\\"
c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes\
testing "c:\test\1B1~1\1\a"
c:\test\1 b1\1\a
testing "c:\test\1B1~1\1\a a.txt" with return variable
c:\test\1 b1\1\a a.txt
return variable : -- c:\test\1 b1\1\a a.txt --
And one attempt with WMIC and Win32_Directory.Probably is slower than using cd and dir , but the current directory is not changed:
#echo off
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
for /f "delims=" %%n in ('dir /b /n "%~dps1\*%~snx1"') do set "name=%%n"
set "short_path=%~dps1"
set "short_path=%short_path:~0,-1%"
set "drive=%short_path:~0,2%"
set "full_name="
:repeat
set "short_path=%short_path:\=\\%"
set "short_path=%short_path:'=\'%"
FOR /F "usebackq skip=2 delims=" %%P in (`WMIC path win32_directory where name^='%short_path%' get Path^,FileName /Format:Textvaluelist.xsl`) do for /f "delims=" %%C in ("%%P") do (
set "_%%C"
)
set "_Path=%_Path:~0,-1%"
set full_name=%_FileName%\%full_name%
if "%_Path%" NEQ "" (
set "short_path=%drive%%_Path%"
goto :repeat
) else (
set full_name=%drive%\%_FileName%\%full_name%
)
echo %full_name%%name%
endlocal if "%~2" NEQ "" set "%~2=%full_path%%name%"
Not heavy tested yet....
Here is a batch script based on the answer by npocmaka, using the ftp command (together with its sub-command lcd). There you can see that only the last element of a path is expanded to the long name. My idea is now to apply the lcd sub-command for every element of the path individually, so we will get the full names of all elements in the final output.
This script works for directories only. It does not work for files, neither does it work for UNC paths.
So here we go:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ARGS=%*"
set FTP_CMD=lcd
set "TEMP_FILE=%TEMP%\%~n0_%RANDOM%.tmp"
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA" & set "SEP=\"
setlocal EnableDelayedExpansion
> "%TEMP_FILE%" (
for %%D in ("!ARG:\=" "!") do (
endlocal
if not "%%~D"=="" (
set "ITEM=%%~D"
setlocal EnableDelayedExpansion
echo(%FTP_CMD% "!ITEM!!SEP!"
endlocal
set "SEP="
)
setlocal EnableDelayedExpansion
)
)
set "PREFIX="
for /F "delims=" %%L in ('^< "%TEMP_FILE%" ftp') do (
endlocal
if not defined PREFIX set "PREFIX=%%L"
set "LONG_PATH=%%L"
setlocal EnableDelayedExpansion
)
set "PREFIX=!PREFIX::\.=!" & set "PREFIX=!PREFIX:~,-1!"
for /F "delims=" %%E in ("!PREFIX!") do (
set "LONG_PATH=!LONG_PATH:*%%E=!"
set "LONG_PATH=!LONG_PATH:~,-1!"
)
echo(!LONG_PATH!
)
endlocal
del /Q "%TEMP_FILE%"
endlocal
exit /B
Basically there is a for %%D loop that iterates through all elements of the given path (after it has been expanded to its full path by the outer-most for %%A loop). Each element is enclosed within "" and preceded with lcd (the sub-command of the ftp command to change the local working directory). For the first path element that constitutes a drive, a trailing \ is appended to refer to its root directory. Each of these built path strings is written to a temporary file.
Next the temporary file is redirected into the ftp command, so it changes its local working directory path element by path element. The output of ftp is captured by a for /F %%L loop. Actually the last line of the output is of interest only as this contains the full long path. However, the first line is also stored, where the root directory of the applicable drive is used. This is just needed to easily extract the prefix of the output lines in order to remove it from the output line containing the full path (the ftp command outputs something like Local directory now D:\. on English systems, but I want the script to be language-independent). Finally the said prefix is removed from the full long path and the result is returned on the console.
Here is an improved approach that can also handle paths of files, by handling the last path element in such a case separately by the sub-routine :LAST_ITEM, which does not rely on ftp but on the fact that for loops expand tthe last path element to long paths when wildcards are given:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ARGS=%*"
set FTP_CMD=lcd
set "TEMP_FILE=%TEMP%\%~n0_%RANDOM%.tmp"
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA" & set "SEP=\" & set "ITEM="
if exist "%%~fA" (
if exist "%%~fA\" (set "FLAG=") else set "FLAG=#"
setlocal EnableDelayedExpansion
> "%TEMP_FILE%" (
for %%D in ("!ARG:\=" "!") do (
endlocal
if not "%%~D"=="" (
set "ITEM=%%~D"
setlocal EnableDelayedExpansion
echo(!FTP_CMD! "!ITEM!!SEP!"
endlocal
set "SEP="
) else set "ITEM="
setlocal EnableDelayedExpansion
)
)
set "PREFIX="
for /F "delims=" %%L in ('^< "%TEMP_FILE%" 2^> nul ftp') do (
endlocal
if not defined PREFIX set "PREFIX=%%L"
set "LONG_PATH=%%L"
setlocal EnableDelayedExpansion
)
set "PREFIX=!PREFIX::\.=!" & set "PREFIX=!PREFIX:~,-1!"
for /F "delims=" %%E in ("!PREFIX!") do (
set "LONG_PATH=!LONG_PATH:*%%E=!"
set "LONG_PATH=!LONG_PATH:~,-1!"
)
if not "!LONG_PATH:~-2!"==":\" set "LONG_PATH=!LONG_PATH!\"
for /F "tokens=1,2 delims=|" %%S in ("!LONG_PATH!|!ITEM!") do (
endlocal
set "LONG_PATH=%%S" & set "ITEM=%%T"
if defined FLAG call :LAST_ITEM ITEM LONG_PATH
setlocal EnableDelayedExpansion
)
if defined FLAG (echo(!LONG_PATH!!ITEM!) else echo(!LONG_PATH!
) else setlocal EnableDelayedExpansion
)
endlocal
del /Q "%TEMP_FILE%"
endlocal
exit /B
:LAST_ITEM var_last_item var_long_path
setlocal EnableDelayedExpansion
for %%I in ("!%~2!!%~1!*") do (
endlocal
set "LONG=%%~nxI" & set "SHORT=%%~snxI"
setlocal EnableDelayedExpansion
if /I "!LONG!"=="!%~1!" (set "%~1=!LONG!"
) else if /I "!SHORT!"=="!%~1!" set "%~1=!LONG!"
)
for /F "delims=" %%T in ("!%~1!") do (
endlocal
set "%~1=%%T"
setlocal EnableDelayedExpansion
)
endlocal
exit /B
My solution:
set shortname=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
for /F %f in ('dir /b /s %shortname%') do where /R %~dpf %~nf%~xf
if you use it in a batch file:
for /F %%f in ('dir /b /s %shortname%') do where /R %%~dpf %%~nf%%~xf
Alright, here is a script I began some time ago, relying on the fact that dir /B returns long file or directory names when a wildcard is used. This is a recursive approach that walks up the directory hierarchy of the path given as command line argument and resolves each element. Note that it has problems with paths containing % and/or ^ due to the usage of call:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set ARGS=%*
set "COLL="
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA"
if exist "%%~fA" (
call :PROC_ITEM COLL "%%~fA" || set "COLL="
)
setlocal EnableDelayedExpansion
)
if defined COLL (echo(!COLL!) else exit /B 1
endlocal
endlocal
exit /B
:PROC_ITEM rtn_built_path val_source_path
setlocal DisableDelayedExpansion
set "FND="
if "%~pnx2"=="\" (
set "COLL=%~d2"
) else (
cd /D "%~dp2." & rem (this must be set before `for /F` in order for `%%~snxJ` to refer to it!)
for /F "delims= eol=|" %%J in ('dir /B /A "%~f2?"') do (
if /I "%%J"=="%~nx2" (
set "FND=\%%J" & rem (this assignment should be executed for long names)
) else (
if /I "%%~snxJ"=="%~nx2" set "FND=\%%J" & rem (and this for short ones)
)
)
if defined FND (
call :PROC_ITEM COLL "%~dp2."
) else (
exit /B 1 & rem (this intercept exceptions and should usually not happen)
)
)
endlocal & set "%~1=%COLL%%FND%"
exit /B

How to compare two files' creation date?

Using batch for XP, how can I compare the creation dates of two files with a function isMoreRecentThan(pathToFile1,pathToFile2) that returns true or false ?
The solution should run without admin rights (and in particular should NOT use wmic).
for /f "tokens=1,2,3 delims=/" %%T in ('forfiles /m file1 /C "cmd /c echo #fdate"') do set date1=%%V%%U%%T
for /f "tokens=1,2,3 delims=/" %%T in ('forfiles /m file2 /C "cmd /c echo #fdate"') do set date2=%%V%%U%%T
if %date2% GTR %date1% *some command here*
You can also use #ftime for more precise comparing.And. forfiles is not native command for windows XP and 2003 and you'll have to download and install windows 2003 resource kit tools if you are not using newer OS
EDIT(for XP and 2003):
setlocal enabledelayedexpansion
for /f "tokens=1,2,3,4,5,6 delims=/: " %%T in ('for %%a in ^(FILE1^) do echo %%~ta') do (
if "%%Y" EQU "PM" (
set /a "hour=%%W+12"
) else (
set hour=%%W
)
set time1=%%V%%U%%T!hour!%%Y
)
for /f "tokens=1,2,3,4,5,6 delims=/: " %%T in ('for %%a in ^(FILE2^) do echo %%~ta') do (
if "%%Y" EQU "PM" (
set /a "hour=%%W+12"
) else (
set hour=%%W
)
set time2=%%V%%U%%T!hour!%%Y
)
if !time1! GTR !time2! *do something*
endlocal
you'll need to change FILE1 and FILE2 with relevant values
LAST EDIT
#echo off
call :isOlder "C:\test.file1" "C:\test.file2"
goto :eof
:isOlder [%1 path to first file ; %2 path to second file]
setlocal
call :get_file_c_time "%~1" time1
call :get_file_c_time "%~2" time2
if "%time1%" LEQ "%time2%" (
echo YES
) else (
echo NO
)
goto :eof
:get_file_c_time [ %1 path to file; %2 variable to assign value ]
set file_path=%~1
if not exist "%file_path%" echo file %1 does not exist&& exit /b 1
if "%~2" equ "" echo need a secont parameter && exit /b 2
setlocal enableDelayedExpansion
for /f "skip=5 tokens=1,2,3,4,5,6 delims=/:.гчЈз " %%T in ('dir /tc "%file_path%"') do (
if "%%Y" EQU "PM" (
set /a "hour=%%W+12"
) else (
set hour=%%W
)
set ftime=%%V%%U%%T!hour!%%X
goto :endfor
)
:endfor
endlocal & set "%~2=%ftime%"
goto :eof
now you can use the :isOlder subroutine
#ECHO OFF
SETLOCAL
:: files to be compared
SET "filea=c:\mys\oldobj.txt"
SET "fileb=c:\mys\15850492.htm"
:: Create a tempdir
:Tloop
SET tempdir="%temp%\%random%"
IF EXIST %tempdir%* GOTO tloop
MD %tempdir%
MOVE "%filea%" %tempdir%\a >nul
MOVE "%fileb%" %tempdir%\b >NUL
SET file
ECHO.
FOR /f %%i IN ('dir /b /a-d /o-d /tc %tempdir%') DO SET first=%%i
ECHO first file (create date) was %first%
FOR /f %%i IN ('dir /b /a-d /o-d /tw %tempdir%') DO SET first=%%i
ECHO first file (write date) was %first%
MOVE %tempdir%\a "%filea%" >nul
MOVE %tempdir%\b "%fileb%" >nul
RD %tempdir%
ECHO.
ECHO create times=v=v=v=v=v=v=v=v=v=v=v
ECHO.
DIR %filea% %fileb% /od /tc
ECHO.
ECHO write times=v=v=v=v=v=v=v=v=v=v=v
ECHO.
DIR %filea% %fileb% /od /tw
test result: (irrelevant lines excised)
filea=c:\mys\oldobj.txt
fileb=c:\mys\15850492.htm
first file (create date) was a
first file (write date) was b
create times=v=v=v=v=v=v=v=v=v=v=v
Directory of c:\mys
07/07/2012 21:40 788,960 oldobj.txt
07/04/2013 19:06 16,295 15850492.htm
write times=v=v=v=v=v=v=v=v=v=v=v
Directory of c:\mys
07/04/2013 19:06 16,295 15850492.htm
10/05/2013 14:04 788,960 oldobj.txt
So - yes, with this approach, it's very relevant whether the files are in the same directory because it relies on sorting the files by the relevant date.
And as demonstrated, the creation date and last-update date can vary wildly - it depends on what the file is used for. It's even possible to have a creation date that's later than the last-update date - in fact it's common since if a file is COPIED, the new file acquires the date that it was created, but the last-update date is just that - the date the original file was last updated.
This is the last edit version in npocmaka's answer, but slightly modified. I have introduced these Car981's information into the code:
a function isMoreRecentThan(file1,file2) that returns true if file1 is more recent than file2, and false otherwise
The date format in dir is 17/05/2013 15:01
The isMoreRecentThan subroutine return an %ERRORLEVEL% equal 1 (true) if first file is more recent than second one and 0 in any other case, including if any file does not exist.
#echo off
call :isMoreRecentThan "C:\path1\file1.ext" "C:\path2\file2.ext"
if %errorlevel% equ 1 echo File1 is more recent than File2
goto :eof
:isMoreRecentThan [%1=path to first file ; %2=path to second file]
setlocal
call :get_file_c_time "%~1" time1
call :get_file_c_time "%~2" time2
set exitCode=0
if %time1% neq 0 if %time2% neq 0 if "%time1%" gtr "%time2%" set exitCode=1
exit /B %exitCode%
:get_file_c_time [ %1 path to file; %2 variable to assign value ]
set %2=0
if not exist %1 exit /B
for /F "skip=5 tokens=1-5 delims=/: " %%a in ('dir /TC %1') do (
set %2=%%c%%b%%a%%d%%e
exit /B
)

Resources