do some math/manipulate 'find' output in a windows batch file - batch-file

I am trying to output a file (count.txt) with the name of each csv and tab file in the current directory and the number of lines in that file. Although the tab file needs its value divided by 4. Also, I would like to get rid of the 10 dashes 'find' outputs and maybe rework the filename. How do i grab the out put of find and manipulate it?
del count.txt
for %%f in (*.csv) do (
find /c /v "" %%f >> count.txt
)
for %%f in (*.tab) do (
::need to divide the next value by 4
find /c /v "" %%f >> count.txt
)

edited to correct errors and adapt to comments
#echo off
setlocal enableextensions disabledelayedexpansion
rem Define extensions to handle
set "extensions=.tab .csv"
rem Define dividers for each extension
set ".tab=4"
set ".csv=1"
rem Retrieve starting folder from command line
rem If not present, assume current folder
set "root=%~1" & if not defined root set "root=%cd%"
rem For each folder starting at the indicated root
rem Change current path to the folder that will be processed and
rem (%%X) For each extension in list
rem (%%a/%%b) For each matching file/lines number processed by find command
rem Calculate the number of lines
rem Echo file information
rem Return to previous active directory
(for /r "%root%" /d %%F in (.) do (
pushd "%%~fF" && (
for %%X in (%extensions%) do if exist ".\*%%~X" (
for /f "tokens=2,3 delims=\:" %%a in ('
find /c /v "" ".\*%%~X" 2^>nul
') do (
set /a "numLines=%%b / %%~xa"
setlocal enabledelayedexpansion
for %%c in (!numLines!) do (
endlocal
echo [%%~fa] [%%c]
)
)
)
popd
)
)) > count.txt
Where the task of each of the for loops is
%%F will recurse the folder hierarchy from the indicated starting point
%%X will iterate over the list of extensions to handle
%%a will execute the find command over all the indicated files and split each line using a backslash and a colon. The slash is included in the output of the find command because we use a relative reference to each file (.\*.csv). That way the initial dashes and space are removed. Then the colon is used to separate the file name from the line count. At the end %%a will hold the file name and %%b the line count.
Once we have the required elements, we will need delayed expansion active to access the result of the calculation. The file extensions is used to determine the divider using variables defined for each case.
%%c Having delayed expansion active generates a problem with files that contain an ! it its name, so we need to disable (endlocal) the delayed expansion before executing the echo, so an aditional for command is used to hold the value of the number of lines that need to be echoed. With delayed expansion disabled, the information is echoed.
The outer for loop is enclosed in parenthesis so we can redirect all the output opening the output file only once instead of one open/write operation for each file.

Related

Can I use FOR /L counter variable in the FOR command?

::for /l %%n in (0, 1, 6) do (
for /F "skip=1 delims=" %%i in (path.txt) do set "dirvar=%%i"&goto nextline
:nextline
for /F "skip=1 delims=" %%i in (file.txt) do set "filevar=%%i"&goto nextline
:nextline
for /F "skip=1 delims=" %%i in (dotonefile.txt) do set "dotvar=%%i"&goto nextline
:nextline
SET dirvar=%dirvar%
SET filevar=%filevar%
SET dotvar=%dotvar%
SET dirfile=%dirvar%%filevar%
SET dirdotfile=%dirvar%%dotvar%
IF EXIST %dirfile% (
del %dirdotfile%
) ELSE (
rename %dirdotfile% %dirfile%
)
::)
My batch script above works fine in that it runs one time. It reads the 2nd line from three separate text files into variables. Then it tests to see if a filename is in a directory and if it is named IMG001.jpg it deletes IMG001.1.jpg. in the same directory. If IMG001.jpg is NOT found in the directory, it renames IMG001.1.jpg in the directory to IMG001.jpg.
path.txt is just a text file with a list of folder paths like:
F:\My Pictures\2005-Misc\
F:\My Pictures\2006-Misc\
F:\My Pictures\2007-Misc\
file.txt is just a text file with a list file names, where line 1 is a file in the directory that's also line 1 of the path.txt file. So there could be a IMG001.jpg in the 2005-Misc folder, could be a IMG001.jpg in the 2006-Misc folder, and there could be a IMG001.jpg in the 2007-Misc folder:
IMG001.JPG
IMG001.JPG
IMG001.JPG
Similarly with dotonefile.txt, it's a list of filenames that ARE in the corresponding directory listed in path.txt. So there IS a IMG001.1.jpg in folder 2005-Misc, there's one in 2006-Misc, and there's one in 2007-Misc.
IMG001.1.JPG
IMG001.1.JPG
IMG001.1.JPG
I want to loop this script and repeat it so it reads in lines 1 through n (n can be hard coded, above it is currently 7) from the text files to variables, then tests and renames for each filename.
I tried uncommenting the first and last lines and then in the three for loops, I replaced the hardcoded "1" with "%%n" but the batch file won't run erroring with "the sntax of the command is incorrect". Below is my attempt that doesn't work. Any advice on how to tweak it to run? I've tried all kinds of combinations of making a new count variable that increments by 1 at the end, using delayed expansion in various forms of variables, nothing works.
for /l %%n in (0, 1, 6) do (
for /F "skip=%%n delims=" %%i in (path.txt) do set "dirvar=%%i"&goto nextline
:nextline
for /F "skip=%%n delims=" %%i in (file.txt) do set "filevar=%%i"&goto nextline
:nextline
for /F "skip=%%n delims=" %%i in (dotonefile.txt) do set "dotvar=%%i"&goto nextline
:nextline
SET dirvar=%dirvar%
SET filevar=%filevar%
SET dotvar=%dotvar%
SET dirfile=%dirvar%%filevar%
SET dirdotfile=%dirvar%%dotvar%
IF EXIST %dirfile% (
del %dirdotfile%
) ELSE (
rename %dirdotfile% %dirfile%
)
)
The main problem is that Windows command processor cmd.exe does not support labels inside command blocks which are parsed completely before executing the command making use of the command block. Please read for details How does the Windows Command Interpreter (CMD.EXE) parse scripts?
The solutions is using a subroutine.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /L %%N in (0,1,6) do call :ProcessFiles %%N
endlocal
goto :EOF
:ProcessFiles
if not %1 == 0 ( set "SkipOption=skip=%1 " ) else ( set "SkipOption=" )
set "DirVar="
for /F "%SkipOption%eol=| delims=" %%I in (path.txt) do set "DirVar=%%I" & goto GetFileVar
:GetFileVar
set "FileVar="
for /F "%SkipOption%eol=| delims=" %%I in (file.txt) do set "FileVar=%%I" & goto GetDotVar
:GetDotVar
set "DotVar="
for /F "%SkipOption%eol=| delims=" %%I in (dotonefile.txt) do set "DotVar=%%I" & goto CheckFile
:CheckFile
set "DirFile=%DirVar%%FileVar%"
set "DirDotFile=%DirVar%%DotVar%"
if exist "%DirFile%" (
del "%DirDotFile%"
) else (
rename "%DirDotFile%" "%DirFile%"
)
goto :EOF
A smarter approach would be using this batch file code without usage of text files at all.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir "F:\My Pictures\*.1.JPG" /A-D /B /S 2^>nul') do (
for %%J in ("%%~dpnI") do (
if exist "%%~dpnJ%%~xI" (
del "%%I"
) else (
ren "%%I" "%%~nJ%%~xI"
)
)
)
endlocal
The FOR loop starts with %ComSpec% /C one more cmd.exe command process in background to execute the command line:
dir "F:\My Pictures\*.1.JPG" /A-D /B /S 2>nul
DIR searches with the specified options for
files because of option /A-D (attribute not directory)
matching case-insensitive the pattern *.1.JPG
in directory F:\My Pictures and all its subdirectories because of option /S
and outputs in bare format because of option /B just
file name with file extension and with full path because of option /S.
DIR would output an error message in case of no file can be found in entire directory tree matching these criteria. This error message is suppressed by redirecting it to device NUL.
Read the Microsoft article 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 output to handle STDOUT of started command process and processes the captured text line by line after started cmd.exe finished.
FOR ignores empty lines which do not occur here at all. FOR would also ignore lines starting with ; because of being the default for end of line option. As DIR outputs the file names with full path, it is not possible that a line starts with ;. But eol=| is nevertheless used to define the vertical bar as end of line which no folder/file name can contain ever.
FOR splits up by default each line into substrings (tokens) using normal space and horizontal tab character as delimiters. This behavior is not wanted here as file path could contain a space character. For that reason delims= is used to define an empty list of delimiters which disables the line splitting behavior.
The inner FOR is used to get assigned to loop variable J just the string left to .1.JPG.
The IF condition checks if there is already a file *.JPG for current file *.1.JPG in same directory as current file in which case the file *.1.JPG is deleted or otherwise the renamed to *.JPG if this file deletion or file rename operation is permitted at all depending on read-only attribute, file permissions of current account and current file sharing access permissions.
But let us assume the image file names can be any file name matching *.jpg and there can be not only *.1.jpg, but also *.2.jpg to *.99.jpg image files, i.e. any number after a dot before file extension .jpg. In this case DIR is not enough to get the list of file names with file extension and full path. It is additionally necessary to use FINDSTR with a regular expression to filter the list of file names.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir "F:\My Pictures\*.*.jpg" /A-D /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "\.[0123456789][0123456789]*\.jpg$"') do (
for %%J in ("%%~dpnI") do (
if exist "%%~dpnJ%%~xI" (
del "%%I"
) else (
ren "%%I" "%%~nJ%%~xI"
)
)
)
endlocal
First FINDSTR outputs just lines read from STDIN which
matches case-sensitive because of option /I
the regular expression \.[0123456789][0123456789]*\.jpg$
as explicitly declared with option /R.
The regular expression matches a string consisting of a dot, one or more digits, one more dot and the string jpg found at end of line. So a file name like Hello.World.jpg output by DIR is not matched by FINDSTR and therefore not output by FINDSTR and so not processed by FOR.
But a file name like Hello.World.393.jpg is processed and either deleted or renamed to Hello.World.jpg depending on existence of Hello.World.jpg in same 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 /?
del /?
dir /?
echo /?
endlocal /?
findstr /?
for /?
goto /?
if /?
ren /? or rename /?
set /?
setlocal /?
See also Where does GOTO :EOF return to?

searching for text file(s) and adding a line in them with batch file

I am trying to write a batch file that can be used for the following task - search for certain text files and adding a text line in them - below the specified line. I am trying to use the following approach:
dir /s /a /b "%SystemDrive%\config*.ini" >> %userprofile%\temp.txt
for /F "tokens=* delims=," %%G IN (%userprofile%\temp.txt) DO (echo "%%G" >> %userprofile%\temp2.txt)
for /F "tokens=* delims=," %%G IN (%userprofile%\temp2.txt) DO (
SETLOCAL ENABLEDELAYEDEXPANSION
cd %%~dG%%~pG
set inputFile=%%~nG%%~xG
set outputFile=in.tmp
set _strInsert=TCPPortNumber=870
set _strFind=Random=No
FOR /F "usebackq delims=" %%A IN ("%inputFile%") DO (
Echo %%A | Find "%_strFind%" && ECHO %%A>>"%outputFile%" && ECHO %_strInsert%>>"%outputFile%"
IF [!errorlevel!] == [1] ECHO %%A>>"%outputFile%"
)
MOVE /Y "%outputFile%" "%inputFile%" && DEL /F /Q "%outputFile%"
Searching for config*.ini file(s) and writing results into temp.txt. Inserting quotes in case there are spaces in file paths for each line in temp.txt -> temp2.txt. Then for each file path in temp2.txt trying to insert TCPPortNumber=870 below the line Random=No. If I use a "hard" file path this approach works, but with reading the files path from temp2.txt it gives me an error: The system cannot find the file specified.
Can anyone help?
SETLOCAL ENABLEDELAYEDEXPANSION
dir /s /a /b "%SystemDrive%\config*.ini" >> %userprofile%\temp.txt
set outputFile=in.tmp
set _strInsert=TCPPortNumber=870
set _strFind=Random=No
for /F "usebackqdelims=" %%G IN ("%userprofile%\temp.txt") DO (
pushd %%~dpG
FOR /F "usebackq delims=" %%A IN ("%%~nxG") DO (
ECHO %%A>>"%outputFile%"
Echo %%A | Find "%_strFind%" && >>"%outputFile%" ECHO %_strInsert%
)
MOVE /Y "%outputFile%" "%%~nxG"
popd
)
endlocal
The setlocal command should not be within the loop (unless it is matched by an endlocal) because it is not a switch, but establishes a frame, hence within the loop, cmd is establishing many nested local environments.
The three constant strings also should appear outside of the loop since their values do not change.
The second tempfile is not required as it is simply the first with each line quoted.
Since %temp% may contain spaces, the for/f ... %%Gneeds to have the filename quoted, and consequently theusebackqoption. The line is to be accepted into%%Gin its entirity, sodelims=` is required, turning delimiters off.
The substring selectors may be combined, so ~dp delivers the drive and path and ~nx the name and extension.
In the original code, inputfile was being set up, but %inputfile% would be resolved to the value of inputfile at the time the entire block (parenthesised sequence of lines) was parsed not at run-time as the values change through execution of the code. This is the commonly-encountered delayed expansion problem - hundreds of references here on SO.
Since what appears to be needed is to insert a line after a particular line, we can simply reproduce each line then see whether the insertion is required, which simplifies the code. The placement of the redirector to before the echo overcomes the Windows-NT characteristic of selecting the device to be redirected by preceding the redirector with a device number.
Finally, move /y forces the replacement of the input file with the output file, so the output file will no longer exist
I haven't actually tried this code - you should execute it against a small copied subtree of the real data and evaluate it before relying on it.
small fix - changed original cd to pushd and added corresponding popd also removed superfluous %
PUSHD changes the current directory to that specified. POPD restores the original current-directory.
The extra % was a keying error (my normal machine is ill and I have to use the laptop - and boy, do I loathe the keyboard...)

Delete Last 6 Rows From Multiple .txt Files

I have never done a batch file before. I have a few dozen .txt files sitting in a folder (ex. C:\files).
The files all end with 6 rows of text that need to be deleted. A sample would be (note spaces in first line):
var...
'ascending';...
'LIT-xxx,LIT-xxx...
setfunction...
0.33...
getdate...
Additionally, I would like the "new" files to overwrite the current files so that the file names and directory do not change.
abs 10.txt
him 4.txt
lab 18.txt
The following code snippet does exactly what you want, deleting the last six lines from text files:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "FILES=C:\files\*.txt" & rem // (specify file location and pattern)
set /A "LINES=-6" & rem /* (specify number of lines to delete;
rem positive number: delete from begin,
rem negative number: delete from end) */
rem // Standard `for` loop to resolve file pattern:
for %%F in ("%FILES%") do (
rem // Get the count of lines for the current file:
for /F %%N in ('^< "%%~F" find /C /V ""') do set "COUNT=%%N"
rem // Initialise a line index:
set /A "INDEX=-LINES"
rem /* Enumerate all lines of the current file, preserving empty ones
rem by preceding each with a line number, so no line appears empty
rem to the `for /F` loop; the line number is split off later on;
rem in addition, the current file is emptied after being read: */
for /F "delims=" %%L in ('
findstr /N /R "^" "%%~F" ^& ^> "%%~F" break
') do (
rem // Increment index, get text of currently iterated line:
set /A "INDEX+=1" & set "LINE=%%L"
rem // Toggle delayed expansion to preserve exclamation marks:
setlocal EnableDelayedExpansion
rem // Check index value and write to current file conditionally:
if !INDEX! GTR 0 if !INDEX! LEQ !COUNT! (
rem // Split off line number from line text:
>> "%%~F" echo(!LINE:*:=!
)
endlocal
)
)
endlocal
exit /B
This approach does not use temporary files in order to avoid any name conflicts. However, due to the fact that there are multiple file write operations for every single file, the overall performance is a bit worse than when writing all data to a temporary file at once and moving it back onto the original file.
Backup your original files to a different backup folder, then run this script:
#echo off
setlocal enabledelayedexpansion
pushd "%temp%\Test"
for %%G in ("*.txt") do (set "break="
(for /f "delims=|" %%H in (%%~G) do (
if not defined break (
echo %%H | findstr /r /b /c:"[ ]*var.*" >nul && set break=TRUE || echo %%H )
)) >> %%~nG_mod.txt
del %%~G & ren %%~nG_mod.txt %%G )
popd
exit /b
It assumed:
your 6 rows of text always start from [any number of spaces]var[any text] row, as you posted in the question, where only one string of such kind is present in any file
other 5 bottom rows don't need to match in every file
you save the files to filter in %temp%\Test, and there are no other unrelated files in that dir.

I need to create a list of folders that contain a specific file type with a batch file

Using a batch file I'm trying to generate a list of only folders within a location that contain a certain file type, let's call it *.abc
at the moment I only know how to echo a DIR command output to a file called folder.lst, I would like to expand on that and try to either
a) echo only folders containing the *.abc file type to folder.lst
b) remove references in folder.lst of folders that do not contain the *.abc file type.
I also tried having a FOR loop check each line to see if a *.abc file existed in that location and skip it, if not, but I just could not get that to work, here is an example of what I had.
setlocal enableextensions enabledelayedexpansion
FOR /F "delims=" %%C in (folder.lst) do (
set temp=%%C
if not exist !temp!\*.abc (goto skip) else (goto resume)
:resume
then my actions live here
:skip
)
but I am aware I am doing something wrong here...I just do not know what.
Maybe the /R form of the for command will help:
for /r "basedir" %%a in (.) do if exist "%%~a\*.abc" (
echo %%a contains .abc file(s)
)
The %%a will be the directories you want (with a trailing \., but you should be able to not care or accommodate this).
There are problems with such of the script as you have posted in that you can'y use labels within a block statement. You've also not provided any examples.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "lastdir="
FOR /r "%sourcedir%" %%a IN (*.abc) DO IF "!lastdir!" neq "%%~dpa" (
SET "lastdir=%%~dpa"
ECHO %%~dpa
echo "!lastdir:~0,-1!"
)
GOTO :EOF
Each directory found will be echoed twice - one with the trailing \ and once without.
You would need to change the setting of sourcedir to suit your circumstances.
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=%cd%"
for %%a in ("%root%") do for /f "delims=: tokens=2" %%b in ('
dir /a-d /s "%root%\*.abc" ^| find "\"
') do echo(%%~da%%~pnxb
This executes a recursive dir command searching for the indicated file type under the starting point (change root variable to suit your needs). For each found folder we retrieve the folder from the dir header that precedes the file list (the lines that contain a backslash).
To separate the path from the rest of the information in the line, the colon is used as delimiter. As this will leave the drive out of the retrieved information, an aditional for is used to retrieve the drive from the folder reference.
From the command line:
for /f "delims=" %a in ('dir /s /b *.abc') do echo %~dpa >> folders.lst
In a batch file:
for /f "delims=" %%a in ('dir /s /b *.abc') do echo %%~dpa >> folders.lst
The above commands will place only the folder names containing the *.abc files in folders.lst.
Notes:
% should be replaced by %% when the command is used in a batch file.
The ~dp part of %~dpa expands %a to a drive letter and path only. Remove the d if you don't want the drive letter. The p path includes a trailing \ which may be interpreted as an escape character by some commands.
The above commands start the search in the current directory. To search from the root of the current drive you can do cd \ first.
For more information see FOR /F Loop command: against the results of another command and Parameters.

Renaming multiples files with .bat

I've been looking for a .bat file in Windows 7 that given a name and a file directory, renames all of the files in that directory to the name that the user pass concatenated to a number that goes from 0 to 9.
For example:
Let's say that i have a directory with two files a.txt and b.txt, i want to change their names to documentX.txt (X being a number from 0 to 9), so at the end the script will change the names to document1.txt and document2.txt
i have tried something like this but without sucess:
#echo off
#set a=1
#set pref=hola
for /D %%f in (C:\*.txt)
do
rename *.txt %pref%%a%
#set /A a = %a%+1
The FOR command cannot span multiple lines unless you use parentheses or line continuation ^. The first opening parenthesis must be on the same line as the IN and the 2nd on the same line as the DO.
You cannot use normal expansion within a parenthesised block of code that also sets the value because the expansion occurs at parse time and the entire block is parsed at once. So the value will be the value that existed before you set it! The solution is to use delayed expansion. That occurs when the line is executed. It must be enabled with setlocal enableDelayedExpansion.
Delayed expansion causes a problem because ! is valid within a file name and FOR variable expansion will be corrupted if it contains ! while delayed expansion is enabled. The solution is to toggle delayed expansion on and off within the loop.
You want a simple FOR without any options. The /D option looks for directories instead of files.
You do not need to expand numeric variables within a SET /A statement.
One last thing - no need for # after you use ECHO OFF.
#echo off
setlocal disableDelayedExpansion
set "n=0"
set "pref=document"
for %%F in (c:\*.txt) do (
set "file=%%F"
set /a n+=1
setlocal enableDelayedExpansion
ren "!file!" "%pref%!n!.txt"
endlocal
)
There is a simpler way to accomplish the task. Use DIR /B to list all the .txt files and pipe the results to FINSTDR /N "^". The FINDSTR will match all files and will prefix each value with a sequential number followed by a colon. Use FOR /F to parse the result into the number and the file name so you can then RENAME.
#echo off
setlocal
set "pref=document"
for /f "tokens=1* delims=:" %%A in ('dir /b /a-d *.txt^|findstr /n "^"') do (
ren "%%B" "%pref%%%A.txt"
)

Resources