Ok I need a batch file that does the following and for the life of me I cannot seem to get it to work.
Searches the entire C:\ drive for all *.PST files
Copies these files to a network drive location (j:\#PCBackup\PST)
If more than one file exists with the same name, rename instead of overwrite
So if the following exists:
c:\archive.pst
c:\user\archive.pst
the output would be something like:
:\#PCBackup\PST\archive1.pst
:\#PCBackup\PST\archive2.pst
Is this possible?
It can be done as follows:
#echo off
FOR /F "usebackq delims=;" %%I IN (`dir C:\*.pst /s /b`) DO (
IF NOT EXIST "j:\#PCBackup\PST\%%~nxI" (
CALL :COPYFILE "%%I"
) ELSE (
CALL :RENAME "%%I"
)
)
GOTO :EOF
:COPYFILE
COPY "%1" "j:\#PCBackup\PST"
GOTO :EOF
:RENAME
FOR /L %%N IN (1, 1, 1000) DO (
IF NOT EXIST "j:\#PCBackup\PST\%~n1%%N%~x1" (
COPY "%1" "j:\#PCBackup\PST\%~n1%%N%~x1"
GOTO :EOF
)
)
Note that in the last FOR loop, I'm trying to find the next file name to use whenever a file name conflict occurs. I've set the maximum counter value to 1000, which should be more than enough to prevent all similar file name problems.
Related
My operating system is Windows 7 and I have files with names such as:
123.txt
abcd_123.txt
abcd_1234.txt
bcde_123_456.txt
bcde_123_4567.txt
cde_fgh_123_456.txt
cde_fgh_123_4567.txt
I would like for folders to be generated based on the starting parts of these filenames (or without the trailing numbers) and prefaced with a specific character, and for the files to then be sorted accordingly into them. Example result:
#abcd\abcd_123.txt
#abcd\abcd_1234.txt
#bcde\bcde_123_456.txt
#bcde\bcde_123_4567.txt
#cde_fgh\cde_fgh_123_456.txt
#cde_fgh\cde_fgh_123_4567.txt
*123.txt is skipped / not sorted.
This is the code I've come up with so far:
#echo OFF
setlocal enabledelayedexpansion
set var_dir="#Sorted"
for /f "delims=_" %%i in ('dir /b /a-d *_*.txt') do (
mkdir "#Sorted\#%%i" 2>nul
move "%%i_*.txt" "%var_dir%\#%%i" >NUL 2>nul
)
echo Sorting Complete!
#pause
GOTO :EOF
It works, but I am not sure how to:
Ignore filenames that start with a number (0-9).
Obtain strings beyond the first delim (_).
As for the second point, I think the filenames can sometimes be too complex to correctly differentiate which part to use as the name for the folder. Example, it sorts:
cde_fgh_123_4567.txt
Into:
#cde\cde_fgh_123_4567.txt
As such, I was thinking for the algorithm to be something like:
Set Folder Name to
(1) string before (first) "_" if string is greater than 3 characters
OR
(2) entire string before second "_" if first string is less than or equal to 3 characters
Thus, the example above should be changed to:
#cde_fgh\cde_fgh_123_4567.txt
How do I improve my batch code to obtain the desired outcome?
Your specifications are not clear enough. I assumed that the folder name is comprised of the first parts of filename that are not numbers. This method works with your filename examples:
#echo off
setlocal EnableDelayedExpansion
for %%a in (*.txt) do (
call :getFolder "%%~Na"
if defined folder (
if not exist "!folder!\" mkdir "!folder!"
move "%%a" "!folder!"
)
)
goto :EOF
:getFolder filename
set "file=%~1"
rem Separate filename in parts at undescores and
rem collect them while part is not a number (greater than zero)
set "folder="
set "part=%file:_=" & set /A "num=part" & (if !num! equ 0 set "folder=!folder!_!part!") & set "part=%"
if defined folder set "folder=#%folder:~1%"
exit /B
For example, this is the result obtained with your data:
File "123.txt" folder ""
File "abcd_123.txt" folder "#abcd"
File "abcd_1234.txt" folder "#abcd"
File "bcde_123_456.txt" folder "#bcde"
File "bcde_123_4567.txt" folder "#bcde"
File "cde_fgh_123_456.txt" folder "#cde_fgh"
File "cde_fgh_123_4567.txt" folder "#cde_fgh"
You could do the following:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\#Sorted"
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop through matching files:
for /F "eol=| tokens=1,2,* delims=_" %%A in ('dir /B /A:-D-H-S "*_*.txt"') do (
rem // Skip if file name consists of less than two `_`-separated parts:
if not "%%B"=="" (
rem // Check if file name consists of more than two parts:
if "%%C"=="" (
rem // Two parts, so part one becomes directory name:
2> nul md "#%%A"
move "%%A_%%B" "#%%A\%%A_%%B"
) else (
rem // More than two parts:
set "ONE=%%A"
setlocal EnableDelayedExpansion
rem // Check length of part one:
if "!ONE:~3!"=="" (
rem /* Part one contains not more than 3 characters,
rem hence parts one and two become directory name: */
endlocal
2> nul md "#%%A_%%B"
move "%%A_%%B_%%C" "#%%A_%%B\%%A_%%B_%%C"
) else (
rem /* Part one contains more than 3 characters,
rem hence only part one becomes directory name: */
endlocal
2> nul md "#%%A"
move "%%A_%%B_%%C" "#%%A\%%A_%%B_%%C"
)
)
)
)
rem // Return from target directory:
popd
)
endlocal
exit /B
You could quickly do this using this code:
#(SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET "_SrcFolder=C:\Admin\CMD\s-e\tmp"
REM Use for DIR to pre-filter the list as much as possible
SET "_FileGlob=*_*.txt"
REM Used for FindStr Matches a Value that Begins with non numeric characters, followed by an underscore multiple times, followed by any number of numeric characters and underscore and ending in .txt
SET "_FileRegex=!_SrcFolder:\=\\!\\[a-Z][a-Z_]*_[0-9][0-9_]*.txt$"
)
CALL :Main
( ENDLOCAL
EXIT /B
)
:Main
REM Loop through the file sin the directory filtering non-matches and then perform actions based on matches
ECHO."%_FileRegex%"
FOR /F "Tokens=*" %%A IN ('
DIR /S/B/A-D "%_SrcFolder%\%_FileGlob%" ^|
FINDSTR /r "%_FileRegex%"
') DO (
SET "_FileName=%%~nA"
REM Break File Name after the Characters needed for the directory
FOR /F "TOKENS=* delims=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" %%a IN (
"%%~nA"
) DO (
REM Create Folder
IF NOT EXIST "%_SrcFolder%\#!_FileName:_%%a=!\" (
ECHO.&ECHO.== Creating Folder "#!_FileName:_%%a=!"
MD "%_SrcFolder%\#!_FileName:_%%a=!\"
)
REM Move Original File to the New Directory
ECHO. + Moving "%%~nxA" TO "#!_FileName:_%%a=!"
MOVE /Y "%%A" "%_SrcFolder%\#!_FileName:_%%a=!\%%~nxA" >NUL
)
)
GOTO :EOF
I have a directory "D:\logs" consisting of many log files eg: HRS.log, SRM.log, KRT.log, PSM.log etc.
Each of this log file may or may not have a string "found" inside them. If the log file contains the string "found", then i have to generate "fileName.found" eg: "SRM.found" file in "D:\flags"folder.
i have written the following script but not able to proceed further:
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%f IN ("D:\logs\*.log") do (
findstr /i "found" "%%f" >NUL
if "!ERRORLEVEL!"=="0" (
echo.>"D:\flags\%%f.found"
)
)
pause
exit /b
)
#echo off
for /f "delims=" %%A in (
'2^>nul findstr /i /m "found" D:\logs\*.log'
) do echo( > "D:\flags\%%~nA.found"
findstr /i can search in the files for the case insensitive string found and use of argument /m which allows for return of only the filepaths that contain that string. This can make it more efficient as the for /f command returns the filepaths only of interest.
%%~nA uses a for variable modifier of n which is the filename with no extension. View for /? for more information about the modifiers available.
ok, so here's the solution i found for the above question that i asked:
#echo off
setlocal enabledelayedexpansion
for %%f IN ("D:\logs\*.log") do (
find "found" "%%f" >NUL
if "!ERRORLEVEL!"=="0" (
echo.>"D:\flags\%%~nf.found"
)
)
pause
exit /b
)
I have a file with a lot of text.
EG
Hello
This is my file
this is the end of the file
I need a script that will search the file and pull out all words (just the words and not the line into another file) that contain for example the letter e
In this case the new file would look like
Hello
file
the
end
the
file
It may also need to search for as another example bh. (including the full stop) so a file with the following
hello
bh.ah1
my file
the end
would produce a file with
bh.ah1
hope this is enough detail
#ECHO OFF
SETLOCAL
SET "target=%~1"
FOR /f "delims=" %%a IN (q22560073.txt) DO CALL :findem %%a
GOTO :EOF
:findem
SET candidate=%1
IF NOT DEFINED candidate GOTO :EOF
ECHO %1|FIND /i "%target%" >NUL
IF NOT ERRORLEVEL 1 ECHO(%1
shift
GOTO findem
I used a file named q22560073.txt for my testing.
To find the text string, use
thisbatch text
so
thisbatch e
would find the first list and
thisbatch bh.
the second.
(I combined both sample testfiles as q22560073.txt)
the /i in the find command makes the test case-insensitive.
To output to a file, simply use
thisbatch text >"filename"
where the "rabbits ears" are only required if the filenames contains spaces and other problematic characters, but do no harm in any case.
This should work for a target of any alphabetic or numeric combination plus full stop. It will not work with characters that have a special meaning to cmd.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "target=%~1"
FOR /f "delims=" %%a IN (q22560073.txt) DO (
SET "line=%%a"
SET "line=!line:(= !"
SET "line=!line:)= !"
CALL :findem !line!
)
GOTO :EOF
:findem
SET candidate=%1
IF NOT DEFINED candidate GOTO :EOF
ECHO %1|FIND /i "%target%" >NUL
IF NOT ERRORLEVEL 1 ECHO(%1
shift
GOTO findem
revised on further information.
#echo off
set "searchfor=bh."
for /f "delims=" %%i in (t.t) do (
for %%j in (%%i) do (
echo %%j|find "%searchfor%" >nul && echo %%j
)
)
for every line (%%i) do
for every word in this line (%%j) do
if searchstring found then echo word
EDIT to your comment: replace the ( with a space in the line, before processing words
#echo off
setlocal enabledelayedexpansion
set "searchfor=bh."
for /f "delims=" %%i in (t.t) do (
set t=%%i
set t=!t:(= !
for %%j in (!t!) do (
echo %%j|find "%searchfor%" >nul && echo %%j
)
)
You can do this for more characters with additional lines like set t=!t:(= ! (replace ( with )
This code works well for me to move files with the same filename but different extension into a subfolder.
So the current logic is: If an NC1 file has the same filename as a PDF file then move that NC1 to its respective subfolder.
But my files don't have the same filename.
The following are 2 example files:
"f100.nc1"
"999-P-f100 - PLATE - Rev 0 - 287x200.pdf"
How do I change this code to follow this logic: If a PDF filename CONTAINS the filename of an NC1 file then move that NC1 to its respective subfolder.
for %%j in ("..\2PDF_Outsourced\1PDF_Heavy\*.pdf") do (
if exist "%%~nj.nc1" (
move /-y "%%~nj.nc1" "\2NC1_Outsourced\1NC1_Heavy"
)
)
for %%j in ("..\2PDF_Outsourced\1PDF_Light\*.pdf") do (
if exist "%%~nj.nc1" (
move /-y "%%~nj.nc1" "\2NC1_Outsourced\1NC1_Light"
)
)
Thank you in advance for any help. I have been stuck at this stage for a while and am struggling to understand delimiters, strings, and wildcards.
The trick is to reverse your logic. Iterate the .nc1 files and then look to see if matching .pdf exists (with wildcards). Adding a 2nd loop for Heavy and Light avoids code replication.
for %%F in ("*.nc1") do for %%P in (Heavy Light) do (
if exist "..\2PDF_Outsourced\1PDF_%%P\*%%~nF*.pdf" (
if exist "%%F" move /-y "%%F" "\2NC1_Outsourced\1NC1_%%P"
)
)
EDIT
I added a 2nd IF EXIST to the code above just in case the name matches both Heavy and Light pdf files.
If Endoro's concern about ignoring names that match a substring of a larger word is valid, then the above can be extended:
for %%F in ("*.nc1") do (
set "name=%%~nF"
setlocal enableDelayedExpansion
for %%C in (. [ ^^) do set "name=!name:%%C=\%%C!"
for %%N in (!name!) do (
endlocal
for %%P in (Heavy Light) do for /f "eol=: delims=" %%A in (
'dir /b /a-d "..\2PDF_Outsourced\1PDF_%%P\*%%~nF*.pdf"^|findsdr /i "\<%%N\>"'
) do if exist "%%F" move /-y "%%F" "\2NC1_Outsourced\1NC1_%%P"
)
)
you can use findstr:
REM this is true
echo(999-P-f100 - PLATE - Rev 0 - 287x200|findstr "\<f100\>"
REM this is false
echo(999-P-f1000 - PLATE - Rev 0 - 287x200|findstr "\<f100\>"
REM also false
echo(999-P-f10 - PLATE - Rev 0 - 287x200|findstr "\<f100\>"
you can put this in a for loop:
for %%i in (*.nc1) do (
for /f "delims=" %%j in ('dir /a-d /b "..\2PDF_Outsourced\1PDF_Heavy\*.pdf"^|findstr /i "%%~ni"') do (
not tested :
#echo off
call :copy_pdf "\2NC1_Outsourced\1NC1_Light"
call :copy_pdf "\2NC1_Outsourced\1NC1_Heavy"
goto :eof
:copy_pdf [%1 - directory to search]
if not exist "%~1\" echo dir not exist >2 & exit /b 1
pushd "..\%~1"
for %%N in (*.nc1) {
for /f "delims=" %%P in ('dir /b /s /a:-d *.pdf^| find /i "%%~nN"') do (
move /-y "%%~nP.nc1" "%~1"
)
)
popd
this is a brute force approach that can help you get started
for %%a in ("*.nc1") do (
for %%b in ("*.pdf") do (
echo.%%~nb | findstr /i /c:"%%~na" 1>nul
if not errorlevel 1 (
echo %%~na found in %%~nb
)
)
)
if you have many NC1 and PDF files then you may need to optimize this method (for example by storing the list of PDF files)
How could one select a random line of text from a text file and set it in a variable to use?
The Batch program below is Eitan's solution slightly modified to run faster:
#echo off
setlocal EnableDelayedExpansion
set INPUT_FILE="test.txt"
:: # Count the number of lines in the text file and generate a random number
for /f "usebackq" %%a in (`find /V /C "" ^< %INPUT_FILE%`) do set lines=%%a
set /a randnum=%RANDOM% * lines / 32768 + 1, skiplines=randnum-1
:: # Extract the line from the file
set skip=
if %skiplines% gtr 0 set skip=skip=%skiplines%
for /f "usebackq %skip% delims=" %%a in (%INPUT_FILE%) do set "randline=%%a" & goto continue
:continue
echo Line #%randnum% is:
echo/!randline!
Like it is already mentioned here in StackOverflow, among others, %RANDOM% expands to a random number between 0 and 32767.
You can use this mechanism to generate a random line number. However, to make it a valid line number you will have to normalize it by the the number of lines in the input text file.
Here's a simple script that shows how to do it:
#echo off
setlocal enabledelayedexpansion
set INPUT_FILE="test.txt"
:: # Count the number of lines in the text file and generate a random number
set lines=0
for /f "usebackq" %%a in (%INPUT_FILE%) do set /a lines+=1
echo %RANDOM% >nul
set /a randnum=%RANDOM% * !lines! / 32768 + 1
:: # Extract the line from the file
set lines=0
for /f "usebackq tokens=*" %%a in (%INPUT_FILE%) do (
set /a lines+=1
if !lines!==!randnum! set randline=%%a
)
echo Line #!randnum! is:
echo.!randline!
Here's yet another approach. It reads the file name from the command line and uses a FOR /L loop to get to the calculated line number:
#ECHO OFF
FOR /F "" %%I IN ('FIND /C /V "" ^<%1') DO SET /A lines=%%I
SET /A skip=%RANDOM%%%lines
<%1 (
FOR /L %%I IN (1,1,%skip%) DO (
SET /P line=
)
SET line=
SET /P line=
)
ECHO(%line%
The FOR /F loop simply gets the number of lines in the file (the method is borrowed from #Aacini's answer).
A rather simplistic formula then calculates the number of lines to skip in the file.
Next, the file is read. The FOR /L loop merely consumes the specified number of lines using a SET /P instruction. Following the loop, one more SET /P command reads the line that is eventually ECHOed.
The above implementation is just to show the basic idea. It is not without issues, but some of them could easily be resolved:
There's no testing whether the parameter is indeed supplied. If it is absent, the script will break. You could add the necessary check at the beginning of the script like this:
IF "%~1"=="" GOTO :EOF
If there's no parameter, this command terminates the script by sending the control to the end of the script (GOTO :EOF).
The file specified might not exist. Again, you could test that at the beginning, just after verifying that the parameter is supplied, to terminate the script if necessary:
IF NOT EXIST %1 GOTO :EOF
If the file is empty, the lines will be 0 and the subsequent expression using it will run into a division by zero error. Therefore, you'll also need to test the resulting line count (and prevent the script from running further if the count is indeed 0). You can do that by adding the following line just after the FOR /F loop:
IF %lines%==0 GOTO :EOF
Like I said, the formula is somewhat simplistic. It doesn't produce a number greater than 32767, which is the limitation of %RANDOM%. That might well be enough for you, but in case it is not, you could extend the range to 230-1 using two %RANDOM% calls like this:
SET /A skip=(%RANDOM%*32768+%RANDOM%)%%lines
So, here's the same script again, amended to address the above mentioned issues:
#ECHO OFF
IF "%~1"=="" GOTO :EOF
IF NOT EXIST %1 GOTO :EOF
FOR /F "" %%I IN ('FIND /C /V "" ^<%1') DO SET /A lines=%%I
IF %lines%==0 GOTO :EOF
SET /A skip=(%RANDOM%*32768+%RANDOM%)%%lines
<%1 (
FOR /L %%I IN (1,1,%skip%) DO (
SET /P line=
)
SET line=
SET /P line=
)
ECHO(%line%
One other note is that, if you like, you can add messages explaining the reason for the premature termination of the script. Basically, wherever you want to add the message, you'll just need to replace the single
GOTO :EOF
with
(ECHO your message & GOTO :EOF)
For instance:
IF NOT EXIST %1 (ECHO Error! File not found & GOTO :EOF)