Batch code to turn every line of text to a variable - windows

So I need a code that will take a text file (we'll cal it list.txt) and turn every line into a variable.
To give some context, I have some file names listed in list.txt, which adds and deletes file names occasionally by user request. I want the user of this code to be able to select which document they'd like to open using variables.
For example, if list.txt looks like this
list.txt
loading.txt
test1.txt
test2.txt
test3.txt
test4.txt
Then I'd like an automatic variable for every .txt listed. I then would add a simple if statement to open the file matched with the variable.
Am I making this too complicated for myself or is this the only way to do this?
EDIT:
I am not attempting something like this:
type list.txt
echo.
echo.
set /p GROUPSELECT= Please type out the FULL name of the group listed:
CD %grouplist%
type %GROUPSELECT%
It will display the file contents, and then display the specific file chosen by the input. I'd think that the variable might be easier to do more with later though, just a thought.
Edit2
I tried this:
#Echo OFF
FOR /F "Usebackq Delims=" %%a IN (
"list.txt"
) DO (
set jim=%%a
)
echo %jim%
PAUSE
%jim% will only be the last line in the text file, so how do I make the next step into making them all different?

Give this a try. I have commented each line of code that should explain what it is doing. Let me know if you need any further explanation.
#echo off
REM FOR commnd is parsing the output of the FINDSTR commands.
REM First FINDSTR command is finding non empty lines in the file
REM Second Findstr command is assigning a number to each line of the file
REM The output is split into two tokens and the number is assigned to %%G and the line of the file to %%H
for /F "tokens=1* delims=:" %%G IN ('findstr /V "^$" list.txt ^|findstr /N ".*"') DO (
REM create a variable of the lines in the file
set "file%%G=%%H"
REM Create a menu of the lines in the file
echo %%G %%H
REM Get the number of lines in the output
set "num=%%G"
)
REM Ask user to chose a number
set /P "filenum=Chose a file number 1 to %num%:"
REM Need to enable delayed expansion to use array variables
setlocal enabledelayedexpansion
REM Check if they type in a correct number and display the file
IF DEFINED file%filenum% type !file%filenum%!
endlocal
pause

Is this the sort of thing you're looking for?
#For /F "Delims==" %%# In ('Set # 2^>Nul')Do #Set "%%#="
#For /F Tokens^=1*Delims^=[]^ EOL^= %%# In ('Type "file.txt"^|Find /V /N ""'
)Do #Set "#%%#=%%$"&Echo( %%#. %%$
:Opt
#Echo(
#Set /P "Opt=Choose a document to open>"
#Set #|Findstr /B "#%Opt%=">Nul||GoTo :Opt
#Call Start "" "%%#%Opt%%%"
Just change the name of the text file containing your list on line 2 as needed.

#TheBoy your question is confusingly framed. If you DO NOT want the example you put in the edit, then can you better explain what you DO want??
Do you want to say iterate over the list file, and create a choice screen?
#(SETLOCAL EnableDelayedExpansion
ECHO OFF
SET "_ChoiceList_File=C:\Admin\ChoiceFile.txt"
REM Full Character List to populate Choices
SET "_CharList=0 1 2 3 4 5 6 7 8 9 A B C D E F N X"
)
CALL :Main
( ENDLOCAL
EXIT /B 0
)
:Main
REM Now we can Do Our Choices btu lets do it in a Sub Function.
CALL :MakeChoice _ChoiceResult
ECHO.
ECHO The Index Chosen Was: %_Chosen%
ECHO The Result Matched is: "%_ChoiceResult%"
REM ECHO Here you output "%_ChoiceResult%"
TYPE "%_ChoiceResult%"
GOTO :EOF
:MakeChoice
cls
color 1A
SET %~1="
SET "_Choices="
SET "_Chosen="
SET "_Amount="
SET "_Choice.17.Value=Next Set!"
SET "_Choice.18.Value=EXIT!"
SET "_Choice_Show_Next="
echo. Pick a File:
echo.========================
REM Create Numbered Array of Choices and output Choices to the Screen
FOR /F "tokens=* usebackq" %%A IN ("%_ChoiceList_File%") DO (
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=%%A"
IF !_Amount! EQU 16 (
SET /A "_Amount+=2"
CALL :MakeChoice "%~1"
IF DEFINED _Chosen (
IF !_Chosen! NEQ 17 (
REM IF !_Chosen! NEQ 18 (
GOTO :EOF
REM )
)
SET "_Amount="
SET "_Chosen="
)
)
)
IF NOT DEFINED _Chosen (
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=!"
SET /A "_Amount+=1"
SET "_Choice.!_Amount!.Value=EXIT!"
CALL :MakeChoice "%~1"
)
GOTO :EOF
:MakeChoice
CLS
ECHO.
SET "_Temp="
SET "_Choices="
SET /A "_Skipline= !_Amount! - 1"
REM Create Choice List to Display only the choices needed.
FOR %%A IN (%_CharList%) DO (
SET /A "_Temp+=1"
IF !_Temp! LEQ !_Amount! (
IF !_Temp! EQU !_Skipline! (
ECHO.
ECHO.=============================
)
IF DEFINED _Choice.!_Temp!.Value (
SET "_Choices=!_Choices!%%A"
CALL ECHO. %%A : %%_Choice.!_Temp!.Value%%
)
)
)
ECHO.
CHOICE /C !_Choices! /N /M "What File do you want to choose? "
SET "_Chosen=%ERRORLEVEL%"
SET "%~1=!_Choice.%_Chosen%.Value!"
GOTO :EOF
)

You can try this:
#echo off
setlocal enabledelayedexpansion
set jim=
for /f "delims=] tokens=1*" %%a in ('find /v /n "" ^<list.txt') do (
set jim=!jim! %%b
)
echo Select a file from !jim!
set /p file=
type !file!
pause
This will read all lines from the list.txt and return them within a variable !jim!.

Related

Batch - Get block of text between flags, output and iterate over all files

I've got a bunch of text files in a directory that have a block of text I want to extract between two strings into a new text file of a similar name. I've got the single file working but think I've come unstuck with looping through all .txt files. Maybe at the "goto" command?
Here is the original, single file code I used:
Batch File - Find two lines then copy everything between those lines
~Top Break
foobar
~ more data title
more foobar
~Bottom Break
Garbage data
I have this code that works for a single file called FileNumber1.txt.
#echo off
set "FIRSTLINE=~Top Break"
set "LASTLINE=~Bottom Break"
set "INFILE=FileNumber1.txt"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%INFILE%_MyData.txt" (
rem findstr configured so that each line in a file is given a "1:" number and colon.
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
set "LINE=!LINE:*:=!"
rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(#!LINE!
endlocal
) else (
endlocal
)
)
)
:CONTINUE
endlocal
NewFile1_MyData.txt Output:
foobar
~ more data title
more foobar
I've tried to wrap this in another "FOR" loop that looks for all txt files in the same directory.
This is my code that isn't working.
#echo off
set "FIRSTLINE=~Top Break"
set "LASTLINE=~Bottom Break"
for /F %%f in (*.txt) do (
set "INFILE=%%f"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%INFILE%_OldHeader.txt" (
rem findstr configured so that each line in a file is given a "1:" number and colon.
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
set "LINE=!LINE:*:=!"
rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(#!LINE!
endlocal
) else (
endlocal
)
)
)
endlocal
:CONTINUE
))
The Command window gets to the "for /F" statement and exits.
Mmm... I would change the method to extract the lines for a simpler one based on lines to skip at beginning of file and number of lines to extract. After that, I would use a for to process all files and call a subroutine to extract the lines:
#echo off
setlocal EnableDelayedExpansion
set "FirstLine=~Top Break"
set "LastLine=~Bottom Break"
rem Process all text files in this folder
for %%f in (*.txt) do (
rem Search for First line and Number of lines
set "FirstNum="
for /F "delims=:" %%n in ('findstr /C:"%FirstLine%" /C:"%LastLine%" /N "%%f"') do (
if not defined FirstNum (
set "FirstNum=%%n"
) else (
set /A "LastNum=%%n-FirstNum-1"
)
)
rem Copy the lines
call :CopyLines >"%%~Nf_MyData.out" "%%f", !FirstNum!, !LastNum!
)
ren *.out *.txt
goto :EOF
:CopyLines File, Skip, Num
set "Num=%3"
for /F "usebackq skip=%2 delims=" %%a in (%1) do (
setlocal DisableDelayedExpansion
echo %%a
endlocal
set /A Num-=1
if !Num! equ 0 exit /B
)
exit /B

Creating Each line of text as variable and them constantly changing in a loop in batch

So what I'm trying to do is create a find for multiple people where it in the text file it will say names and numbers like
Example of text file:
Beth
1234567891
Jay
2134456544
This is the best way I can explain what I'm trying to do:
#echo off
set "file=Test1.txt"
setlocal EnableDelayedExpansion
<"!file!" (
for /f %%i in ('type "!file!" ^| find /c /v ""') do set /a n=%%i && for /l %%j in (1 1 %%i) do (
set /p "line_%%j="
)
)
set /a Name=1
set /a Number=2
Echo Line_%Name%> %Name%.txt (Im trying to get this to say line_2 to say 1st line in the text file)
Echo Line_%Number%> %Name%.txt (Im trying to get this to say line_2 to say 2nd line in the text file)
:Start
set /a Name=%Name%+2 (These are meant to take off after 1 so lines 3,5,7,9 so on)
set /a Number=%Number%+2 (These are meant to take off after 2 so lines 4,6,8,10 so on)
Echo Line_%Name%
Echo Line_%Number%
GOTO :Start
so the outcome would be
In Beth.txt:
Beth
1234567891
So every name will be a file name and the first line in a file. I will change it later so I can do a addition in each text file.
Name: Beth
Number: 1234567891
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
SET "filename1=%sourcedir%\q65417881.txt"
rem make sure arrays are empty
For %%b IN (name number) DO FOR /F "delims==" %%a In ('set %%b[ 2^>Nul') DO SET "%%a="
rem Initialise counter and entry array
SET /a count=0
SET "number[0]=dummy"
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
IF DEFINED number[!count!] (SET /a count+=1&SET "name[!count!]=%%a") ELSE (SET "number[!count!]=%%a")
)
rem clear out dummy entry
SET "number[0]=dummy"
FOR /L %%c IN (1,1,%count%) DO (
rem replace spaces with dashes
SET "name[%%c]=!name[%%c]: =-!"
rem report to console rem report to console
ECHO Name: !name[%%c]! Number: !number[%%c]!
rem generate name.txt file
(
ECHO !name[%%c]!
ECHO !number[%%c]!
)>"%destdir%\!name[%%c]!.txt"
)
GOTO :EOF
You would need to change the values assigned to sourcedir and destdir 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.
I used a file named q65417881.txt containing your data for my testing.
The line data read from the file is assigned to %%a is assigned to and number[!count!] alternately. The data is retained in these arrays for use by further processing.
[Edited to include conversion of spaces within names to dashes]
If I understand correctly, you want to precede every second line with Number: + SPACE and every other line with Name: + SPACE. For this you do not need to store each line in a variable first, you can use a single for /F loop lo read the file line by line and process every line individually. There are two possibilities:
Temporarily precede every line with a line number plus : using findstr /N:
#echo off
rem // Loop through lines and precede each with line number plus `:`:
for /F "tokens=1* delims=:" %%K in ('findstr /N "^" "Test1.txt"') do (
rem // Calculate remainder of division by two:
set /A "MOD=%%K%%2" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !MOD! neq 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This will fail when a line begins with the a :.
Check whether numeric representation of current line string is greater than 0:
#echo off
rem // Loop through (non-empty) lines:
for /F "usebackq delims=" %%L in ("Test1.txt") do (
rem // Determine numeric representation of current line string:
set /A "NUM=%%L" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !NUM! equ 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This fails when a name begins with numerals and/or when a numeric line is 0.
And just for the sake of posting something different:
#SetLocal EnableExtensions DisableDelayedExpansion & (Set LF=^
% 0x0A %
) & For /F %%G In ('Copy /Z "%~f0" NUL') Do #Set "CR=%%G"
#For /F "Tokens=1,2* Delims=:" %%G In ('%__AppDir__%cmd.exe /D/V/C ^
"%__AppDir__%findstr.exe /NR "^[a-Z]*!CR!!LF![0123456789]" "Test1?.txt" 2>NUL"
') Do #(SetLocal EnableDelayedExpansion
(Set /P "=Name: %%I!CR!!LF!Number: " 0<NUL & Set "_="
For /F Delims^=^ EOL^= %%J In ('%__AppDir__%more.com +%%H "%%G"') Do #(
If Not Defined _ Set "_=_" & Echo %%J)) 1>"%%I.txt" & EndLocal)
This file should be run with the Test1.txt file in the current working directory. It is important that along side Test1.txt, there are no other .txt files with the same basename followed by one other character, (for example Test1a.txt or Test12.txt). Should you wish to change your filename, just remember that you must suffix its basename in the above code with a ? character, (e.g. MyTextFile.log ⇒ MyTextFile?.log).
I had the rare opportunity to verify that this script worked against the following example Test1.txt file:
Beth
1234567891
Jay
2134456544
Bob
2137856514
Jimmy
4574459540
Mary
3734756547
Gemma
6938456114
Albert
0134056504

Create a .bat file based on common column and out put will be selective column

I am completely new to .bat code. I want to merge two csv file based on common column and selective columns for the output file.
For ex
file1.csv
id,name,roll
1,x,12
2,y,13
file2.csv
id,class,subject
1,V,english
2,III,Math
Output will be
result.csv
id,name,class,subject
1,x,V,english
2,y,III,Math
Below is the code I started. My problem is how to use the common column and select the column.
#echo off
set "tmpfile=%temp%\result.tmp"
set "csvfile=result.csv"
copy nul "%tmpfile%" >nul
echo.
echo Processing all CSV files...
set "header="
for %%a in (%1) do (
if not "%%a"=="%csvfile%" (
set /p =Processing %%a...<nul
for /f "tokens=1* usebackq delims=," %%b in ("%%a") do (
if /i "%%b"=="Keyword" (
if not defined header (
set /p =Found header...<nul
set "header=%%b,%%c"
)
) else (
title [%%a] - %%b,%%c
findstr /b /c:"%%b" /i "%tmpfile%">nul || echo %%b,%%c>>"%tmpfile%"
)
)
echo OK
)
)
echo Finished processing all CSV files
echo.
echo Creating %csvfile%
echo %header%>"%csvfile%"
set /p =Sorting data...<nul
sort "%tmpfile%">>"%csvfile%"
echo OK
del "%tmpfile%"
echo Finished!
title Command Prompt
exit /b
The problem description lacks multiple details and the example code provided has no relation to the description. In the description you talk about two files but in the code you process "all CVS files" with no indication of which "two files" should be merged. The code has an undescribed "Keyword" value. The code sorts the output file, but this is not necessary because "both input files are sorted by common column" as you said...
Anyway I tried to help you writting a working code that achieve what you said in the problem description. I searched for "file merge" in the Batch-file tag and get multiple results, so I just used this method as base...
#echo off
setlocal EnableDelayedExpansion
set "Header1="
set "Header2="
set "common2=-999999999"
rem Read second file from redirected input
< file2.csv (
rem Read first file via FOR /F
for /F "tokens=1,2 delims=," %%a in (file1.csv) do (
if not defined Header1 (
set "Header1=%%a,%%b"
) else (
if !common2! lss %%a call :ReadNextFile2RecordUntil %%a
if !common2! equ %%a echo %%a,%%b,!rest2!
)
)
) > result.csv
goto :EOF
:ReadNextFile2RecordUntil commonCol
set "line2="
set /P "line2="
if not defined line2 set "common2=999999999" & exit /B
for /F "tokens=1* delims=," %%A in ("%line2%") do (
if not defined Header2 (
set "Header2=%%B"
echo %Header1%,%%B
goto ReadNextFile2RecordUntil %1
) else (
set "common2=%%A"
set "rest2=%%B"
)
)
if !common2! lss %1 goto ReadNextFile2RecordUntil %1
exit /B
file1:
id,name,roll
1,x,12
2,y,13
4,z,record1 with no matching record2
5,t,15
file2.csv:
id,class,subject
1,V,english
2,III,Math
3,IV,record2 with no matching record1
5,V,OK
result.csv:
id,name,class,subject
1,x,V,english
2,y,III,Math
5,t,V,OK

Create a numbered list based on a given list of strings

windows cmd batch
I have a text file that looks like this:
Dog
Cat
Frog
Bat
Bird
Mouse
I want to attribute a number to each string, each line.
So that it becomes
1 Dog
2 Cat
3 Frog
4 Bat
5 Bird
6 Mouse
Then I want to ask the user to input a number, and then have the corresponding string stored in the variable.
So if the user inputs 1, then the variable is set to the string Dog
So when the user inputs 1 the program stores/outputs Dog
set /p var1="number? " so var1 becomes the string, not the number.
This does the first part of the task kinda, but now I need the second part, storing the strings in avariable.
#echo off
set TEXT_T="list.txt"
set /a c=0
setlocal ENABLEDELAYEDEXPANSION
FOR /F "tokens=1 usebackq" %%i in (%TEXT_T%) do (
set /a c=c+1
echo !c! %%i
)
endlocal
pause
Here below is an updated answer, thanks to LotPings.
With a small tweak to ask for the folder by string
This provides an easier way to use Megatools from CMD
https://megatools.megous.com/man/megals.html
https://github.com/megous/megatools
#echo off
:start:
megals /Root/
set /p var1="dir? " & megals /Root/%%var1%%
for /f "tokens=1,* delims=:" %%A in ('megals -n /Root/%%var1%% ^|findstr
/n "." ') do (
set Link[%%A]=%%B
Echo %%A %%B
)
for /f "tokens=1,* delims=:" %%A in ('megals -n -e /Root/%%var1%% ^|findstr
/n "." ') do (
set Link[%%A]=%%B
)
set /p choice=Select #:
Call Set Link=%%Link[%choice%]%%
set "trimmedlink="
for %%h in (%Link%) do if not defined trimmedlink set "trimmedlink=%%h"
Megadl %trimmedlink% && goto :start:
pause
Edit: Had to trim %Link% to just the first word, i.e just the link
The output of Megals -e /Root/misc looks like this:
The asterisk are the unique link ids for the files
/Root/misc
https://mega.nz/#!********!********************* /Root/misc/File1
https://mega.nz/#!********!********************* /Root/misc/File2
https://mega.nz/#!********!********************* /Root/misc/File3
With the batch script above it looks like:
1 File1
2 File2
3 File3
Select #: <------input file number
Edit2 number listing is fixed
Edit3 Parentheses in filenames crash the program
i.e
1 File(1)
The chosen file number then gets passed to Magadl as the corresponding link
Megadl Link
The batch script allows you to download the link by just entering the corresponding file number so you don't have to type out the long link id in a standard cmd window.
To use megals output directly and avoid delayedexpansion (which removes the !)
Findstr /n will do the numbering.
#echo off
for /f "tokens=1,* delims=:" %%A in ('megals -e /Root/ ^|findstr /n "." ') do (
set Item[%%A]=%%B
Echo %%A %%B
)
set /p choice=Select #:
Call Echo Choice:%%Item[%choice%]%%
Using a (pseudo-)call with doubled percent signs is an old fashioned method of realizing delayed expansion without the problem with the !.
In programming/scripting you need to adapt techniques to fit your needs.
Without knowing the exact output of your megatools,
this could do the job :
#echo off
for /f "tokens=1,* delims=:" %%A in ('megals -e /Root/ ^|findstr /n "." ') do (
set Folder[%%A]=%%B
Echo %%A %%B
)
set /p choice=Select #:
Call Set Folder=%%Folder[%choice%]%%
for /f "tokens=1,* delims=:" %%A in ('megals -e %Folder% ^|findstr /n "." ') do (
set Link[%%A]=%%B
Echo %%A %%B
)
set /p choice=Select #:
Call Set Link=%%Link[%choice%]%%
megadl %Link%
As compo advised, please edit your question to contain all necessary information - don't force others to gather it from unnecessary answer and comments.
Just use an array:
#echo off
setlocal ENABLEDELAYEDEXPANSION
set TEXT_T="list.txt"
set /a c=0
FOR /F "tokens=1 usebackq" %%i in (%TEXT_T%) do (
set /a c=c+1
echo !c! %%i
set string[!c!]=%%i
)
set /P number=Enter number:
echo !string[%number%]!
pause
For further details, see this answer.
What about making the output of a command into a list, instead of a text file into a list?
so the line with var1 here would be turned into a numbered list (the files listed in that directory), and Var2 is the number the users enters to create the returned string that gets passed to a command. i.e
https://megatools.megous.com/man/megals.html
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
(megals -e /Root/) <--------list all folders in root
(set /p var1="dir? " && megals -e /Root/!var1!) <-- select folder + list files
(set /p var2="Megalink? " && Megadl !var2!) <--- enter the file name for download
ENDLOCAL
pause
I want to be able to enter a number for the download, instead of the long file name. So this way the user can enter the number that corresponds to the link that they want to download. So if they enter 1 then the program does Megadl Dog
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
megals -e /Root/
set /p var1="dir? "
megals -e /Root/!var1! > rlist.txt
set TEXT_T="rlist.txt"
set /a c=0
FOR /F "tokens=1 usebackq" %%i in (%TEXT_T%) do (
set /a c=c+1
echo !c! %%i
set string[!c!]=%%i
)
set /P number=Enter number:
Megadl !string[%number%]!
Endlocal
pause
This kills the first part of the link because it removes everything in between and including exclamations. !dasasdasd!
all megalinks start with #!something!
This didn't work
I need output of Call Echo %%Item[%choice%]%% passed to Megadl
#echo off
for /f "tokens=1,* delims=:" %%A in ('megals -e /Root/anime ^|findstr /n "." ') do (
set Item[%%A]=%%B
Echo %%A %%B
)
set /p choice=Select #:
Call Echo %%Item[%choice%]%%
for /F "tokens=*" (`%%Item[%choice%]%%`) do (
set "var1=%%A"
Megadl !Var1!
)
pause

sorting files according to keywords, need a more database-y solution

I'm making a script that sorts video files into folders by checking for known keywords in the file. As the amount of keywords grows out of control the script becomes very slow, taking several seconds for each file to be processed.
#echo off
cd /d d:\videos\shorts
if /i not "%cd%"=="d:\videos\shorts" echo invalid shorts dir. && exit /b
:: auto detect folder name via anchor file
for /r %%i in (*spirit*science*chakras*) do set conspiracies=%%~dpi
if not exist "%conspiracies%" echo conscpiracies dir missing. && pause && exit /b
for /r %%i in (*modeselektor*evil*) do set musicvideos=%%~dpi
if not exist "%musicvideos%" echo musicvideos dir missing. && pause && exit /b
for %%s in (*) do set "file=%%~nxs" & set "full=%%s" & call :count
for %%v in (*) do echo can't sort "%%~nv"
exit /b
:count
set oldfile="%file%"
set newfile=%oldfile:&=and%
if not %oldfile%==%newfile% ren "%full%" %newfile%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"music" >nul && set words=%words%, music&& set /a count+=1
echo "%~n1" | findstr /i /c:"official video" >nul && set words=%words%, official video&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for music videos
set musicvideoscount=%count%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"misinform" >nul && set words=%words%, misinform&& set /a count+=1
echo "%~n1" | findstr /i /c:"antikythera" >nul && set words=%words%, antikythera&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for conspiracies
set conspiraciescount=%count%
set wanted=3
set winner=none
:loop
:: count points and set winner (in case of tie lowest in this list wins, sort accordingly)
if %conspiraciescount%==%wanted% set winner=%conspiracies%
if %musicvideoscount%==%wanted% set winner=%musicvideos%
set /a wanted+=1
if not %wanted%==15 goto loop
if not "%winner%"=="none" move "%full%" "%winner%" >nul && echo "%winner%%file%" && echo.
Notice the "weight value" for each keyword. It counts the total points for each category, finds the largest value and moves the file to the folder appointed to that category. It also displays the words it has found and lastly lists files it finds unsortable so I can add keywords or tweak weight values.
I have stripped the amount of folders and keywords in this sample to bare minimum. The full script has six folders and 64k size with all the keywords (and growing).
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories=music conspiracies"
REM SET "categories=conspiracies music"
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|%%s^|%%t
)
)
)>"%tempfile%"
SET "lastname="
FOR /f "tokens=1,2,*delims=|" %%a IN ('sort "%tempfile%"') DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner="
SET /a maxfound=0
FOR %%v IN (%categories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO CALL :compare %%w %%x
)
IF DEFINED winner ECHO %winner% %lastname:&=and%
:RESET
FOR %%v IN (%categories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
:compare
IF %2 lss %maxfound% GOTO :EOF
IF %2 gtr %maxfound% GOTO setwinner
:: equal scores use categories to determine
IF DEFINED winner GOTO :eof
:Setwinner
SET "winner=%1"
SET maxfound=%2
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q45196316.txt containing this category data for my testing.
music,6,music
music,8,Official video
conspiracies,3,misinform
conspiracies,6,antikythera
missing,0,not appearing in this directory
I believe your problem is that repeatedly executing findstr is time-consuming.
This approach uses a data file containing lines of category,weight,mask. The categories variable contains a list of the categories in order of preference (for when the score is equal)
Read the data file, assigning category to %%s, weight to %%t and mask to %%u and then do a directory-scan using the mask. This will echo a line to the tempfile in the format name|category|weight for each name-match found. dir seems to be very fast after the first scan.
The resultant tempfile will thus have one line for each filename+category plus the weight, so if a filename fits into more than one category, more than one entry will be created.
We then scan a sorted version of that file and resolve the score.
First, if the filename changes, we can report on the last filename. This is done by comparing the values in the variables $categoryname. Since these are scanned in the order %categories% then the first category in the list is chosen if there is an equivalence of scores. The scores are then reset and lastname initialised to the new filename.
We then accumulate the score into $categoryname
So - I believe that will be a bit faster.
Revision
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories="rock music" music conspiracies"
REM SET "categories=conspiracies music"
:: set up sorting categories
SET "sortingcategories="
FOR %%a IN (%categories%) DO SET "sortingcategories=!sortingcategories!,%%~a"
SET "sortingcategories=%sortingcategories: =_%"
:: Create "tempfile" containing lines of name|sortingcategory|weight
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|!sortingcategory!^|%%t^|%%s^|%%u
)
)
)>"%tempfile%"
SET "lastname="
SORT "%tempfile%">"%tempfile%.s"
FOR /f "usebackqtokens=1,2,3delims=|" %%a IN ("%tempfile%.s") DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:: resolve by totalling weights (%2) in sortingcategories (%1)
:: for each name (%3)
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner=none"
SET /a maxfound=0
FOR %%v IN (%sortingcategories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO IF %%x gtr !maxfound! (SET "winner=%%v"&SET /a maxfound=%%x)
)
ECHO %winner:_= % %lastname:&=and%
:RESET
FOR %%v IN (%sortingcategories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
I've added a few significant comments.
You can now have spaces in category names - you need to quote the name (for reporting purposes) within the set catagories... statement.
sortingcategories is automatically derived - it's only used for sorting and is simply the categories with any space in a name replaced by an underscore.
In creating the tempfile, the category is processed to contain underscores (the sortingcategory) and when the final placement is resolved, the underscores are removed returning the category name.
Negative weights should now be processed appropriately.
-- further revision for "not append *"
FOR /f "tokens=1-5delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR %%z IN ("!sortingcategory!") DO (
SETLOCAL disabledelayedexpansion
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\%%~v%%u%%~w" 2^>nul'
AND
add 2 extra columns to the q45196316 file
music,6,music,*,*
music,8,Official video,"",*
conspiracies,3,misinform,*,*
conspiracies,6,kythera,*anti,*
missing,0,not appearing in this directory,*,*
rock music,2,metal,*,*
conspiracies,-5,negative,*,*
The for /f ... %%s now generates %%v and %%w containing the last two columns (as tokens is nor 1-5)
These are applied as prefix and suffix to %%u in the dir command. Note that "" should be used for nothing as two successive , are parsed as a single separator. The ~ before the v/w in %%~v means remove the quotes.

Resources