copying most recent file set from txt in unknown directory - windows

i have a notepad.txt document that lists the files that need to be copied to the folder that holds the batch file. the files are located in several sub directories and with my code, it copies all of the files with specified name.
for /f "delims=" %%i in (testlist.txt) do echo |robocopy "%dir1%." "C:\temporary" "%%i.*" /s /ndl /njs /njh /nc /ts /ns
how do i set this up properly so it will search the most recent file, and copy only the file not the folder and subfolder?

How to get file's last modified date on Windows command line?
for %a in (MyFile.txt) do set FileDate=%~ta
Compare 2 dates in a Windows batch file
set "sdate1=%olddate:~-4%%olddate:~3,2%%olddate:~0,2%"
set "sdate2=%newdate:~-4%%newdate:~3,2%%newdate:~0,2%"
if %sdate1% GTR %sdate2% (goto there) else echo here
So given that you can already read the file and do the copy, here is pseudo code of the logic I would write for putting it all together:
set oldTimeStamp = "1901-01-01" //so first comparison wins and doesn't throw a null error
for each filename in list.txt
set newTimestamp = getTimeStamp(filename)
if newTimeStamp > oldTimeStamp then set fileToCopy = filename
set oldTimeStamp = newTimeStamp
next
doCopy(fileToCopy)
Basically loop through each filename and get the timestamp. Store the timestamp of the previous file and compare the new and old timestamps. If the current one is newer, save the filename to a variable that you will use to copy. At the end of the loop, fileToCopy should contain the name of the file with the most recent modified time.

The following code snippet retrieves the most recent file and echos its path. Here the wmic command is used to get standardised locale-independent timestamps, which can immediately be compared as strings, so it is not necessary to convert them to numbers. So here it is:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "RECENT=00000000000000.000000+000"
set "RECENTFILE="
for /F "usebackq eol=| delims=" %%L in ("testlist.txt") do (
setlocal DisableDelayedExpansion
set "CURRFILE=%%~fL"
if exist "%%~fL" (
setlocal EnableDelayedExpansion
for /F "skip=1 tokens=1 delims= " %%T in ('
wmic DATAFILE ^
WHERE Name^="!CURRFILE:\=\\!" ^
GET LastModified ^
/FORMAT:TABLE
') do (
for /F "delims=" %%S in ("%%T") do (
if %%S GTR !RECENT! (
endlocal
endlocal
set "RECENT=%%S"
set "RECENTFILE=%%~fL"
) else (
endlocal
endlocal
)
)
)
) else (
endlocal
)
)
if defined RECENTFILE (
rem Perform your action here:
echo(!RECENTFILE!
)
endlocal
exit /B
What happens:
there are two variables RECENT and RECENTFILE which hold the timestamp of and the path to most recent file, respectively;
the outer for /F loop walks through the items in the list file testlist.txt;
for each existing item, a wmic query is executed to get the last modify date, and its output is parsed by two nested for /F loops, each iterating once only; since wmic returns Unicode strings, a single for /F loop is not enough because it leaves some orphaned carriage-return characters, which may impact the remaining code, but a second loop removes them;
the retrieved file date is compared to the buffered one in RECENT, and if it is greater, meaning that the file is newer, it is stored in RECENT and the respective file path is stored in RECENTFILE;
if variable RECENTFILE is finally not defined, the list testlist.txt does not point to existing files, or it is empty;
the toggling of delayed expansion is necessary to avoid trouble with any special characters;
Besides the fact, that the wmic queries are worse in terms of performance compared to getting the timestamps using for (for instance for %F in ("*.*") do echo %~tF), the following restriction applies:
The , character must not occur in any of the listed file paths!
According to this answer, there is a way to overcome this, but then the ) character is disallowed: to replace the clause WHERE Name^="!CURRFILE:\=\\!" by WHERE ^(Name^="!CURRFILE:\=\\!"^) (the escaping ^ of the parenthesis is only required as the wmic command line is placed within a for /F set). So you can either have , or ) within a wmic command line, but not both of these characters.

Related

How to run a batch file with a FOR loop with two file names with two parameters?

I am using an alass tool to synchronize two subtitles. It is simple to use with one file at a time but I want to use it on multiple files using a loop.
The usage of the tool is like this:
alass.bat correct_subtitle.srt incorrect_subtitle.srt output.srt
I want to do a simple for loop with two parameters with this command:
FOR %i IN (*g.srt) DO FOR %n IN (*t.srt) DO alass.bat %i %n %n
The script is working but I want the command works one time with the second file not looping the first file with all the second files.
I want the script to do like this:
C:\Users\user\Downloads\alass-windows64\alass.bat Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt
C:\Users\user\Downloads\alass-windows64\alass.bat Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt
etc.
All the subtitles are in one folder the correct and incorrect subtitles are like this:
Correct sub (Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt)
incorrect sub (Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt)
Correct sub (Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt)
incorrect sub (Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt)
etc.
A solution for revision 17 of the question is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir *.eng.srt /A-D-L /B /ON 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "\.S[0123456789][0123456789]*E[0123456789][0123456789]*\."') do call :ProcessFile "%%I"
endlocal
exit /B
:ProcessFile
echo Correct file: %1
set "FileNameBegin=%~n1"
:GetMatchingPart
for %%J in ("%FileNameBegin%") do (
echo %%~xJ| %SystemRoot%\System32\findstr.exe /I /R "^\.S[0123456789][0123456789]*E[0123456789][0123456789]*$" >nul
if errorlevel 1 set "FileNameBegin=%%~nJ" & goto GetMatchingPart
)
for /F "eol=| delims=" %%J in ('dir "%FileNameBegin%.*-FGT.srt" /A-D /B 2^>nul') do (
echo Incorrect file: "%%J"
call alass.bat %1 "%%J" "%%J"
)
goto :EOF
That code was run on a FAT32 drive with following files in current directory:
Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt
Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt
Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt
Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt
Exa..mple!.S01E01.anotherName.DTS-FGT.srt
Exa..mple!.S01E01.name.eng.srt
example.S01E02.anotherName-FGT.srt
example.S01E02.name.eng.srt
The output without really calling alass.bat is:
Correct file: "Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt"
Incorrect file: "Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt"
Correct file: "Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt"
Incorrect file: "Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt"
Correct file: "Exa..mple!.S01E01.name.eng.srt"
Incorrect file: "Exa..mple!.S01E01.anotherName.DTS-FGT.srt"
Correct file: "example.S01E02.name.eng.srt"
Incorrect file: "example.S01E02.anotherName-FGT.srt"
The main FOR loop runs in background one more cmd.exe with option /c the command line within ' appended as additional arguments.
The command DIR executed by this second command processor outputs all names of files in current directory matching the wildcard pattern *.eng.srt.
This list is redirected to FINDSTR which filters the list of file names based on the regular expression \.S[0123456789][0123456789]*E[0123456789][0123456789]*\.. So a file name to process must contain a string consisting of
a dot
case-insensitive the letter S
one or more digits in range 0 to 9
case-insensitive the letter E
one or more digits in range 0 to 9
on more dot.
All the file names ending case-insensitive with .eng.srt and matching the regular expression filter criteria are output by FINDSTR to handle STDOUT of background command process and captured by cmd.exe processing the batch file.
The main FOR loop processes the list of file names line by line after the started cmd.exe process closed itself. File names can contain spaces characters which is the reason for using the option delims= to define an empty list of delimiters to turn off the default line splitting behavior on spaces/tabs. File names can start with a semicolon and for that reason the option eol=| is used to define the vertical bar as end of line character which no file name can contain ever. So each file name is assigned completely to the specified loop variable I
For each file name is called the subroutine ProcessFile which first outputs the current file name with correct subtitles.
Next a FOR loop is used to remove from the file name the string after last dot which is the file extension according to the definition of Microsoft. The "file extension" string is tested with FINDSTR on being the part which is used as identifier and also as separator string between film title and the meta data of the film in file name. If regular expression does not return a positive match on the current "file extension" string, the file name is truncated at end by removing the current "file extension".
Finally after one or more loop runs the beginning of the file name is found consisting of film name with zero or more dots inside and the string matched by the regular expression. So the environment variable FileNameBegin is for the four examples:
Batman.Beyond.S01E01
Batman.Beyond.S01E02
Exa..mple!.S01E01
example.S01E02
That string part is now used to find the matching file with incorrect subtitles ending case-insensitive with the string -FGT.srt. That is again done starting one more cmd.exe to run DIR to find that file.
The usage of the command DIR to get a list of matching file names first loaded into memory can be important depending on what alass.bat does with the passed file names. That is important especially on FAT file systems like FAT32 or exFAT which do not store the file names in an local specific alphabetic order. The file tables of the file system can be changed on each call of alass.bat if this batch file modifies the srt files and that is not good on using FOR directly to process the files. It can result in skipping some srt files or processing some srt files more than once or in worst case even in an endless running loop. That is the reason for using DIR executed by a command process in background to always get a list of matching file names which does not change anymore while the main FOR loop as well as the last FOR loop run the commands which perhaps result in changing the file tables of the file system.
That solution is definitely not the fasted possible, but a very fail-safe solution and should work for all film titles and all file systems independent on what alass.bat does as long as this batch file does not change the current directory.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
dir /?
echo /?
endlocal /?
exit /?
findstr /?
for /?
goto /?
if /?
set /?
setlocal /?
Here's my approach to v17
#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following setting for the source directory is a name
rem that I use for testing and deliberately includes spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files\t w o"
FOR %%s IN (0 1) DO FOR /L %%t IN (0 1 9) DO FOR %%e IN (0 1) DO FOR /L %%f IN (0 1 9) DO (
FOR /f "delims=" %%o IN (
'dir /b /a-d "%sourcedir%\*.S%%s%%tE%%e%%f.*T.srt" 2^>nul'
) DO (
SET "name=%%~no"
FOR /f "tokens=1,2delims=/" %%p IN ("!name:.S%%s%%tE%%e%%f.=/!") DO (
FOR %%b IN ("%sourcedir%\%%p.S%%s%%tE%%e%%f.*g.srt") do ECHO CALL alass.bat "%%~fb" "%%~fo" "%sourcedir%\%%p.S%%s%%tE%%e%%f.%%q.srt"
)
)
)
GOTO :EOF
Always verify against a test directory before applying to real data.
As ever, the generated lines are simply echoed.
Essentially, for each S and E 00..19, locate the "T" file (otherwise, no point) split the name on the .SssEee. string and find the {1}.SssEee.*g.srt file
mix and match the parts.
...but I still have problems understanding the destination filename...
EDIT: New better version
I added a new version that I think is the fastest and most convenient way to solve this problem:
#echo off
setlocal EnableDelayedExpansion
rem Process "correct" files with eng.srt extension
for %%i in (*.eng.srt) do (
echo Correct sub: %%i
set "correct=%%i"
rem Search for the prefix of this file
set "name="
set "prefix="
for %%k in ("!correct:.=" "!") do if not defined prefix (
set "part=%%~k"
set "name=!name!.!part!"
rem Check if this part have the "S##E##" end of prefix format
if "!part:~0,1!!part:~3,1!!part:~6!" equ "SE" ( rem "S__E__" letters and length match
set /A "S=E=0, S=1!part:~1,2!-100, E=1!part:~4,2!-100" 2>nul
if !S! gtr 0 if !E! gtr 0 ( rem Both ## numbers match: end of prefix found
rem Process the companion "incorrect" *FGT.srt file
set "prefix=!name:~1!"
set "name="
for %%n in (!prefix!.*FGT.srt) do (
echo Incorrect sub: %%n
echo/
REM call alass.bat %%i %%n %%n
set "name=%%n"
)
if not defined name (
echo Warning: Incorrect sub not found
echo/
)
)
)
)
if not defined prefix (
echo Warning: Bad filename format
echo/
)
)
EDIT: New version for the last OP´s revision
This Batch file should solve this question:
#echo off
setlocal EnableDelayedExpansion
rem Count files with same prefix
for %%a in (*.srt) do (
set "name=%%a"
set "prefix="
for %%b in ("!name:.=" "!") do (
set "prefix=!prefix!%%~b."
set /A "count[!prefix:-=_!]+=1"
)
)
rem Process pairs of files
for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do (
if %%b equ 2 (
set "right="
for %%c in ("%%a*.srt") do (
if not defined right (
set "right=%%c"
) else (
set "wrong=%%c"
)
)
call alass.bat !right! !wrong! !wrong!
)
)
Accordingly to your description: "The files have different names like this: Correct sub (example.S01E01.name.ybg.srt). Incorrect sub (differentExample.S01E01.anotherName.wrt.srt)." That is: correct and incorrect names of the same set have the second dot-separated token the same, like S01E01 or S01E02 in the examples shown.
The Batch file below solve such problem:
#echo off
setlocal
for %%i in (*g.srt) do for /F "tokens=2 delims=." %%k in ("%%i") do (
for %%n in (*.%%k.*t.srt) do (
call alass.bat %%i %%n %%n
)
)
NOTE: This part of the answer relates to Revision 17 of the question.
I would do it with the following batch-file, assuming that the first .-separated parts up to the S??E?? pattern of the file names of a pair of files are the same:
#echo off
setlocal EnableExtensions DisableDelayedexpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (target directory)
set "_PREF=*" & rem // (prefix of base file names)
set "_MASK=S??E??" & rem // (middle part of file names without `.`)
set "_FILT=S[0123456789][0123456789]E[0123456789][0123456789]"
set "_SUFF1=*g.srt" & rem // (suffix of 1st file name with extension)
set "_SUFF2=*T.srt" & rem // (suffix of 2nd file name with extension)
set "_TOOL=%~dp0alass.bat"
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop over 1st files:
for %%I in ("%_PREF%.%_MASK%.%_SUFF1%") do (
rem // Reset left part of file name, store currently iterated base name:
set "LEFT=" & set "NAME=%%~nI"
setlocal EnableDelayedExpansion
rem // Loop as many times as there are `.`-separated parts in the base name:
for %%K in ("!NAME:.=" "!") do (
rem // Do the following only as long as the left part is still not found:
if not defined LEFT (
rem // Utilise a `for` loop on the base name to yield `~`-modifiers:
for %%L in ("!NAME!") do (
rem /* Split base name into last part and the rest, the latter
rem of which is going to be used for the next iteration: */
endlocal & set "LAST=%%~xL" & set "NAME=%%~nL"
rem // Determine whether the last part matches the given pattern:
cmd /D /V /C echo(!LAST:~1!| findstr /R /X /I /C:"%_FILT%" > nul && (
rem // Match encountered, so store currently processed path:
set "LEFT=%%~nxL"
)
setlocal EnableDelayedExpansion
)
)
)
rem // Procede further only if a suitable left part of file has been found:
for %%L in ("!LEFT!") do endlocal & if not "%%~L"=="" (
rem // Search for respective 2nd file:
for %%J in ("%%~L.%_SUFF2%") do (
rem /* Store names of both 1st and 2nd file, then call the sub-script
rem utilising the second `%`-expansion established by `call` to
rem avoid doubling of `^`-symbols as well as loss of `%`-signs: */
set "FILE1=%%~I" & set "FILE2=%%~J"
call "%_TOOL%" "%%FILE1%%" "%%FILE2%%" "%%FILE2%%"
rem /* Erase 2nd file to prevent reprocessing of same file pairs in
rem case of re-execution of this script (remove `ECHO` first!): */
ECHO del "%%~I"
)
)
)
rem // Return from target directory:
popd
)
endlocal
exit /B
The trick herein is to use the ~-modifiers (namely ~x and ~n in particular) of for-loop meta-variables to split the file names at . from the back within a loop that iterates as many times as there are .-separated parts in the base names.
This approach correctly handles file names with characters !, ^ and %. You can prove that when you create an interim sub-script alass.bat with the following contents:
#echo off
setlocal DisableDelayedExpansion
echo(%0 %*
endlocal
exit /B
In case the tool alass.bat overwrites the original *T.srt files, which is what I assume, the script deletes the *g.srt files (when removing the upper-case ECHO in front of the related command) in order not to reprocess the same pair of files upon re-execution of the script.
NOTE: This part of the answer relates to Revision 9 of the question.
I would do it with the following batch-file:
#echo off
setlocal EnableExtensions DisableDelayedexpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (target directory)
set "_SUFF1=g" & rem // (suffix for base names of 1st files)
set "_SUFF2=T" & rem // (suffix for base names of 2nd files)
set "_MASK=*%_SUFF1%" & rem // (name search pattern for 1st files)
set "_EXT=.srt" & rem // (extensions for 1st and 2nd files)
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop over 1st files:
for %%I in ("%_MASK%%_EXT%") do (
rem // Store base name of currently iterated 1st file:
set "NAME=%%~nI"
setlocal EnableDelayedExpansion
rem /* Build base name of respective 2nd file; temporarily appending `|` to the
rem name (which cannot occur in a file name) ensures to just replace the very
rem last occurrence of the suffix: */
set "REPL=!NAME!|" & set "REPL=!REPL:%_SUFF1%|=%_SUFF2%!"
rem // Skip in case there is no respective 2nd file:
if exist "!REPL!!_EXT!" (
rem /* Call sub-script with 1st and 2nd file as input files and 2nd one also
rem as output file, preventing delayed expansion but utilising the second
rem `%`-expansion phase established by `call` in order to avoid doubling
rem of `^`-symbols as well as loss of `%`-signs: */
REM call "%~dp0alass.bat" "!NAME!!_EXT!" "!REPL!!_EXT!" "!REPL!!_EXT!"
call "%~dp0alass.bat" "%%NAME%%%%_EXT%%" "%%REPL%%%%_EXT%%" "%%REPL%%%%_EXT%%"
rem /* Erase 2nd file to prevent reprocessing of same file pairs in case of
rem re-execution of the script: */
ECHO del "!NAME!!_EXT!"
)
endlocal
)
rem // Return from target directory:
popd
)
endlocal
exit /B
This approach correctly handles file names with characters !, ^ and %. You can prove that when you create an interim sub-script alass.bat with the following contents:
#echo off
setlocal DisableDelayedExpansion
echo(%0 %*
endlocal
exit /B
If you used the commented-out call command line (with the upper-case REM in front), ^-symbols would become doubled and %-signs would become lost.
In case the tool alass.bat (which is assumed to reside in the same location as this script) overwrites the original *T.srt files, which is what I assume, the script deletes the *g.srt files (when removing the upper-case ECHO in front of the related command) in order not to reprocess the same pair of files upon re-execution of the script.

Remove duplicates from comma separated list in batch file

I have a batch file that (among other things) turns a list like this:
'foo_ph1-1.tif', 'foo_ph2-1', 'foo_ph2-2'
into a list like this, in a local variable called INVNOS:
'fooph1', 'fooph2', 'fooph2'
I want to remove the duplicates from the second list. I've been trying to do this when I create the list, from the answers to this question, to no avail.
Here's how I make the list.
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%f in ("*.tif") do #echo %%~nf>>list.lst
set FNAMES=
set INVNOS=
for /f %%i in ('type list.lst') do (
set FNAMES=!FNAMES!'%%i.jpg',
for /f "tokens=1 delims=-" %%a in ("%%i") do (
set BEFORE_HYPHEN=%%a
set INVNOS=!INVNOS!'!BEFORE_HYPHEN:_=!',
)
)
set "FNAMES=%FNAMES:~0,-2%"
set "INVNOS=%INVNOS:~0,-2%"
echo %INVNOS%
endlocal
Solutions with findstr won't work because I need to initialize INVNOS with an empty string, and I get stuck with the difference between % and '!', and slicing, inside the for loop.
I know this is easy in Python, however I'd like to do it with what's native (Windows 10/Windows Server), so CMD or Powershell.
Any suggestions?
Just to sketch the bigger picture, INVNOS (inventory numbers) is derived from directories full of tif's, so we can check whether or not they exist in some sql database.
I would approach the problem differently:
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%f in (*.tif) do (
for /f "delims=-" %%g in ("%%~nf") do set "~%%g=."
)
for /f "delims=~=" %%a in ('set ~') do set "INVOS='%%a', !INVOS!"
set "INVOS=%INVOS:~0,-2%
echo %INVOS:_=%
The trick is to define variables for each filename (the variableNAMES contain the filenames. A variable can only exist once, so per definition, there are no duplicates)
With another for loop extract the names from the defined variables and join them. The underscores can be deleted in one go instead of removing them from each substring.
When needed, you can delete the variables with for /f "delims==" %%a in ('set ~') do set "%%a=", but they are destroyed anyway when the script ends. (same line when you want to be sure, no variable starting with ~ is defined by accident before you set them)
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
:: The values assigned to these variables suit my system and test environment
SET "sourcedir=u:\your files"
SET "tempfile=%temp%\tempfile.txt"
:: remove variables starting :
FOR /F "delims==" %%a In ('set : 2^>Nul') DO SET "%%a="
(for %%f in ("%sourcedir%\*.tif") do echo %%~nf)>"%tempfile%"
set "FNAMES="
set "INVNOS="
for /f "usebackqdelims=" %%i in ("%tempfile%") do (
set FNAMES=!FNAMES!'%%i.jpg',
for /f "tokens=1 delims=-" %%a in ("%%i") do (
set "BEFORE_HYPHEN=%%a"
SET "before_hyphen=!BEFORE_HYPHEN:_=!"
IF NOT DEFINED :!BEFORE_HYPHEN! set "INVNOS=!INVNOS!'!BEFORE_HYPHEN:_=!', "&SET ":!BEFORE_HYPHEN!=Y"
)
)
set "FNAMES=%FNAMES:~0,-2%"
set "INVNOS=%INVNOS:~0,-2%"
echo %INVNOS%
IF DEFINED tempfile DEL "%tempfile%"
GOTO :EOF
You would need to change the value assigned to sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
%tempfile% is used temporarily and is a filename of your choosing.
The usebackq option is only required because I chose to add quotes around the source filename.
it is standard practice on SO to use the syntax set "var=value" for string
assignments as this ensures stray trailing spaces on the line are ignored.
Evil trailing space on OP's code set INVNOS... within the for ... %%a loop.
Given OP's original filename list, foo_ph1-1.tif foo_ph2_1 foo_ph2-2, the processing should produce fooph1 fooph21 fooph2, not fooph1 fooph2 fooph2 as claimed.
My testing included foo_ph2-2.tif
The code is essentially the same, but first clearing any environment variables that start :, on the Irish principle.
The temporary file nominated is recreated avoiding the (unfulfilled) requirement to first delete it.
BEFORE_HYPHEN is explicitly expunged of underscores before the if not defined test is applied. I selected : because : can't be part of a filename. Once the name is applied to the invnos list, the :!BEFORE_HYPHEN! variable is established to prevent further accumulation of repeat BEFORE_HYPHEN values into invnos.
If you wanted to step up to PowerShell, something like this could be done in a .bat file script. Of course, It would be easier to write and maintain if it were all written in PowerShell.
=== doit.bat
#ECHO OFF
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"(Get-ChildItem -File -Filter '*.tif' |" ^
"ForEach-Object { '''' + $($_.Name.Split('-')[0].Replace('_','')) + '''' } |" ^
"Sort-Object -Unique) -join ','"') DO (
SET "INVNOS=%%~A"
)
ECHO INVNOS is set to %INVNOS%
EXIT /B
Get-ChildItem produces a list of all the *.tif files in the directory. Split() does what "delims=-" does in a FOR loop. The [0] subscript chooses everything up to the first '-' character in the file name. Replace will remove the '_' characters. Sort-Object removed duplicates to produce a unique list. The -join converts the list of names to a single, comma delimited string. The resulting string is stored into the INVNOS variable.
Do you really want APOSTROPHE characters around each name in the list?

How do I create folder from file name and move files into folder?

I need a windows batch file to create a folder based on part of a file name (the part before an underscore) and move any files that start with the folder name into the folder.
I'm not familiar with windows batch files. I've googled and tinkered a solution which works except that I cannot substring the file name at the underscore.
(Yes there are a few similar threads but nothing I could use to exactly answer my question)
FWIW my unsuccessful solution:
#ECHO OFF
setlocal enabledelayedexpansion
SETLOCAL
SET "sourcedir=C:\Development\test"
PUSHD %sourcedir%
FOR /f "tokens=1*" %%a IN (
'dir /b /a-d "TTT*_*.*"'
) DO (
ECHO MD NEED FILE NAME BEFORE UNDERSCORE HERE
ECHO MOVE "%%a" .\NEED FILE NAME BEFORE UNDERSCORE HERE\
)
(Ideally I'd remove the leading 'TTT' from files too but if necessary can create the files without this.)
Try this batch file code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceDir=C:\Development\test"
set "DestDir=C:\Development\test"
for /F "eol=| delims=" %%A in ('dir /B /A-D-H "%SourceDir%\TTT*_*" 2^>nul') do (
for /F "eol=| tokens=1 delims=_" %%B in ("%%~nA") do (
md "%DestDir%\%%B" 2>nul
move /Y "%SourceDir%\%%A" "%DestDir%\%%B\"
)
)
endlocal
The first FOR executes in a separate command process started with cmd.exe /C in background the command line:
dir /B /A-D-H "C:\Development\test\TTT*_*" 2>nul
DIR searches in specified directory for
just non-hidden files because of /A-D-H (attribute not directory and not hidden)
matching the wildcard pattern TTT*_* which could be also just *_*
and outputs to handle STDOUT in bare format because of /B just the file names with file extension, but without file path.
The error message output by DIR to handle STDERR if the specified directory does not exist at all or there is no file matching the pattern is suppressed by redirecting it with 2>nul to device NUL.
Read also the Microsoft documentation about Using Command Redirection Operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.
FOR captures everything written to STDOUT of started command process and processes the captured output line by line.
FOR ignores by default all empty lines (do not occur here) and all lines starting with a semicolon. A file name could begin with a semicolon. For that reason option eol=| is used to redefine end of line character to vertical bar which a file name can't contain, see Microsoft documentation Naming Files, Paths, and Namespaces. In this case on using TTT*_* as wildcard pattern it is not possible that a file name starts with a semicolon, but it would be possible on usage of *_* as wildcard pattern.
FOR would split up also each line into substrings (tokens) using space/tab as delimiters and would assign just the first space/tab separated string to specified loop variable A. This splitting behavior is not wanted here as file names can contain one or more space characters. Therefore the option delims= is used to define an empty list of delimiters which disables line splitting completely and results in assigning entire file name with extension to loop variable A.
The inner FOR processes just the file name (without extension) as string. This time the file name is split up using the underscore as delimiter because of delims=_ with assigning just first underscore delimited string to loop variable B because of tokens=1. Well, tokens=1 is the default on using for /F and so this option string could be removed from code.
So the outer FOR assigns to A for example TTTxy_test & example!.txt and the inner FOR processes TTTxy_test & example! and assigns to B the string TTTxy.
The command MD creates in set destination directory a subdirectory for example with name TTTxy. An error message is output also on directory already existing. This error message is suppressed by redirecting it to device NUL.
Then the file is moved from source to perhaps just created subdirectory in destination directory with overwriting an existing file with same name in target directory of the file.
The inner FOR loop could be optimized away when there are never files starting with an underscore or which have more than one underscore after first part of file name up to first underscore.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceDir=C:\Development\test"
set "DestDir=C:\Development\test"
for /F "eol=| tokens=1* delims=_" %%A in ('dir /B /A-D-H "%SourceDir%\TTT*_*" 2^>nul') do (
md "%DestDir%\%%A" 2>nul
move /Y "%SourceDir%\%%A_%%B" "%DestDir%\%%A\"
)
endlocal
Option tokens=1* results in assigning first underscore delimited part of file name to loop variable A and rest of file name to next loop variable B according to ASCII table without further splitting up on underscores.
But please take into account that the optimized version does not work for file names like
_TTTxy_test & example!.txt ... underscore at beginning (ignored by pattern), or
TTTxy__test & example!.txt ... more than one underscore after first part.
The optimized version can be further optimized to a single command line:
#for /F "eol=| tokens=1* delims=_" %%A in ('dir /B /A-D-H "C:\Development\test\TTT*_*" 2^>nul') do #md "C:\Development\test\%%A" 2>nul & move /Y "C:\Development\test\%%A_%%B" "C:\Development\test\%%A\"
Well, the not optimized version could be also written as even longer single command line:
#for /F "eol=| delims=" %%A in ('dir /B /A-D-H "C:\Development\test\TTT*_*" 2^>nul') do #for /F "eol=| tokens=1 delims=_" %%B in ("%%~nA") do #md "C:\Development\test\%%B" 2>nul & move /Y "C:\Development\test\%%A" "C:\Development\test\%%B\"
See also Single line with multiple commands using Windows batch file for an explanation of operator &.
For additionally removing TTT from file name on moving the file the first batch code is modified with using two additional commands SET and CALL:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceDir=C:\Development\test"
set "DestDir=C:\Development\test"
for /F "eol=| delims=" %%A in ('dir /B /A-D-H "%SourceDir%\TTT*_*" 2^>nul') do (
for /F "eol=| tokens=1 delims=_" %%B in ("%%~nA") do (
md "%DestDir%\%%B" 2>nul
set "FileName=%%A"
call move /Y "%SourceDir%\%%A" "%DestDir%\%%B\%%FileName:~3%%"
)
)
endlocal
The file name is assigned to an environment variable FileName. The value of this environment variable cannot be referenced with just using %FileName% because of all references of environment variable values using percent signs are substituted by Windows command processor in entire command block starting with first ( and ending with matching ) before FOR is executed at all. Delayed expansion is usually used in such cases, but that would result here in file names containing one or more exclamation marks would not be corrected processed by the batch file.
The solution is using %% on both sides of FileName environment variable reference instead of % and force a double parsing of the command line by using command CALL.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
dir /?
echo /?
endlocal /?
for /?
md /?
move /?
set /?
setlocal /?
It is really very simple:
#echo off
for /f "tokens=1-2 delims=_" %%i in ('dir /b /a-d "TTT*_*"') do (
if not exist "%%i" mkdir "%%i"
move "%%i_%%j" "%%i\%%j"
)
We split by _ into 2 tokens, %%i everything before _ and %%j everything after.
We simply create folder (if it does not exist) then move the file with only the name after the _ into the new folder.
So as an example file TTT123_File1.txt will create a folder called TTT123 and place the file into it but rename it as File1.txt
You might consider using Tcl/Tk. Tcl/Tk is an open source script language. You can call it as a stand-alone or execute it from a windows batch file. You will need to install it first if you don't have it yet.
The following Tcl script does what you want:
cd "C:/Development/test"
# glob is a tcl command to list all functions that match the requirements
set files [glob TTT*_*]
foreach f $files {
# use the underscore as a separator to split f and store the parts in dir and fnew
lassign [split $f "_"] dir fnew
if {![file exist $dir]} {
file mkdir $dir
}
file rename $f [file join $dir $fnew]
}
In my opinion, this is a very readable script, even if you don't know tcl.
You can call this script from a batch file as:
tclsh script.tcl
if you have saved the script as script.tcl

loop through file saving to variable

I now have the following bat file working (which allows one to add text to the end of each line of a file) -- please see also:
bat file: Use of if, for and a variable all together
#echo off
setLocal EnableDelayedExpansion
IF EXIST "%FileToModify1%" (
for /f "tokens=* delims= " %%a in (%FileToModify1%) do (
echo %%a Note: certain conditions apply >> "%SaveFile1%"
)
)
However, I would like to save each line to a variable (including the new line symbol(s)) and then echo the variable to a file at the end. Since there are several lines in the file it is really inefficient to save to a file with each line.
I tried googling this, but the answers do not fit my situation...
essentially I need the syntax for concatenating and saving to a variable (cumulatively like "+=" in C#), and also using the new lines...
Actually you do not need to put everything into a variable, you just need to place the redirection at another position.
Try this:
#echo off
setlocal EnableDelayedExpansion
if exist "%FileToModify1%" (
for /F "usebackq delims=" %%a in ("%FileToModify1%") do (
echo %%a Note: certain conditions apply
)
) > "%SaveFile1%"
endlocal
Note that empty lines in the original file are ignored by for /F, so they are not transferred to the new file. Also lines starting with ; are ignored by for /F (unless you change the eol option -- see for /?).
I modified the for /F options:
no delims are allowed, so the each line is output as is (with "tokens=* delims= ", leading spaces are removed from each line if present);
usebackq allows to surround the file specification in "" which is helpful if it contains spaces;
Appendix A
If you still want to store the file content into a variable, you can do this:
#echo off
setlocal EnableDelayedExpansion
rem the two empty lines after the following command are mandatory:
set LF=^
if exist "%FileToModify1%" (
set "FileContent="
for /F "usebackq delims=" %%a in ("%FileToModify1%") do (
set "FileContent=!FileContent!%%a Note: certain conditions apply!LF!"
)
(echo !FileContent!) > "%SaveFile1%"
)
endlocal
The file content is stored in variable FileContent, including the appendix Note: certain conditions apply. LF holds the new-line symbol.
Note:
The length of a variable is very limited (as far as I know, 8191 bytes since Windows XP and 2047 bytes earlier)!
[References:
Store file output into variable (last code fragment);
Explain how dos-batch newline variable hack works]
Appendix B
Alternatively, you could store the file content in a array, like this:
#echo off
setlocal EnableDelayedExpansion
if exist "%FileToModify1%" (
set /A cnt=0
for /F "usebackq delims=" %%a in ("%FileToModify1%") do (
set /A cnt+=1
set "Line[!cnt!]=%%a Note: certain conditions apply"
)
(for /L %%i in (1,1,!cnt!) do (
echo !Line[%%i]!
)) > "%SaveFile1%"
)
endlocal
Each line of the file is stored in an array Line[1], Line[2], Line[3], etc., including the appendix Note: certain conditions apply. cnt contains the total number of lines, which is the array size.
Note:
Actually this is not a true array data type as such does not exist in batch, it is a collection of scalar variables with an array-style naming (Line[1], Line[2],...); therefore one might call it pseudo-array.
[References:
Store file output into variable (first code fragment);
How to create an array from txt file within a batch file?]
you can write the output file in one shot:
(
for /l %%i in (0,1,10) do (
echo line %%i
)
)>outfile.txt
(much quicker than appending each line separately)

Trim Date from file name windows script

How do you trim the date from a text file. For example, I have multiple files like:
test_MX_abc_20091011.txt
test_MX_pqrdhdsu_20091011.txt
test_MX_xyieuz_20091011.txt
All files will have test_MX in common but the 3rd part will of different size.
I would like to change into:
test_MX_abc.txt
test_MX_pqrdhdsu.txt
test_MX_xyieuz.txt
I know how to change the file if name is like test_20091011.txt with the below code, But if name has more string along with date, how to do that?
for /F "tokens=1 delims=_" %%i in ("%%~na") do (
move /Y %%~fa %data_in%\%%i%%~xa >nul
)
Thanks in advance.
This rename operation can be done for example with:
#echo off
for /F "tokens=1-3* delims=_" %%A in ('dir /A-D /B test_MX_*.txt') do (
ren "%%A_%%B_%%C_%%D" "%%A_%%B_%%C.txt"
)
Each file name is separated into 4 strings assigned to loop variables A to D with using underscore as separator. The loop variable D takes everything of file name after third underscore.
Or also working for the 3 files:
#echo off
setlocal EnableDelayedExpansion
for /F "delims=" %%F in ('dir /A-D /B test_MX_*.txt') do (
set "ActFileName=%%~nF"
set "NewFileName=!ActFileName:~0,-9!"
ren "%%~F" "!NewFileName!.txt"
)
endlocal
This solution assigns the name of a file without file extension and path to environment variable ActFileName. Next a new environment variable with name NewFileName is defined with name of active file without the last 9 characters (underscore and date string). This modified file name is used next in the rename operation.
Other solutions using commands for, set and ren can be found on Stack Overflow.
Search with the string
[batch-file] for set rename files
and more than 600 results are presented all using more or less something like above.
For details on the used commands, open a command prompt window, execute one after the other following commands and read output help.
dir /?
for /?
ren /?
set /?

Resources