Loop through files in a folder and check if they have different extensions - windows

I have a folder that contains files; each document should have .pdf and .xml format. I need to write a BAT file to run from a scheduled task to verify that both documents exist for each.
My logic is:
loop through files in the folder
strip each file to its name without extension
check that same name files exist for both .xml and pdf.
if not mark a flag variable as problem
when done, if the flag variable is marked, send an Email notification
I know how to use blat to sending email, but I'm having trouble to execute the loop. I found a way to get path and file name without extension but can't merge them.
I've used batch files a few time, before but I'm far from an expert. What am I missing?
Here's the code I have so far:
set "FolderPath=E:\TestBat\Test\"
echo %FolderPath%
for %%f in (%FolderPath%*) do (
set /p val=<%%f
For %%A in ("%%f") do (
Set Folder=%%~dpA
Set Name=%%~nxA
)
echo Folder is: %Folder%
echo Name is: %Name%
if NOT EXIST %FolderPath%%name%.xml
set flag=MISSING
if NOT EXIST %FolderPath%%name%.pdf
set flag=MISSING
)
echo %Flag%
pause

There is no need for fancy code for a task such as this:
#Echo Off
Set "FolderPath=E:\TestBat\Test"
If /I Not "%CD%"=="%FolderPath%" PushD "%FolderPath%" 2>Nul||Exit/B
Set "flag="
For %%A In (*.pdf *.xml) Do (
If /I "%%~xA"==".pdf" (If Not Exist "%%~nA.xml" Set "flag=MISSING")
If /I "%%~xA"==".xml" (If Not Exist "%%~nA.pdf" Set "flag=MISSING")
)
If Defined flag Echo=%flag%
Timeout -1

Something like this :
set "FolderPath=E:\TestBat\Test\"
pushd "%FolderPath%"
for %%a in (*.xml) do (
if exist "%%~na.pdf"(
echo ok
) else (
rem do what you want here
echo Missing
)
)
popd

Is this what you want?
#echo off
setlocal enabledelayedexpansion
set "FolderPath=E:\TestBat\Test\"
echo !FolderPath!
for /f "usebackq delims=" %%f in (`dir !FolderPath! /B`) do (
set /p val=<%%f
For %%A in ("%%f") do (
Set Folder=%%~dpA
Set name=%%~nxA
)
echo Folder is: !Folder!
echo Name is: !name!
if NOT EXIST !FolderPath!!name!.xml set flag=MISSING
if NOT EXIST !FolderPath!!name!.pdf set flag=MISSING
)
echo Flag: !flag!
pause
endlocal
You should reformat your code and keep in mind that the grama for batch file is critical. BTW, if you are trying to update the existing batch variable and read it later, you should enable localdelayedexpansion and use ! instead of %.

Keep it simple:
#echo off
pushd "E:\TestBat\Test" || exit /B 1
for %%F in ("*.pdf") do if not exist "%%~nF.xml" echo %%~nxF
for %%F in ("*.xml") do if not exist "%%~nF.pdf" echo %%~nxF
popd
This returns all files that appear orphaned, that is, where the file with the same name but the other extension (.pdf, .xml) is missing. To implement a variable FLAG to indicate there are missing files, simply append & set "FLAG=missing" to each for line and ensure FLAG is empty initially. Then you can check it later by simply using if defined FLAG.
Note: This does not cover the e-mail notification issue. Since I do not know the BLAT tool you mentioned, I have no clue how you want to transfer the listed files to it (command line arguments, temporary file, or STDIN stream?).
In case there is a huge number of files in the target directory, another approach might be better in terms of performance, provided that the number of file system accesses is reduced drastically (note that the above script accesses the file system within the for loop body by if exist, hence for every iterated file individually). So here is an attempt relying on a temporary file and the findstr command:
#echo off
pushd "E:\TestBat\Test" || exit /B 1
rem // Return all orphaned `.pdf` files:
call :SUB "*.pdf" "*.xml"
rem // Return all orphaned `.xml` files:
call :SUB "*.xml" "*.pdf"
popd
exit /B
:SUB val_pattern_orphaned val_pattern_missing
set "LIST=%TEMP%\%~n0_%RANDOM%.tmp"
> "%LIST%" (
rem // Retrieve list of files with one extension:
for %%F in ("%~2") do (
rem /* Replace the extension by the other one,
rem then write the list to a temporary file;
rem this constitutes a list of expected files: */
echo(%%~nF%~x1
)
)
rem /* Search actual list of files with the other extension
rem for occurrences of the list of expected files and
rem return each item that does not match: */
dir /B /A:-D "%~1" | findstr /L /I /X /V /G:"%LIST%"
rem // Clean up the temporary file:
del "%LIST%"
exit /B
To understand how it works, let us concentrate on the first sub-routine call call :SUB "*.pdf" "*.xml" using an example; let us assume the target directory contains the following files:
AlOnE.xml
ExtrA.pdf
sAmplE.pdf
sAmplE.xml
So in the for loop a list of .xml files is gathered:
AlOnE.xml
sAmplE.xml
This is written to a temporary file but with the extensions .xml replaced by .pdf:
AlOnE.pdf
sAmplE.pdf
The next step is to generate a list of actually existing .pdf files:
ExtrA.pdf
sAmplE.pdf
This is piped into a findstr command line, that searches this list for search strings that are gathered from the temporary file, returning non-matching lines only. In other words, findstr returns only those lines of the input list that do not occur in the temporary file:
ExtrA.pdf
To finally get also orphaned .xml files, the second sub-routine call is needed.
Since this script uses a temporary file containing a file list which is processed once by findstr to find any orphaned files per extension, the overall number of file system access operations is lower. The weakest part however is the for loop (containing string concatenation operations).

Related

Loop through folders in subdirectories and combine text files

I am wanting to loop through folders within a subdirectory and combine all text files into one file. I found some answers online but none seems to work. Any help is much appreciated. I have provided what I've found below. In the example below the DummyFolder has multiple subdirectories that contain .txt files that need to be merged into 1 file. I got code 3 to work yesterday but somehow I changed something and it is no longer working for some reason.
Code 1:
#echo off
set "header=C:\Users\user\Desktop\DummyFolder\Headings.txt"
set "folder=C:\Users\user\Desktop\DummyFolder\"
set "tempFile=%folder%\temp.txt"
for %%F in ("%folder%\*.txt") do (
type "%header%" >"%tempFile%"
type "%%F" >>"%tempFile%"
move /y "%tempFile%" "%%F" >nul
)
Also found this code (Code 2):
$startingDir = 'C:\Users\user\Desktop\DummyFolder\'
$combinedDir = 'C:\Users\user\Desktop\DummyFolder\CombinedTextFiles'
Get-ChildItem $startingDir -Recurse | Where-Object {
$txtfiles = Join-Path $_.FullName '*.txt'
$_.PSIsContainer -and (Test-Path $txtfiles)
} | ForEach-Object {
$merged = Join-Path $combinedDir ($_.Name + '_Merged.txt')
Get-Content $txtfiles | Set-Content $merged
}
Also found this code (Code 3):
#echo on
set folder="C:\Users\user\Desktop\DummyFolder\"
for /F %%a in ('dir /b /s %folder%') do (
if "%%~xa" == ".txt" (
(echo/------------------------------
type %%~a
echo/)>>"%~dp0list.txt"
)
)
In CMD you'd do something like this:
#echo off
set "basedir=C:\some\folder"
set "outfile=C:\path\to\output.txt"
(for /r "%basedir%" %f in (*.txt) do type "%~ff") > "%outfile%"
For use in batch files you need to change %f to %%f and %~ff to %%~ff.
In PowerShell you'd do something like this:
$basedir = 'C:\some\folder'
$outfile = 'C:\path\to\output.txt'
Get-ChildItem $basedir -Include *.txt -Recurse | Get-Content |
Set-Content $outfile
There are so many ways to do this. For example, using the Wolfram Language you can:
StringJoin ##
FileSystemMap[
If[FileExtension[#] == "txt", Import[#, "Text"]] &,
"C:\\Users\\user\\Desktop\\DummyFolder\\", Infinity, 1]
An then write the result using
Export[C:\\Users\\user\\Desktop\\, %, "Text"]
You can also do this with Python, Perl, etc.. use PowerShell only if you need to share your solution and want to avoid installers. I would not spend too much time learning 1981 technology (CMD).
Assuming that your source files are located in immediate sub-directories of the root directory DummyFolder and that you want the content of Headings.txt to occur once only on top of the resulting file, you could accomplish your task using the following script:
#echo off
rem // Define constants here:
set "folder=C:\Users\user\Desktop\DummyFolder"
set "header=%folder%\Headings.txt"
set "result=%folder%\merged.txt"
rem // Prepare result file, copy content of header file:
copy "%header%" "%result%" > nul
rem // Enumerate immediate sub-directories of the given root directory:
for /D %%D in ("%folder%\*") do (
rem // Enumerate matching files per sub-directory:
for %%F in ("%%~D\*.txt") do (
rem // Append content of current file to result file:
copy /Y "%result%" + "%%~F" "%result%" /B > nul
)
)
In case your source files are located anywhere in the directory tree DummyFolder, you need to make sure that the header file Headings.txt and the result file merged.txt are not iterated:
#echo off
rem // Define constants here:
set "folder=C:\Users\user\Desktop\DummyFolder"
set "header=Headings.txt"
set "result=merged.txt"
rem // Prepare result file, copy content of header file:
copy "%folder%\%header%" "%folder%\%result%" > nul
rem // Enumerate matching files in the whole given directory tree:
for /R "%folder%" %%F in ("*.txt") do (
rem // Exclude the header file to be re-processed:
if /I not "%%~nxF"=="%header%" (
rem // Exclude the result file to be processed:
if /I not "%%~nxF"=="%result%" (
rem // Append content of current file to result file:
copy /Y "%folder%\%result%" + "%%~F" "%folder%\%result%" /B > nul
)
)
)
This may be a simple answer for what you are looking for, the usebackq is important to allow "" around paths. tokens=* to include all information. To use in a console instead of a batch file change %% to %.
for /f "tokens=*" %%a in ('dir /s /b C:\testpath\*.txt') do (for /f "usebackq tokens=*" %%b in ("%%a") do (echo %%b >> C:\test.txt))
Code 3 is not bad but it won't work with spaces in a path because you use the standard delims as you're not providing one. Also there a several other errors about working with spaces in a path.
The following code works and combine all txt files in all subdirectories. It will create a new file list.txt in the folder where this batch file is located. If there is already an existing list.txt it will be overwritten. Note that it's a batch file:
#echo off
set "folder=C:\Users\user\Desktop\DummyFolder\"
rem create new empty file: list.txt in directory of batch file: %~dp0
break>"%~dp0list.txt"
rem loop through all output lines of the dir command, unset delimns
rem so that space will not separate
for /F "delims=" %%a in ('dir /b /s "%folder%"') do (
rem just look for txt files
if "%%~xa" == ".txt" (
rem don't use the list.txt
if not "%%a" == "%~dp0list.txt" (
rem append the output of the whole block into the file
(echo/------------------------------
type "%%a"
echo/)>>"%~dp0list.txt"
)
)
)
If you don't understand something it's quite easy to find something good on the internet because there are several great batch scripting sites. Further you can always use echo This is a message visible on the command prompt to display something that might be useful e.g. variables etc. With that you can "debug" and look what happens.
Some explanations beyond the comments (rem This is a comment) in the code:
1.
break command:
To clear a file I use the break command which will produce no output at all. That empty output I redirect to a file, read it here: https://stackoverflow.com/a/19633987/8051589.
2.
General variables:
You set variables via set varname=Content I prefer the way as I do it with quotes: set "varname=Content" as it works with redirection characters also. Use the variable with one starting % and one trailing % e.g. echo %varname%. You can read a lot of it on https://ss64.com/nt/set.html. I think ss64 is probably the best site for batch scripting out there.
3.
Redirection > and >>:
You can redirect the output of a command with > or >> where > creates a new file and overwrites existing files and >> appends to a file or create one if not existing. There are a lot more thing possible: https://ss64.com/nt/syntax-redirection.html.
4.
for /f loop:
In a batch file you loop through the lines of a command output by using a for /f loop. The variable that is used will be written with 2 % in front of it, here %%a. I also set the delimiter delimns to nothing so that the command output will not be separated into several tokens.
You can read a lot of details about a for /f loop at: https://ss64.com/nt/for_cmd.html.
5.
Special variable syntax %%~xa and %~dp0:
The variable %%a which hold one line of the dir command can be expand to the file extension only via: %%~xa as explained here: https://stackoverflow.com/a/5034119/8051589. The %~dp0 variable contains the path where the batch file is located see here: https://stackoverflow.com/a/10290765/8051589.
6.
Block redirection ( ... )>>:
To redirect multiple commands at once you can open a block (, execute commands, close the block ) and use a redirection. You could also execute every command and redirect that only that would have the same effect.

Batch file to sort files and list missing

I am trying to write a batch file that would read rows/lines from a txt file containing a list. The batch file would then copy the documents that match, and produce a list of missing files.
So far, the code successfully copies the files that it matches, but it also fills the "Missing.txt" file will exact contents of the input list, rather than simply the missing files.
#echo off
::Requests name of list file to be used by batch file
echo Enter list file name and press enter
set /p var=
mkdir %userprofile%\Desktop\%var%\
set /A lis=1
::Logic to search for files based on contents of list inputted by user at start.
for /f "tokens=*" %%i in (%var%.txt) DO (
call :processline %%i
IF NOT EXIST %%i (echo %%i>>%userprofile%\Desktop\%var%\Missing.txt)
)
pause
::Function called processline
::Assigns a string/value to variable "line"
::Copies a file with name = "line" to the user's desktop
::Renames the file to include a number reference, based on original list being searched
::Increments number for next file to be searched,copies and renamed
:processline
echo line=%*
xcopy /s %*.* %userprofile%\Desktop\%var%
move /Y %userprofile%\Desktop\%var%\%*.pdf %userprofile%\Desktop\%var%\%lis%_%*.pdf
set /A lis=%lis%+1
:eof
I suspect my problem is within the "for" logic, although there might be a way to input missing file names within the processline function.
Any help or advice would be much appreciated.
It's command order: :processline subroutine moves something, maybe including %%i file. I'd use next code snippet:
IF EXIST "%%~i" (
call :processline %%i
) ELSE (
>>"%userprofile%\Desktop\%var%\Missing.txt" echo %%i
)

Write script to search the solution

I'm working on removing a large number of old and unused images from our website. We run ASP.NET with C# code behind, and do our work out of Visual Studio (2013). Right now I'm just going through our images directory and searching the solution for the image file name. While we have some filenames that follow a pattern and can be done in a group using regex, this is still rather tedious. Is there a way that I can write a batch script (or anything) to search the solution for every file in this directory? I can imagine pseudocode like
for file in images_directory
if file not in solution
delete file
but is this possible?
Technically we're just moving the files into another folder to be safe, so I guess the actual pseudocode would be more like
for file in images_directory
if file not in solution
move file to backup_directory
Within your solution file, find all references to .csproj files. Within each .csproj file, find all include lines. Within each included file, find all lines containing references to images. Copy each relevant line to a temporary list. This will make searching faster than searching every .cs file multiple times for every image.
For each graphic file, use findstr to perform a regexp search for /\bfilename\b/i within the temporary list. If not found, use conditional execution to initiate a move of the orphaned image to backup.
Save this with a .bat extension, modify the first three set lines to appropriate values, and give it a shot. By default, it only pretends to move. If you're satisfied that the simulations will produce correct results, remove echo from the move line near the bottom to let the script off its leash.
#echo off
setlocal
set "image_dir=c:\path\to\images"
set "sln_file=c:\path\to\solution\Project1.sln"
set "backup_dir=c:\path\to\backup"
set "remember=%temp%\proj_images.txt"
for %%I in ("%sln_file%") do pushd "%%~dpI"
rem // .sln -> .csproj -> .cs -> images. Find image references and remember.
del "%remember%" >NUL 2>NUL
for /f "delims=" %%I in ('findstr /i ".csproj\>" "%sln_file%"') do (
rem // %%I contains lines matching /.csproj\b/ig
for %%p in (%%I) do if exist "%%~p" (
rem // %%p contains a .csproj filename
for /f "delims=" %%J in ('findstr /i "\<include\>" "%%~p"') do (
rem // %%J contains lines matching /\binclude\b/ig
for %%c in (%%J) do if exist "%%~c" (
rem // %%c contains the filename of an include
findstr /i ".png\> .jpg\> .gif\> .bmp\> .tif\>" "%%~c" >>"%remember%" && (
echo Images referenced within %%~nxc. I'll remember this.
)
)
)
)
)
rem // for each image file in image_dir (recursive)
for /r "%image_dir%" %%I in (*.png *.jpg *.gif *.bmp *.tif) do (
rem // regexp test for /\bfilename.ext\b/i
findstr /i "\<%%~nxI\>" "%remember%" >NUL || (
rem // non-zero exit status of findstr means not found
echo %%~nxI is not referenced by any files included in the solution's projects.
rem // *********************************************************
rem // REMOVE "ECHO" FROM THE FOLLOWING LINE TO ENABLE THE MOVES
rem // *********************************************************
echo move "%%~fI" "%backup_dir%"
)
)
del "%remember%" >NUL 2>NUL
echo Press any key to exit.
pause >NUL
Is this what you had in mind?
All together If I understand correctly You first want to obtain all images files from directory. Using PowerShell:
$imageFiles = Get-ChildItem 'path/to/image/directory' -Recurse | Where-Object { !($_.PSIsContainer) }
This grabs all files excluding Directories. Then:
$solutionText = Get-Content 'path/to/solution/file.csproj' | Out-String
ForEach ($file in $imageFiles ) {
if ($solutionText -match $file.Name) {
# Move to another folder
}
}
The only issue is that you'd need to make sure that the filenames don't have a chance of matching elsewhere on the file giving false positives.

CMD delete files

Perhaps someone can be of help; I have several files with the following naming convention:
fooR1.txt, fooR2.txt, fooR3.txt, . . . , fooR1000.txt
I wish to delete all the files greater than R500. I have several folders and I know how to pass through each folder, but I am not sure how to capture and delete the files with replication 501 and greater. How can I do such?
How about simply:
ren foo500.txt foo499bis.txt
del fooR5??.txt fooR6??.txt fooR7??.txt fooR8??.txt fooR9??.txt fooR10??.txt
ren foo499bis.txt foo500.txt
Not elegant, but efficient.
This will delete all files fooR###.txt where ### is greater than 500.
#echo off
setlocal EnableDelayedExpansion
for %%f in (fooR*.txt) do (
set num=%%~f
set num=!num:~4,-4!
if !num! gtr 500 del /q "%%~f"
)
endlocal
Because your range is open, I've reversed your criteria: delete anything that is not in the range 1-499. Please be aware that this is not exactly equivalent to yours, for example it will also delete a file named fooR001.txt or fooR_something_else.txt
It's also pretty slow.
#echo off
for %%F in (fooR*.txt) do (
echo %%F | findstr /v /r "fooR[1-9]\.txt fooR[1-9][0-9]\.txt fooR[1-4][0-9][0-9]\.txt" >nul && echo del %%F
)
First line (for) enumerates files starting with fooR, then for each file findstr checks if it does not match pattern (/v option) and finally a command is executed if a check (ie does not match) is positive (&& means execute only if previous command was successfull).
Code above will just echo commands, not execute them, so you may safely run it to verify it actually behaves as it should. To actually run delete, just remove echo in front of it.
note: you could actually run this directly from command line in a form of:
#for %F in (fooR*.txt) do #echo %%F | findstr /v /r "fooR[1-9]\.txt fooR[1-9][0-9]\.txt fooR[1-4][0-9][0-9]\.txt" >nul && echo del %F
You would need to make a Batch script for this. Then in the Batch file you could write.
DEL "fooR500.txt"
To delete all files with a .txt ending you would just write:
DEL "*.txt"
That's all I know, but if you want to get it so it does files 500 and higher you would have
to create a variable in Batch that holds the value 500 using:
set Value = 500
and then have it delete file "fooR" + Index + ".txt" so to do that you would have to do:
set "FilePre = fooR"
set "FileW = %FilePre% %Value%"
set "Ex = .txt"
set "FileX = %FileW% %Ex%"
del FileX
Then you will have to make Value go up by one and repeat the process 500 times until it reaches 1000.

Batch file to perform a looped search based on the line items of a text file

I have been reading great posts in this forum and got close to what I want to do but couldn't figure out the exact code.
I want to create a windows batch file to do following:
Perform a looped search for each line item of a text file (this is a list of keyword) to locate files in a a specific directory
For this search partial match is okay.
Each time a file is found, move it to a predefined directory (e.g. C:\temp\search_results)
Thanks.
I'm not running Windows at the moment, so I can only post some ideas, not the solution.
1) Use for /f to iterate over file contents.
2) Use find "%Keyword%" %SourceDir% to get the list of matching files. You will have to parse out file names from the output of find.
2a) As an alternative, you can iterate over files in the source dir (with nested for) and call find for each file, discarding its output and using its exit code (%ERRORLEVEL%) to decide whether the file matches (it will return 0 if there is a match and nonzero if there is no match). Something like this:
for %%F in (%SourceDir%\*) do (
find "%Keyword%" %%F > nul
if not errorlevel 1 (echo File %%F matches) else (echo File %%F does not match)
)
3) Move matching files with move.
There are multiple problems.
FIND /i "%A%" ... can't work, the name of the FOR-Varibale is %%A
And the second proble: With FIND you check the content of the file not the name.
And you should use indention to avoid too much parenthesis.
You better try
FOR /F "tokens=*" %%A IN (%listfile%) DO (
FOR %%f in (%searchdir%\*) do (
set "filename=%%~f"
set replaced=!filename:%%A=!
if !replaced! NEQ !filename! (
echo !filename! contains '%%A'
)
)
)
It tries to replace %%A inside of the filename with .
If the replaced is not equal the filename, the filename must contain %%A
I wrote the following code but not sure if I am in the right track. Here is my setup:
list.txt file contents are (my keywords for the filename search) --
one
two
five
ten
six
f1 folder contains --
four.txt
one.txt
three.txt
I want to move the matching ones to F2 folder, but the code simplicity I am using echo instead.
My code is:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET listfile=D:\batchtest\list.txt
SET searchdir=D:\batchtest\f1
FOR /F "tokens=*" %%A IN (%listfile%) DO (
FOR %%f in (%searchdir%\*) do (FIND /i "%A%" %%f
if errorlevel 1 (
echo Search failed) else (
echo Search successful
)
)
)
)
It is running but not finding matching filenames.
Thanks.

Resources