In Windows, I have a batch file for processing the text file C:\BBB\CCC\list.txt for deciding which files to move. It should move all the files that are in a folder (%folder%) and its subfolders, but only if:
in the name of the folder there is not the year set in input (%excludeName%)
that file is not listed in a text file (%excludeFile%).
In that list.txt I have millions of rows like:
C:\AAA\XXX\ZZZ\image_1.jpg
C:\AAA\XXX\KKK\image_2.jpg
C:\AAA\XXX\ZZZ\pdf_1.pdf
This is the batch file and it's working fine for that purpose:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "folder=C:\AAA"
set "excludeFile=C:\BBB\CCC\list.txt"
set /p excludeName="Year not to delete: "
echo:
set /p rootPath="Backup folder path: "
FOR /f %%a in ('WMIC OS GET LocalDateTime ^| find "."') DO Set _DTS=%%a
Set _date=%_DTS:~0,4%%_DTS:~4,2%%_DTS:~6,2%
if "%rootPath:~-1%"=="\" (set rootPath= %rootPath:~0,-1%)
set localPath=%rootPath%\backup_deleted_media_%_date%
echo:
rem // Change to the root directory:
pushd "%folder%" && (
rem // Loop through all files but exclude those listed in the list file:
for /F "delims= eol=|" %%F in ('
dir /B /S /A:-D "*.*" ^| findstr /V /L /I /X /G:"%excludeFile%"') do (
for /D %%I in ("%%F\..") do (
echo.%%~nxI|findstr /C:"%excludeFile%" >nul 2>&1
if not errorlevel 1 (
echo Found
) else (
if not exist %localPath%\%%~pF md %localPath%\%%~pF
move %%F %localPath%\%%~pF
)
)
)
)
rem // Return from currently iterated directory to root directory:
endlocal
cmd /k
What I need now is another batch file for doing more or less the same but:
folder is not C:\AAA, but is C:\EEE\AAA
I have to change the path of the files in list.txt replacing C:\AAA by C:\EEE\AAA and I have to add .jpg to every single row of that list.txt (because, by mistake, all the files in C:\EEE\AAA and its subfolders have that extension, so like image_1.jpg.jpg, pdf_1.pdf.jpg, ...) before doing the same move. And I want these changes to be in a new file (%newExcludeFile%) instead of the original list.txt.
So I've added:
set "newExcludeFile=C:\BBB\CCC\list_new.txt"
set "SEARCHTEXT=\AAA\"
set "REPLACETEXT=\EEE\AAA\"
and I was doing this for deleting and creating the file %newExcludeFile% from the file %excludeFile%
if exist "%newExcludeFile%" del "%newExcludeFile%"
call jrepl "%SEARCHTEXT%" "%REPLACETEXT%" /x /m /l /f "%excludeFile%" /o "%newExcludeFile%"
Now I'm missing the part for appending .jpg at the end of every record in the file %newExcludeFile% and I was thinking if there is a way for doing it without iterating all the rows again after that replace.
I recommend reading first the Stack Overflow page with the question:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
I post next the simple solution for the task to create the output file C:\BBB\CCC\list_new.txt from input file C:\BBB\CCC\list.txt with replacing C:\AAA at beginning of each line by C:\EEE\AAA and append additionally at end of each line .jpg so that the lines in C:\BBB\CCC\list.txt
C:\AAA\XXX\ZZZ\image_1.jpg
C:\AAA\XXX\KKK\image_2.jpg
C:\AAA\XXX\ZZZ\pdf_1.pdf
become in file C:\BBB\CCC\list_new.txt
C:\EEE\AAA\XXX\ZZZ\image_1.jpg.jpg
C:\EEE\AAA\XXX\KKK\image_2.jpg.jpg
C:\EEE\AAA\XXX\ZZZ\pdf_1.pdf.jpg
This task can be done with:
#echo off
set "excludeFile=C:\BBB\CCC\list.txt"
set "newExcludeFile=C:\BBB\CCC\list_new.txt"
(for /F "usebackq tokens=2* delims=\" %%I in ("%excludeFile%") do echo C:\EEE\AAA\%%J.jpg)>"%newExcludeFile%"
That's really all.
FOR with option /F reads one line after the other from file C:\BBB\CCC\list.txt.
Each non-empty line is split up into substrings using backslash as string delimiters because of option delims=\.
The first substring is drive letter and colon which is ignored because of option tokens=2*, with the exception of looking on starting with end of line character in which case the entire line would be ignored, too.
The first substring is always C: and for that reason the default eol=; can be kept in this use case. There is no line ignored because of end of line character as there is no line starting with a semicolon and so no first substring starting with ;.
The second substring is on each line AAA which is assigned to specified loop variable I according to tokens=2.
But of real interest is the remaining part after C:\AAA\ on each line which is assigned without further line splitting according to * after tokens=2 to next but one loop variable J.
It would be also possible to use the FOR command line:
(for /F "usebackq tokens=1,2* delims=\" %%I in ("%excludeFile%") do echo %%I\EEE\AAA\%%K.jpg)>"%newExcludeFile%"
This variant copies drive letter and colon (first substring) from source to destination file.
I am a fan of JREPL.BAT, but this batch/JScript hybrid is not really necessary for this task.
However, here is the command line doing the same using jrepl.bat as the command line with for /F.
call jrepl.bat "(\\AAA\\.*)$" "\EEE$1.jpg" /F "%excludeFile%" /O "%newExcludeFile%"
It runs a regular expression search for
the string \AAA\ whereby each backslash must be escaped with one more backslash as the backslash is the escape character in a search regular expression and
with .*$ for 0 or more characters up to end of the line
within a marking group defined with ( and )
with replacing each found string with
the string \EEE (with backslash not escaped by JScript exception) and
with back-referencing with $1 the found string to keep it and
with .jpg appended.
Next I want to let all readers of this answer know what was not good coded in the few lines of the batch file posted in the question with the reasons.
It is recommended to modify
if "%rootPath:~-1%"=="\" (set rootPath= %rootPath:~0,-1%)
set localPath=%rootPath%\backup_deleted_media_%_date%
to
if "%rootPath:~-1%" == "\" set "rootPath=%rootPath:~0,-1%"
set "localPath=%rootPath%\backup_deleted_media_%_date%"
The arguments for command IF are specified in this case with 100% correct syntax for a string comparison as described extensively by my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files with
first argument being the first string "%rootPath:~-1%";
space as argument separator;
second argument being the comparison operator ==;
space as argument separator;
third argument being the second string "\".
It can be seen on debugging the batch file that Windows command processor corrects automatically if "%rootPath:~-1%"=="\" with the missing spaces around == to if "%rootPath:~-1%" == "\" with spaces before executing the command IF. Therefore it is best to write the string comparison condition 100% correct in the batch file with spaces around ==.
The space right of = is removed in argument string of command SET in improved command line as rootPath should not be redefined with a space at beginning as described in detail by my answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The argument string of the two SET commands are additionally enclosed in " to work also for paths containing an ampersand character as otherwise & in path would be interpreted as AND operator for an additional command to execute after command SET. See my answer on single line with multiple commands using Windows batch file for meaning of & which is not within a double quoted argument string.
See also my answer on syntax error in one of two almost-identical batch scripts: ")" cannot be processed syntactically here which describes several very common syntax issues. Issue 1 is not enclosing file/folder argument strings in double quotes as required on file/folder argument string containing a space or one of these characters &()[]{}^=;!'+,`~ as described by help of Windows command processor output on running cmd /? in a command prompt window.
The two command lines
if not exist %localPath%\%%~pF md %localPath%\%%~pF
move %%F %localPath%\%%~pF
are also very problematic if localPath is for example defined with the string C:\Temp\Test & Development.
The command IF is designed to run one command on condition being true. There should not be used ( and ) if just a single command needs to be executed on true condition although it is always possible to define a command block with just one command. This is the second common syntax issue on batch file coding.
There are lots of characters in the ASCII table which have no special meaning for neither cmd.exe processing a batch file and nor for its internal command FOR, but beginners in batch file writing tend towards using characters from the small set as loop variable which have a special meaning like a or F which must be used very carefully on being used as loop variables.
The command popd should be used always after a successful execution of pushd, especially if pushd assigns a network resource access with a UNC path to a drive letter. Otherwise it could happen on repeated execution of a batch file that all drive letters are used finally.
It is very good practice to use the fully qualified file name of an executable wherever possible to make a batch file independent on the environment variables PATH and PATHEXT and avoid unnecessary file system accesses by Windows command processor to find the files which are usually specified only with its file name like find or findstr or wmic.
Here is the batch file code as posted in question with all more or less small issues fixed:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "folder=C:\AAA"
set "excludeFile=C:\BBB\CCC\list.txt"
set /P "excludeName=Year not to delete: "
echo:
set /P "rootPath=Backup folder path: "
for /F %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime ^| %SystemRoot%\System32\find.exe "."') do set "_DTS=%%I"
set "_date=%_DTS:~0,4%%_DTS:~4,2%%_DTS:~6,2%"
if "%rootPath:~-1%" == "\" set "rootPath=%rootPath:~0,-1%"
set "localPath=%rootPath%\backup_deleted_media_%_date%"
echo:
rem // Change to the root directory:
pushd "%folder%" && (
rem // Loop through all files but exclude those listed in the list file:
for /F "eol=| delims=" %%I in ('dir /B /S /A:-D "*.*" ^| %SystemRoot%\System32\findstr.exe /V /L /I /X /G:"%excludeFile%"') do (
for /D %%J in ("%%I\..") do (
echo.%%~nxJ|%SystemRoot%\System32\findstr.exe /C:"%excludeName%" >nul
if errorlevel 1 (
if not exist "%localPath%\%%~pI" md "%localPath%\%%~pI"
move "%%I" "%localPath%\%%~pI"
) else echo Found
)
)
popd
)
rem // Return from currently iterated directory to root directory:
endlocal
%ComSpec% /K
There is additionally corrected /C:"%excludeFile%" to /C:"%excludeName%" in most inner FOR loop.
Note: This batch file was not tested by me as I have never executed it!
Related
I need to do this on a list of a few thousand filenames such that the files are renamed with the replaced characters. The intent is to allow the following script to run on ALL names
The problem is names with the "&" sign.
I am using this script to locate all the filenames on a list containing several thousand names which has to be run against multiple folders full of the files to find just the ones on the list. If found I can either copy or move the file onto a flash-drive.
The script is working flawlessly except for names containing the "&". Unfortunately quite a few do. Other than finding a way to escape the & during the search, I could replace it with the any unique word or character long enough to run the search and reversing this after the files are found.
The initial script is like this:
set list=C:\ALIST.txt
pushd %source%
for /f "tokens=*" %%a in (%list%) do (echo.%%a && call :FindIt "%%~a")
goto :End
:FindIt
for /f "tokens=* usebackq" %%b in (`dir /b /s /a-d-s-h "%~1"`) do (call :DoIt "%%~dpb" "%~1")
goto :EOF
:DoIt
set str=%~1
set str=%str:~0,-1%
set file=%~2
rem NOTE: ADD /MOV at end of next line to move instead of COPY
robocopy "%str%" "%dest%" "%file%"
goto :EOF
:End
popd
This was written by a good friend and I am not taking credit for his work.
I looked everywhere to find a way to do this and his script was a Godsend that brought me 95% of the way to an end. The only problem with it is getting it to accept names with the "&" sign or short for that finding a way to replace the "&" sign temporarily long enough to run the script.
Any ideas or alternatives appreciated
The problem seems to be missing quotes
set "str=%~1"
set "str=%str:~0,-1%"
set "file=%~2"
But, without full knowledge of how/why this is done this way (probably it should be shorter), the full code can be written as
pushd %source% && (
for /f "tokens=*" %%a in (%list%) do (
echo(%%a
for /f "delims=" %%b in ('
dir /b /s /a-d-s-h "%%~a"
') do robocopy "%%~dpb\." "%dest%" "%%~a"
)
popd
)
without call and without substring operations.
This seems to work nicely though I can't vouch for spaces and special characters. Place this in a file called This.that.and.the.other.bat
#echo off
setlocal EnableDelayedExpansion
set src=%~n0
set "tgt=%src:.=!p! " & call set "tgt=!tgt!%"
#echo %src%
#echo %tgt%
endlocal
this grabs the output from a remote branch list with git::
for /f "delims=\" %r in ('git branch -r') do (
::then in here I want to get rid of the origin/HEAD -> line of the output
::and do a few git ops on the other lines, which are the names of branches
)
anyhow, I'm finding this frustrating as apparently batch doesn't have regex
here's the code I'm using to do this in bash
for remote in `git branch -r | grep -v '\->'`;
do echo $remote; #...git stuff
done;
the grep removes the "origin/HEAD -> origin/master" line of the output of git branch -r
So I'm hoping to ask how to implement the 'contains' verb
for /f "delims=\" %r in ('git branch -r') do (
if not %r contains HEAD (
::...git stuff
)
)
on a loop variable
this stackoverflow question appears to answer a similar question, although in my attempts to implement as such, I became confused by % symbols and no permutation of them yielded function
EDIT FOR FUTURE READERS: there is some regex with findstr /r piped onto git branch -r
for /f "delims=\" %%r in ('git branch -r^|findstr "HEAD"') do (
echo ...git stuff %%r
)
should give you a start.
Note: %%r, not %r within a batch file - %r would work directly from the prompt.
Your delims=\ filter will produce that portion up to the first \ of any line from git branch -r which contains HEAD - sorry, I don't talk bash-ish; you'd need to say precisely what the HEAD string you want to locate is.
Use "delims=" fo the entire line - omitting the delims option will set delimiters to the default set (space, comma, semicolon, etc.)
Don't use ::-comments within a block (parenthesised statement-sequence) as it's actually a broken label and cmd doesn't appeciate labels within a block. Use REM comments here instead.
The resultant strings output from the findstr (which acts on a brain-dead verion of regex) will be processed through to the echo (or whatever statement you may substitute here) - if there are none, the for will appear to be skipped.
Quite what your target string would be for findstr I can't tell. From the prompt, findstr /? may reveal. You may also be able to use find (find /?) - but if you are using cygwin the *nix version of find overrides windows-native.
I don't know what the git branch output looks like, but with a test case of
test 1
HEAD test \-> 2
test 3
test 4
the following prints all the text lines except the one containing \->
#setlocal enableextensions enabledelayedexpansion
#echo off
for /f "tokens=*" %%r in (d:\test2.txt) do (
set str1=%%r
if "!str1:\->=!"=="!str1!" (
echo %%r
)
)
The if test is fundamentally doing this test: string1.replace("HEAD", "") == string1.
Your loop variable needs to be %r if used directly in the command prompt, but %%r if in a batch file.
The string replacement is a part of environment variables, not loop variables, so it needs to be put into a holding string (str1) to work with. If you have the command extensions enabled ( enableextensions ).
And because environment variable setting operations happen when the script is read, you need to override that with enabledelayedexpansion and using !str1! instead of %str1%, otherwise the value of str1 won't change from one loop to the next.
(PS. Use PowerShell instead. Get-Content D:\test2.txt | Select-String "\->" -NotMatch ).
I am very close to my answer, but I cant seem to find it.
I am using the Findstr function in a batch file to narrow now an entire directory to just one file.
cd ...
findstr /s /m "Desktop" *class.asasm >results1.txt
findstr /m /f:results1.txt "Production" *class.asasm >results2.txt
findstr /n /f:results2.txt "Capabilities" *class.asasm >results3.txt
TASK 1: I need to figure out a way to make findstr search backwards for a fourth string from the line number the third line was found on
TASK 2: I need to write lines 1-the one we arrive at of the file in results2.txt
the insert a .txt file. Then write the rest of the original lines.
I am writing an application in VB.Net with Visual Studios and I am having a difficult time figuring out how to complete this process. I am currently having better luch with having the application run batch files that are written within the application.
The correct solution is to find a tool that does this properly. batch/CMD does not.
Here's a script that tells you the line numbers of the 3rd and 4th match. It's probably not exactly what you want, but it is a demonstration of how one can effectively work with line numbers.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET FILE=TestFile.txt
SET _LINENO=1
SET _MATCHNO=0
SET _THIRDLINENUM=
SET _FOURTHLINENUM=
FOR /F %%l IN (%FILE%) DO (
ECHO %%l | FINDSTR "Target" %_TMP% >NUL
IF NOT ERRORLEVEL 1 (
SET /A _MATCHNO=!_MATCHNO!+1
IF !_MATCHNO!==3 SET _THIRDLINENUM=!_LINENO!
IF !_MATCHNO!==4 SET _FOURTHLINENUM=!_LINENO!
)
SET /A _LINENO=!_LINENO!+1
)
#ECHO %_THIRDLINENUM% : %_FOURTHLINENUM%
Here's what's in TestFile.txt
abcdefg
bcdefgh
Target 1
cdefghi
defghij
fghijkl
Target 2
ghijklm
hijklmn
ijklmno
jklmnop
klmnopq
lmnopqr
mnopqrs
Target 3
nopqrst
Target 4
opqrstu
pqrstuv
qrstuvw
rstuvwx
stuvwxy
tuvwxyz
If you insist on using batch/CMD (and I sometimes do when nothing else is available), and you need to get the text on line #n (otherwise, head and tail would do just fine), you could produce a similar loop but replace the code from FINDSTR down to the end of the IF statement with something that compares _LINENO with some other variable, ECHO'ing the line if it is between the two values. I don't know if IF supports logical operators, so you may have to nest the IF statements, like
IF !_LINENO! GEQ %START_LINE% IF !_LINENO! LEQ %END_LINE% #ECHO %%l
assuming you need this (from your first comment):
I still have not found a way to search starting at line xx rather than 1 or to search in reverse order
you can try this (from the command line):
for /r %i in ("file pattern") do #more "%~i" +starting_line |findstr "search string"
for /r = recursively (if you mean really reverse, please explain)
"file pattern" = files to find, eg. "*class.asasm"
starting_line = search starting line, eg. 7 (more +6)
"search string" = your search pattern, eg. "Desktop"
OR search "Desktop Production Capabilities"
AND search |findstr "Desktop"|findstr "Production"|findstr "Capabilities"
I have a situation where I need to combine four CSV files into one CSV file. This is easy if i just wanted to add them one after the other, but I need to get them to be side by side in the CSV file. I know that all four files have the same number of entries (in the 1000 entry range). I have been working on the following code which works for small files but is extremely inefficient for long files. Is there any easier way to accomplish the same task?
#echo off
setlocal enabledelayedexpansion
Set R=1
Set T=1
Set Y=1
Set U=1
for /f %%a in (test1.txt) do (
set I=!R!
for /f %%b in (test2.txt) do (
set J=!T!
for /f %%c in (test3.txt) do (
set K=!Y!
for /f %%d in (test4.txt) do (
set L=!U!
If !I!==!J! If !J!==!K! If !K!==!L! echo %%a,%%b,%%c,%%d >> TestComplete.txt
Set /a U=U+1
)
Set U=1
Set /a Y=Y+1
)
Set Y=1
Set /a T=T+1
)
Set T=1
Set /a R=R+1
)
Note: I know that the code I have pasted in is using .txt files and not .csv files. I assume what will work for one will work for the other.
Again, The above code seems to work great as long as the files are small. I have trouble (of course) when the files (in this case test1.txt) have around 1000 lines of text.
GnuWin, a collection of ports of GNU (Unix like) tools to Windows, contains paste which does exactly what you want. However, I sense you'd prefer a native Windows answer and I've come up with one, but it's not pretty. Starting with #walid2mi's clever solution and striping it down to coljoin.bat:
#echo off
setlocal enabledelayedexpansion
for /f "delims=" %%a in (%1) do (
set /p line=
echo !line!, %%a
)
You can join four files with:
type a.txt | coljoin b.txt | coljoin c.txt | coljoin d.txt > TestComplete.txt
Time required should increase linearly with file length, avoiding the drastic slowdown of your solution. However the failure mode of this solution is not good when all the files do not have an equal number of lines. The user will usually be prompted to enter missing lines.
I'm having trouble remembering how to read the lines of a text file and perform some kind of task. For example, I'm trying to read the contents of a text file (a set of hostnames) and then perform a TASKLIST on those hosts to see if a process is running.
#echo off
set MachineList=computers.log
FOR /f "delims= " %%a in (%MachineList%) DO GOTO :GETINFO
:GETINFO
echo %%a >>results.log
tasklist /s \\%%a | findstr /i iexplore.exe >>results.log
This doesn't appear to work and I can't figure out what I'm doing wrong. The log entires simply show '%a' for the output. I used to do this sort of stuff all the time years ago. I guess 'don't use it, you lose it' is the order of the day. Seems like a should be setting another variable somewhere but I don't remember where.
You have a couple of small errors. The "delims=" option must have not an ending space. The %%a replaceable parameter must be used in the same line of the FOR command; if it is used in a different line, must be delimited by parentheses that begin in the FOR command, but in your case this is not neccessary because you want to execue just one command in the FOR. The TASKLIST command should be executed with each line of the text file. Finally, the FINDSTR command look at those results for iexplore.exe.
#echo off
set MachineList=computers.log
FOR /f "delims=" %%a in (%MachineList%) DO tasklist /s \\%%a >>results.log
findstr /i iexplore.exe results.log
I think You May Try This:
#echo off
set MachineList=computers.log
FOR /f "delims= " %%a in (%MachineList%) DO CALL :GETINFO
:GETINFO
echo %%a >>results.log
tasklist /s \\%%a | findstr /i iexplore.exe >>results.log