I need an equivalent way in a Windows CLI to do what I would be able to in Linux/UNIX in piping through to show unique values.
I don't think there is a command as such, from what I can gather, so is there another way to do this?
What I have to achieve is to list files from multiple directories (whether they exist or not, which I currently do using dir). The way that the script (for the requesting application) works uses multiple sources to construct a directory list and subsequently the command depending on whether the platform is Windows or UNIX but the danger here is that there is a possibility of duplicate directories in the list and this would skew the results at the other end.
The easiest way to deal with this is to do it at source, i.e. the original command being run. So in Linux, the command structure is more or less:
find [dir] [dir] [dir] | grep file_name | sort -u
Doing the same in Windows is obviously more difficult given that:
it has to be a native read only command/script string you would use from Windows Command Prompt (cmd.exe)
I can't install anything on the hosts this is run on
I can't create temporary text files as part of the process
Microsoft has added a /unique switch to sort.exe in Windows 10 but has forgotten to update its documentation. I tested this switch on Windows 7, Windows 8.1 and Windows 10 v1709. It was present in the latter but absent from the first two.
So, sort.exe /unique is the equivalent of UNIX sort -u.
PowerShell already has a Sort-Object cmdlet that supports a -Unique switch. Better still, by default, PowerShell defines the alias sort for Sort-Object so, | sort -unique is pretty much all you need.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
(
SET "line="
FOR /f "delims=" %%a IN ('sort q22351713.txt') DO (
IF "!line!" neq "%%a" ECHO(%%a
set "line=%%a"
)
)>newfile.txt
GOTO :EOF
I used a file named q22351713.txt for my testing.
Produces newfile.txt
Not bullet-proof
Always better to explain what the original does rather than assume it's obvious.
Dirty batch is dirty...
%COMSPEC% /d /v:on /c "(SET LASTLINE=) & (FOR /f "usebackq tokens=*" %L IN (`DIR /b /s /-p dir1 dir2 dir3 dir3 dir2 dir1 "dir with space" ^| %SYSTEMROOT%\system32\find.exe /i "search_file_name" ^| %SYSTEMROOT%\system32\sort.exe`) DO #((IF /i NOT "{!LASTLINE!}"=="{%~L}" ECHO %~L) & SET "LASTLINE=%~L"))"
Let's break this down a bit...
%COMSPEC% /d /v:on /s /c "..." - you wanted a single command, not a batch, and we needed delayed expansion (/v:on), so we invoke a subshell using the known executable of the Windows Command Prompt (%COMSPEC%). We don't want to run autoexec.bat (/d) and we want to run the command and move on, rather than leaving an interactive shell (/c "..."). Quoting is a funky problem, so we try for some 'standardised' behaviour (/s)
SET LASTLINE= - empty any current content of a LASTLINE environment variable
(...) & (...) - run everything before & and everything after & regardless of the exit code of the first bit. Use brackets as appropriate to group commands, where needed.
FOR /f "usebackq tokens=*" %L IN (`...`) DO ... - run the command string between backquotes (usebackq) and read all words (tokens=*) in each line of stdout (FOR /f) into a temporary variable %L.
DIR /b /s /-p dir1 dir2 dir3 dir3 dir2 dir1 "dir with space" - recursively (/s) enumerate through dir1, dir2, dir3 and dir with space, printing only the full path of files to stdout (/b). Don't require keypresses to page the output, if that has been set (/-p). Note that, due to foibles of FOR it is up to you to only quote directories with special chars (e.g. space), i.e. do not quote single-word names.
... ^| ... - pipe the stdout of the first command to the stdin of the second. We use ^ to escape | within the backquotes of FOR.
%SYSTEMROOT%\system32\find.exe /i "search_file_name" - find the string search_file_name in stdin. Use a case-insensitive search (/i).
%SYSTEMROOT%\system32\sort.exe - sort stdin and write to stdout.
DO #(...) - we need to run two commands for each line, so we group them in brackets. FOR normally prints each command before it runs it, the # suppresses this.
(IF /i NOT "{!LASTLINE!}"=="{%~L}" ECHO %~L) - if the line we are looking at %~L is not the same as the line we have stored !LASTLINE!, print the line we are looking at. Again, use a case-insensitive comparison (/i). We bookend both variables with {} to handle empty values, and we use delayed expansion (!LASTLINE! instead of %LASTLINE%) to expand LASTLINE every time it loops, not just when the FOR command is first seen.
SET "LASTLINE=%~L" - store the line we are looking at %~L into the variable LASTLINE.
If you do not have access to the inbuilt environment variables, you may not have %SYSTEMROOT% or %COMSPEC%. Substitute these for C:\Windows and C:\Windows\System32\cmd.exe or, more appropriately, determine the correct locations for the system you are scanning.
Download cygwin utility, it is simple Linux terminal under windows.
It allow to execute any Linux command in Windows.
And you have sort -u there.
Related
How can I within Windows command prompt list all files that are contained in that directory and all subdirectories but the list should only contain a list of files and not the directory location that each file is found in?
Example:
dir /d /s /b /q *.txt will get a list of all text files in all directories so I will end up with c:\example1.txt c:\folder1\example2.txt but I need only the list to show example1.txt example2.txt I hope my question is understandable. just a list of files not showing their paths?
You can try with this command in PowerShell
dir *.txt -r | % Name
From cmd you can execute like
PowerShell -Command "dir *.txt -r | % Name"
There can be used in a Windows command prompt window:
for /R %I in (*.txt) do #echo %~nxI
That results in searching recursive because of option /R in current directory and all its subdirectories for non-hidden files of which long or short 8.3 file name is matched by the wildcard pattern *.txt. Output is just the file name with extension.
Run in a command prompt window for /? to get output the help of the Windows command FOR.
There can be used in a command prompt window also:
for /F "delims=" %I in ('dir *.txt /A-D /B /S 2^>nul') do #echo %~nxI
This command line starts in background one more cmd.exe with option /c to execute the command line enclosed in ' appended as additional arguments. So there is executed in background:
C:\Windows\System32\cmd.exe /c dir *.txt /A-D /B /S 2>nul
The command DIR searches now
in current directory and all its subdirectories because of option /S
for just files because of option /A-D (all attributes except directory attribute) including also hidden files
of which long or short 8.3 name is matched by the wildcard pattern *.txt.
There is output to handle STDOUT of background command process just the list of found file names in bare format because of option /B with full path because of option /S.
It is possible that DIR does not find any file system entry matched by the criteria which results in an error message output to handle STDOUT. This error message is redirected with 2>nul to the device NUL to suppress it.
Read the Microsoft documentation 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.
The output list of fully qualified file names is captured by cmd.exe being opened as Windows command prompt and processed by FOR after cmd.exe started in background closed itself. So it can take some time until there is something displayed in the command prompt window depending on how much file system entries must be searched for by the command DIR of background command process.
FOR would split up by default each line into substrings using normal space and horizontal tab as string delimiters. This line splitting behavior is not wanted here and therefore the option delims= is used to define an empty list of string delimiters to turn off the line splitting. So each file name is assigned one after the other to the specified loop variable I.
The command ECHO outputs finally the file name assigned to loop variable I with just name and extension.
Run in the command prompt window also dir /? for help on this internal command of the Windows Command Processor.
I want to list all the files and directories inside a directory using a for loop in a batch script. How can I do it?
I used below but it didn't work :
for /r %%I in (".") do ( ls -ltr '%%I') ## Listing only filenames and not directories name
Any help is appreciable.
Thanks!
If you just want a list of dirs and files, recursively, what about:
dir /b/s "."
If you want to do something special with each of the stream item, using a for loop, you could do something like:
for /f "tokens=* delims=" %%i in ('dir /b/s "."') do ( echo "%%i" )
There I used echo for echoing, but you can put whatever you need.
"to list all the files and directories inside a directory using a for loop in a batch script." you should use the DIR command.
If you open a Command Prompt window, type dir /? and press the ENTER key you should see its usage information.
One important thing to note is the /A option. What is not mentioned specifically is that using it alone, (without additional parameters D, R, H, A, S, I, L or O), enables all attributes.
Therefore to list all items in the current directory recursively in bare format you'd use:
DIR /A /B /S
or
DIR . /A /B /S
If you wanted to list them in a specific location relative to the current directory, you'd use:
DIR "Location" /A /B /S
or:
DIR ".\Location" /A /B /S
And For a specific absolute path:
DIR "L:\ocation" /A /B /S
And if you wanted it to be in the same location as the batch file itself, you can use the special variable for the current script %0:
DIR "%~dp0." /A /B /S
To perform that command within a For loop, you should first open a Command Prompt window, type for /? and press the ENTER key, to read its usage information.
You should note that you are running a command, and should therefore use a FOR /F loop, i.e.
FOR /F ["options"] %variable IN ('command') DO command [command-parameters]
But should also note that:
To use the FOR command in a batch program, specify %%variable instead of %variable.
So:
FOR /F ["options"] %%variable IN ('command') DO command [command-parameters]
As you have your command already, the options now become important. The first you need to understand is eol which whilst it seems to mean End Of Line, is specific to only one end, the beginning! What this does it does not pass any result of 'command' to the DO if it begins with a single specific character. The defualt for eol is the semicolon ;, (probably because historically it was a common line comment marker in many files). Generally, a file or directory name could include, and begin with a semicolon, so in order to include all files, you would specify a character which cannot be included in a filename, for me the simplest is ?, although I've seen many examples using |. However, when you perform a recursive DIR command, every returned line is a fully qualified path, none of which can begin with a semicolon, so you can for this task ignore eol. You clearly want everything returned, so do not require skip any lines returned. tokens and delimiters, are adjusted according to what you want to do with the results, in this case, you want the entire content of each line returned by your 'command' with no splitting on specific characters. You should note that tokens by default is 1 and delims by default is both the space and a horizontal tab characters. You should stipulate therefore that you do not want any delimiters, so that the first token is everything returned on each line of 'command'. You rarely require the usebackq option, so for the purposes of this answer, and your task, just ignore it.
Now put it all together:
FOR /F "delims=" %%G IN ('DIR "Location" /A /B /S') DO command
Finally you can use your wanted DO command with each result from your parenthesized DIR command. That result will be held within your variable %%G.
For the purposes of just viewing each result, we'll use the ECHO command, (you would just replace that with your chosen command). Please note that as each result of the DIR command is a file or directory name string, you should generally doublequote it.
allObjects.cmd
FOR /F "delims=" %%G IN ('DIR "Location" /A /B /S') DO ECHO "%%G"
Please remember to replace "Location" as needed, before running the Windows Command Script
Create two loops, one for files
for /r %%i in (*.*) do <something>
and one for directories
for /r %%i in (.) do <something>
and use the same command after do
But, since you have Cygwin installed anyway, why not use that power and do
find . | xargs -L1 ls -ltr
where find . finds all files and directories, | xargs passes the output to xargs which -L1 splits the output after each line and passes each line to ls -ltr.
I'm fairly new to batch, my problem is the following:
I have a long list of folders and need to delete the first 3 characters from each of their names. Think 01_Folder1, 02_Folder2, 03_Folder3 and so on. I've tried patching together pieces of CMD commands I've found on the web but could not come up with a script that does what I want it to do. I've even tried using VBScript as I'm more familiar with VB in general but failed to find a solution as well.
Is there an easy way to solve this?
Edit:
Here's my attempt; it's giving me a syntax error but as I am not versed enough in CMD, I cannot really see why:
setlocal enableextensions enabledelayedexpansion
for /d %%i in ("%~dp0*") do (set name=%%i && ren "!name!" "!name:~3!")
endlocal
The FOR command line does not work because of assigned to loop variable i is the name of a directory with full path and so removing the first three characters results in removing drive letter, colon and backslash from path of the directory and not the first three characters from directory name. Further the full qualified directory name is assigned with an additional space to environment variable name because of the space between %%i and operator &&.
One solution would be:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /d %%i in ("%~dp0*") do set "name=%%~nxi" && ren "%%i" "!name:~3!"
endlocal
The disadvantage of this solution is that directory names with one or more exclamation marks in name or path are not processed correct because of enabled delayed expansion resulting in interpreting ! in full directory name as begin/end of a delayed expanded environment variable reference.
Another solution is quite simple with using just the command line:
#for /D %%i in ("%~dp0*_*") do for /F "tokens=1* delims=_" %%j in ("%%~nxi") do #ren "%%i" "%%k"
The outer FOR searches in directory of the batch file for non-hidden subdirectories matching the pattern *_*.
For each directory name assigned with full path to loop variable i one more FOR command is used which processes just the string after last backlash (directory name without path) and splits the string up into substrings (tokens).
The string delimiter is an underscore as defined with option delims=_. The option tokens=1* tells FOR to assign first underscore delimited string to specified loop variable j and everything after one or more underscores after first underscore delimited string to next but one loop variable k according to ASCII table.
The inner FOR would ignore a directory name on which first substring starts with a semicolon as being the default end of line character. But in this case no directory has ; at beginning of its name.
There is one problem remaining with this command line. It does not work on drives with FAT32 or exFAT as file system, just by chance on drives with NTFS file system. The reason is that the list of non-hidden directories changes in file system while the outer FOR iterates over the directory entries matching the pattern.
A better solution loads first the list of directories to rename into memory of Windows command process which is processing the batch file before starting with renaming the directories.
#for /F "delims=" %%i in ('dir "%~dp0*_*" /AD-H /B 2^>nul') do for /F "tokens=1* delims=_" %%j in ("%%i") do #ren "%~dp0%%i" "%%k"
FOR executes in this case in background one more command process with %ComSpec% /c and the command line within ' appended as additional arguments. So executed in background is with Windows installed to C:\Windows:
C:\Windows\System32\cmd.exe /c dir "C:\Batch\File\Path\*_*" /AD-H /B 2>nul
DIR searches in directory of the batch file for
non-hidden directories because of option /AD-H (attribute directory and not hidden)
matching the wildcard pattern *_*
and outputs just the directory names in bare format because of option /B without path to handle STDOUT (standard output) of background command process.
Read the Microsoft documentation 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 respectively the Windows command process processing the batch file captures everything written to standard output handle of background command process and starts processing it as described above after started cmd.exe terminated itself. So there is in memory already a list of directory names and so the executed REN command does not result anymore in a changed list of directory names on processing one after the other.
Please note that a directory with name 01__Underscore_at_beginning is renamed to Underscore_at_beginning and not to _Underscore_at_beginning by both single line solutions.
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.
echo /?
endlocal /?
for /?
ren /?
set /?
setlocal /?
I want to create a batch file able to apply some processing on each JPG file in a folder hierarchy. The following script file works very well for that case (here I only echo the name of each file, but this should be replaced by some more complex statements in the real application):
:VERSION 1
#echo off
set "basefolder=C:\Base"
for /r %basefolder% %%f in (*.jpg) do echo %%f
Actually, I don't want to explore all the folder hierarchy under %basefolder%, but only a given list of subfolders. This modified script is able to deal with that case :
:VERSION 2
#echo off
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
pushd %basefolder%\%%~s"
for /r %%f in (*.jpg) do echo %%f
popd
)
Is there a solution to remove the pushd/popd pair of statements, to get something closer to the initial script. I thought that one of the following scripts would do the job:
:VERSION 3
#echo off
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
for /r %basefolder%\%%~s" %%f in (*.jpg) do echo %%f
)
or, using delayed expansion:
:VERSION 4
#echo off
setlocal enabledelayedexpansion
set "basefolder=C:\Base"
set "subfolders=A B C"
for %%s in (%subfolders%) do (
set "folder=%basefolder%\%%~s"
echo !folder!
for /r !folder! %%f in (*.jpg) do echo %%f
)
but none of them is working. When running the second one, the echo !folder! command in the external loop shows C:\Base\A, C:\Base\B and C:\Base\C as expected, but the inner loop doesn't echo any JPG file, so I guess that the recursive for /r command does not run correctly.
What am I doing wrong ?
Final edit after answers :
Thanks to #aschipfl who provided a link to the answer posted by #jeb on another question, quoted below:
The options of FOR, IF and REM are only parsed up to the special character phase. Or better the commands are detected in the special character phase and a different parser is activated then. Therefore it's neither possible to use delayed expansion nor FOR meta-variables in these options.
In other words, my versions 3 and 4 do not work because when defining the root folder of the FOR /R command, neither the %%~s nor the !folder! are correctly expanded by the expression parser. There is no way to change that, as this is a parser limitation. As I said in a comment below: the root folder option in the FOR /R command is basically only syntactic sugar to avoid the use of pushd/popd before and after the command. As this syntactic sugar is incomplete, we have to stick to the original syntax for some specific use cases, as the one presented here. The alternatives proposed by #Gerhard (using a subroutine CALL) or by #Mofi (parsing the result of a DIR command) are working, but they are neither more readable nor more efficient than the simple pushd/popd version I proposed initially.
My Approach for this would be really straight forward:
#echo off
set "basedir=C:\Base"
set "subfolders="A","B","C""
for %%i in (%subfolders%) do for /R "%basedir%" %%a in ("%%~i\*.jpg") do echo %%~fa
The double quotes inside of the subfolders variable is important here, it will ensure that folder names with whitespace are not seen as separators for the folder names. For instance:
set "subfolders="Folder A","Folder B","Folder C""
Edit
#echo off
set "basedir=C:\Base"
set "subfolders="A","B","C""
for %%i in (%subfolders%) do call :work "%%~i"
goto :eof
:work
for /R "%basedir%\%~1" %%a in (*.jpg) do echo %%~fa
It is in general not advisable to assign the value of a loop variable to an environment variable and next use the environment variable unmodified without or with concatenation with other strings being coded in batch file or defined already above the FOR loop within body of a FOR loop. That causes just problems as it requires the usage of delayed expansion which results in files and folders with one or more ! are not correct processed anymore inside body of the FOR loop caused by double parsing of the command line before execution, or command call is used on some command lines, or a subroutine is used called with call which makes the processing of the batch file much slower.
I recommend to use this batch file for the task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "basefolder=C:\Base"
set "subfolders=A B C "Subfolder D" SubfolderE"
for %%I in (%subfolders%) do for /F "delims=" %%J in ('dir "%basefolder%\%%~I\*.jpg" /A-D /B /S 2^>nul') do echo %%J
endlocal
The inner FOR loop starts for each subfolder defined in subfolders in background one more command process with %ComSpec% /c and the DIR command line appended as additional arguments. So executed is with Windows installed to C:\Windows for example for the first subfolder:
C:\Windows\System32\cmd.exe /c dir "C:\Base\A\*.jpg" /A-D /B /S 2>nul
The command DIR searches
in specified directory C:\Base\A and all it subdirectories because of option /S
for files because of option /A-D (attribute not directory) including those with hidden attribute set
matching the pattern *.jpg in long or short file name
and outputs to handle STDOUT of background command process just the matching file names because of option /B (bare format)
with full path because of option /S.
The error message output by DIR on nothing found matching these criteria is redirecting from handle STDERR to device NUL to suppress it.
Read the Microsoft documentation 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.
The output to handle STDOUT of background command process is captured by FOR respectively the command process which is processing the batch file. FOR processes the captured output line by line after started cmd.exe terminated itself. This is very often very important. The list of files to process is already in memory of command process before processing the first file name. This is not the case on using for /R as this results in accessing file system, getting first file name of a non-hidden file matching the wildcard pattern, run all commands in body of FOR and accessing the file system once again to get next file name. The for /R approach is problematic if the commands in body of FOR change a file to process like deleting, moving, modifying, copying it in same folder, or renaming a found file because of the entries in file system changes while for /R is iterating over these entries. That can easily result in some files are skipped or some files are processed more than once and it could result also an endless running loop, especially on FAT file system like FAT32 or exFAT. It is never good to iterate over a list of files on which the list changes on each iteration.
Command FOR on usage of /F ignores empty lines which do not occur here. A non-empty line is split up into substrings using a normal space and a horizontal tab as string delimiters by default. This line splitting behavior is not wanted here as there could be full qualified file names containing anywhere inside full name one or more spaces. For that reason delims= is used to define an empty list of delimiters which disables the line splitting behavior.
FOR with option /F would also ignore lines on which first substring starts with ; which is the default end of line character. This is no problem here because of command DIR was used with option /S and so each file name is output with full path which makes it impossible that any file name starts with ;. So the default eol=; can be kept.
FOR with option /F assigns by default just first substring to specified loop variable as tokens=1 is the default. This default can be kept here as splitting the lines (full file names) into substrings is disabled already with delims= and so there is always the full file name assigned to the loop variable.
This example uses just echo %%I to output the file names with full path. But it is now safe to replace this single command by a command block which does more with the JPEG files because of the list of JPEG files for each specified subfolder tree in base folder is always already completely in memory of command process processing the batch file.
I have a folder where files are automatically created and I need every 5 minutes to copy the new files (that is, the files whose last modification was in the last 5 minutes).
:loop
for %a in (C:\test\*) do (
set FileDate=%~ta
)
timeout /t 300
goto loop
That's the way I have found to get date of file but I don't know how to compare and get the current date less 5 minutes.
(The copy command is not necessary, because is via SSH and this problem is resolved).
In batch date time calculations are a very tedious task.
I suggest to use PowerShell (at least as a tool)
To get the files created in the current directory in the last 5 minutes.
This powershell command will output a dir-like listing:
Get-ChildItem -Path 'X:\path'|Where-Object {((Get-Date) - $_.LastWriteTime).TotalMinutes -le 5}
To restrict this to only the FullName you can append the pipe
| Select-Object -ExpandProperty FullName
or simply enclose the command in parentheses and append (...).FullName
(Get-ChildItem -Path 'X:\path'|Where-Object {((Get-Date) - $_.LastWriteTime).TotalMinutes -le 5}).FullName
Wrapped in a batch
:: Q:\Test\2018\11\08\SO_53206386.cmd
#Echo off
for /f "usebackq delims=" %%A in (`
powershell -Nop -C "(Get-ChildItem -Path 'X:\path' -File |Where-Object {((Get-Date) - $_.LastWriteTime).TotalMinutes -le 15}).FullName"
`) Do Echo %%A
Sample output of this batch (listing itself)
> SO_53206386.cmd
Q:\Test\2018\11\08\SO_53206386.cmd
The -File parameter requires PowerShell v3+ but can be replaced with another piped command
| Where-Object {!($PSISContainer)}
filtering out folders. (The opposite is -Directory or no ! for not)
#Echo off
for /f "usebackq delims=" %%A in (`
powershell -Nop -C "(Get-ChildItem -Path 'X:\path' | Where-Object {!($PSISContainer)}| Where-Object {((Get-Date) - $_.LastWriteTime).TotalMinutes -le 15}).FullName"
`) Do Echo %%A
Here is a completely different solution resulting in most likely the same behavior with the advantage that the last modification date of a file does not really matter. So if a file is copied into the observed folder, it is also processed even if its last modification time is not within the last X minutes. It uses the archive file attribute set by Windows automatically each time a file is created in a folder or the file is modified by a process.
#echo off
set "Folder=C:\test"
:loop
for /F "eol=| delims=" %%I in ('dir "%Folder%\*" /AA-D-H /B /ON 2^>nul') do (
%SystemRoot%\System32\attrib.exe -a "%Folder%\%%I"
echo Copy the file "%Folder%\%%I"
)
%SystemRoot%\System32\timeout.exe /T 300
goto loop
The command FOR executes the following command line in a separate command process started with cmd.exe /C in background.
dir "C:\test\*" /AA-D-H /B /ON 2>nul
The command DIR outputs
in bare format only file name and file extension because of /B
only non-hidden files with archive attribute set because of /AA-D-H
ordered by file name because of /ON (not really needed)
found in directory C:\test matching wildcard pattern *.
The error message output by DIR on not finding any directory entry matching these requirements is suppressed by redirecting it from handle STDERR 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.
This output of DIR in separate command process is captured by FOR and processed line by line.
Empty lines are always skipped by FOR which do not occur here.
Lines (file names) starting with a semicolon are also skipped by default by FOR. This behavior is disabled by changing with eol=| the end of line character from default ; to vertical bar which no file name can have anywhere.
FOR splits up by default the line in substrings (tokens) using space/tab as delimiters and assigns just first space/tab delimited string to specified loop variable I. This behavior is not wanted here as file names can contain one or more spaces. For that reason delims= is used to specify an empty list of delimiters which disables the line splitting behavior.
So assigned to loop variable I is the file name with file extension as output by DIR without path.
The command ATTRIB is used to remove the archive attribute from current file for next iteration of the FOR loop. Then the file can be copied to a different location or processed otherwise not modifying its content.
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.
attrib /?
dir /?
echo /?
for /?
goto /?
timeout /?