Search Directory for Files Matching Pattern - Windows - windows

I have been trying to make this work for longer than I care to admit but for some reason I cannot figure it out. I usually work with Linux/Unix.
I simply want to search a directory for all instances where a filename matches a string.
Some things I have tried:
dir /s "/path/to/Test*"
dir /s/b "C:/path/to/Test*"
Additionally, I am hoping to return something that can easily be imported into an array. Something without unnecessary information. All I need Is the path or at the very least the filename for each file matched.
Edit: I dont want information like this (if possible)
Volume in drive C is OS
Volume Serial Number is...
Edit: Test* is intended to indicate all filenames beginning with Test. So TestA, TestB, & TestC should all match.

The same commands work on Linux, Mac, and Windows. http://github.com/PowerShell/PowerShell/
PS C:\src> (Get-ChildItem -Recurse -File -Path 'C:/src/d2' -Filter 'test*').FullName
C:\src\d2\test.bat
C:\src\d2\test.ps1
C:\src\d2\test.sql
C:\src\d2\test.txt
C:\src\d2\copyt\test.txt
Using command aliases, it can be shorter for interactive use. But, aliases are not a good practice for scripts.
PS C:\src> (ls -r -file 'C:/src/d2/test*').FullName
C:\src\d2\test.bat
C:\src\d2\test.ps1
C:\src\d2\test.sql
C:\src\d2\test.txt
C:\src\d2\copyt\test.txt
If you want an array, this will make one.
PS C:\src> $files = (ls -r -file 'C:/src/d2/test*').FullName
PS C:\src> $files.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Replace directory separators with -replace.
(Get-ChildItem -Recurse -Path 'C:/src/d2' -Filter 'test.*').FullName -replace '\\','/'
Join them to a single line with -join. This join uses a COMMA. Note that if the join uses a COLON in *NIX-style, it will not work well on Windows.
PS C:\src> (Get-ChildItem -Recurse -Path 'C:/src/d2' -Filter 'test.*').FullName -replace '\\','/' -join (',')
C:/src/d2/test.bat,C:/src/d2/test.ps1,C:/src/d2/test.sql,C:/src/d2/test.txt,C:/src/d2/copyt/test.txt
If you need a PATH-style separator, use:
(Get-ChildItem -Recurse -Path 'C:/src/d2' -Filter 'test.*').FullName -replace '\\','/' -join ([IO.Path]::PathSeparator)

I've just been searching for all files, called "test.*" all over my C:-drive, using this simple command:
dir /S C:\test*
Although I just mention the directory C:\, the /S makes sure all subfolders are used too. In top of that, there are no double quotes, as you can see.
Does this solve your issue?

Sorry, I didn't see that you're used working with UNIX/Linux, so here I have an approach you'll prefer:
forfiles /P C:\ /S /M test* /C "cmd /c echo #path"
This does the following:
/P C:\ Start looking in C:\
/S Search through subdirectories
/M test* Filename looks like "test*"
/C <cmd> When found, launch <cmd>
"cmd /c echo #path" Echo (write to output) the complete path of the found file or directory
This will give you a list of files and directories, written as full paths, something like:
"C:\Octave\Octave-5.2.0\mingw64\bin\test-libiberty.exe"
"C:\Octave\Octave-5.2.0\mingw64\bin\test-lua.exe"
"C:\Octave\Octave-5.2.0\mingw64\bin\test-tinyxml.exe"
"C:\Octave\Octave-5.2.0\mingw64\include\llvm\Testing" <-- this is a directory
"C:\Octave\Octave-5.2.0\mingw64\include\wx-3.0\wx\testing.h"
...
Which resembles a lot the typical UNIX/Linux results, you're used to.
More information about forfiles can be found, launching forfiles /?.

Based upon your statement, "I am hoping to return something that can easily be imported into an array", I'd assume you're probably looking for something more like this:
#Echo Off
Rem Ensure that extensions are enabled (required for SET and FOR commands)
SetLocal EnableExtensions
Rem Ensures that there are no existing variables in the environment with names beginning with file[
For /F "Delims==" %%G In ('" Set file[ 2> NUL "') Do Set "%%G="
Rem Gets every file matching the glob test* in the tree rooted at "C:\path\to"
Rem and defines an incremented variable name for each, beginning at %file[1]%
For /F "Tokens=1,* Delims=]" %%G In (
'" Dir /B /S /A-D "C:\path\to\test*" 2> NUL | "%__AppDir__%find.exe" /N /V "" "'
) Do Set "file%%G]=%%H"
Rem An example line to show you all of the variables you have now defined
Set file[ 2> NUL
Rem Pause the script to ensure that you have been able to read any output
Pause
Rem An example line to show you all the first defined variable value
Echo=%file[1]%
Rem Pause the script to ensure that you have been able to read any output
Pause
...and here it is without the Remarks:
#Echo Off
SetLocal EnableExtensions
For /F "Delims==" %%G In ('" Set file[ 2> NUL "') Do Set "%%G="
For /F "Tokens=1,* Delims=]" %%G In (
'" Dir /B /S /A-D "C:\path\to\test*" 2> NUL | "%__AppDir__%find.exe" /N /V "" "'
) Do Set "file%%G]=%%H"
Set file[ 2> NUL
Pause
Echo=%file[1]%
Pause
Don't forget to change C:\path\to\test* as required for your specific location and glob.

Related

Fix findstr to ignore matching filenames with path

I'm trying to create a list of files created yesterday.
I've got the below working without /c:"cmd /c echo #path" but I want the full path.
forfiles /s /d -1 /m *.txt /c:"cmd /c echo #path" > a.txt
forfiles /s /m *.txt /c:"cmd /c echo #path" > b.txt
FOR /f "delims=; tokens=*" %%X IN ('findstr /v /g:a.txt b.txt') DO (
echo %%X
)
What is the best way to get around the issue with using findstr with a path containing backslashes? Do I need to replace all the backslashes in the comparison files with some other symbol and then change it back later or is there an easier way to do it in the findstr line?
The findstr command checks the very first search expression and changes to regular expression mode when a meta-character is found or to literal mode otherwise, unless you explicitly predefine the mode by /L (literal) or /R (regular expression).
But findstr is a nasty beast since there are still some problems even with /L:
wrong results may be returned with multiple literal search strings, unless you specify /I to do case-insensitive searches; but this is not a problem here anyway since you are dealing with directory and file names, which are treated case-insensitively by Windows anyway;
although in literal mode, escaping of meta-characters like ., [, ], ^, $, \, * and ? still occurs when there is a \ in front; you could just double all \ to work around that;
So the following code should work in most situations; delayed expansion is enabled herein by cmd /V, which is required to read the interim variable FILE that is written and read in the same command line:
forfiles /S /M "*.txt" /D -1 /C "cmd /C if #isdir==FALSE (set FILE=#path) & cmd /V /C echo(!FILE:\=\\!" > "exclude.txt"
forfiles /S /M "*.txt" /C "cmd /C if #isdir==FALSE echo #path" > "all.txt"
for /F "delims=" %%X in ('findstr /V /L /I /X /G:"exclude.txt" "all.txt"') do (
echo(%%X
)
I inserted if #isdir==FALSE here to not match directories whose names end in .txt. Also I added /X to findstr in order to match whole lines/paths only.
Regard that literal findstr search strings are limited to a length of 511 bytes (after doubling \), which can easily be reached with file paths.
However, what about a different approach that avoids findstr at all?
Here is a post I once provided for returning items newer than a relative date with forfiles: FORFILES date -after- (date calc in cmd file).
I prefer powershell for such tasks:
Get-ChildItem -Path "C:\Users\tivrick" -Recurse -ErrorAction SilentlyContinue | Where-Object { -Not $_.PSIsContainer -And $_.CreationTime.Date -Eq (Get-Date).Date.AddDays(-1) } | Select-Object -ExpandProperty FullName
If you really need to run that from a batch-file:
#PowerShell -NoP "GCI "C:\Users\tivrick" -R -EA SilentlyContinue|?{!$_.PSIsContainer -And $_.CreationTime.Date -Eq (Get-Date).Date.AddDays(-1)}|Select -Exp FullName"

Windows 'dir' command: sort files by date when '/s' is specified

Goal: I want to copy the latest file with a certain extension from a "source directory" to a "destination directory" using a batch file. The latest file may be under several sub-directories within the source directory.
This question/answer is exactly what I want, however it does not seem to sort when the /s option is specified (as this comment would suggest):
FOR /F "delims=|" %%I IN ('DIR "K:\path\tp\source\dir\*.ext" /B /S /O:D') DO SET NewestFile=%%I
copy "%NewestFile%" "C:\path\to\destination\dir"
You can test DIR "K:\path\tp\source\dir\*.ext" /B /S /O:D by itself to see that it does not sort.
What I've Tried: This command by itself does work: DIR "K:\path\tp\source\dir\*.ext" /S /B | sort but I can't figure out how to use it in a for loop (batch file exits before I can determine the error - even with a pause at the end).
Any ideas?
See: dir docs
You can call out to Powershell from you batch file if you want to. This code can be shortened on newer versions of Powershell.
for /F "delims=" %%G IN ('powershell "gci -rec | where { ! $_.PSIsContainer } | sort LastWriteTime | select -last 1 | Select-Object fullname"') do set NewestFile=%%G
Full answer:
cd /d "K:\path\to\source\dir"
for /F "delims=" %%G IN ('powershell "gci -rec *.ext | where { ! $_.PSIsContainer } | sort LastWriteTime | select -last 1 | Select-Object fullname"') do set NewestFile=%%G
copy "%NewestFile%" "C:\path\to\dest\dir"
A simple solution could be
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=K:\path\tp\source\dir"
set "mask=*.ext"
for %%r in ("%root%\.") do for /f "tokens=2,*" %%a in ('
robocopy "%%~fr" "%%~fr" "%mask%" /njh /njs /nc /ns /ts /s /ndl /nocopy /is /r:0 /w:0 /l
^| sort /r
^| cmd /v /e /c"(set /p .=&echo(!.!)"
') do set "lastFile=%%b"
echo Last file : "%lastFile%"
This code uses robocopy to generate the list of files with a timestamp prefix (the switches just request no job header, no job summary, no file class, no file size, timestamp, recursive, no directory list, no directory information copy, include same files, no retry, no wait, don't copy only generate list).
This timestamp prefixed list (UTF yyyy/mm/dd hh:mm:ss last file write) is then sorted in reverse order to get the last file in the first line. This line is retrieved with a separate cmd instance (this avoids a time problem with a for /f reading long lists of data) so the for /f only reads one line.
As the robocopy lines contain the date, time and file name, to retrieve this last field, we request the for /f to retrieve two tokens: one containing the hour (will be stored in %%a) and the remaining text until the end of the line (stored in %%b)
The additional for %%r is included just to prevent a usual problem using robocopy. As we are quoting paths to prevent problems with spaces, we need to ensure the paths do not end with a backslash that will escape the path's closing quote and make the command fail.
Everything seems to be working fine. I believe you problem lies within the "delims=|"statement from your original function. By using a "delims=" all it would be doing is cutting off the first | from the loops output. Being that | would not be in a normal windows file path, it's completely not needed. Try just throwing an "tokens=*" in the statement instead.
I also recommend using Set "String=" Instead of the traditional Set String= as spaces can be a problem in many cases. I updated that in the function.
Batch Script:
#ECHO OFF
Rem | Configuration
Set "MainDirectory=C:\Folder1"
Set "FileExtension=*.txt"
Set "CopyDestination=C:\Folder2"
Rem | Search for the newest file in a directory/sub-directory
for /f "tokens=*" %%A in ('DIR "%MainDirectory%\%FileExtension%" /B /S /O:D') do (SET "NewestFile=%%A")
Rem | Copy file to a destination
copy "%NewestFile%" "%CopyDestination%"
goto :EOF

ForFiles to batch move & rename file(s) with timestamp

I have a bunch of *.mp4 & *.jpg files created on an hourly/daily basis, which use the file structure below:
-- before:
c:\video\2017-10-15\21hour\jpg\12.13.15[M][0#0][0].jpg
c:\video\2017-10-15\21hour\mp4\12.13.01-12.14.32[M][0#0][0].mp4
c:\video\2017-10-18\16hour\jpg\21.42.31[M][0#0][0].jpg
c:\video\2017-10-18\16hour\mp4\21.42.31-21.45.38[M][0#0][0].mp4
I want all the *.jpg & *.mp4 files to get moved to c:\video\ & also completely ditch the old name for 'date_time' + extension, similar to below:
-- after:
c:\video\2017-10-15_12.13.15.jpg
c:\video\2017-10-15_12.13.01-12.14.32.mp4
c:\video\2017-10-18_21.42.31.jpg
c:\video\2017-10-18_21.42.31-21.45.38.mp4
I discovered ForFiles, and was able to use the /s to recursively search all subfolders & move the *.mp4 & *.jpg to a single location:
forfiles /p c:\video\ /s /m *.mp4 /c "cmd /c move #PATH c:\video\"
forfiles /p c:\video\ /s /m *.jpg /c "cmd /c move #PATH c:\video\"
Now for the renaming, at this point I can almost taste victory as this gives me the desired filename output:
forfiles /p c:\video\ /s /m *.mp4 /c "cmd /c echo #FDATE_#FTIME.mp4"
forfiles /p c:\video\ /s /m *.jpg /c "cmd /c echo #FDATE_#FTIME.jpg"
But when I replace echo with either ren/rename/move, I get errors:
The system cannot find the path specified.
The syntax of the command is incorrect.
But the error only happens when I'm using #FDATE and/or #FTIME. These variables all work fine: #FNAME, #ISDIR, #FSIZE
forfiles /p c:\video\ /m *.mp4 /c "cmd /c ren #FILE #ISDIR.#EXT"
forfiles /p c:\video\ /m *.mp4 /c "cmd /c rename #FILE #FSIZE.#EXT"
forfiles /p c:\video\ /m *.mp4 /c "cmd /c move #FILE #EXT.#EXT"
Once I get the renaming part to work, I will combine both the move & rename into one command... But I can't figure out why #FDATE & #FTIME won't take, is there something wrong with my syntax, or is this just something ForFiles doesn't allow?
The problem explained
Firstly, replacing echo with just move or ren alone will not work because it will only detect one argument being passed to it. This could explain the syntax error.
Syntax of ren from Microsoft Docs
ren [<Drive>:][<Path>]<FileName1> <FileName2>
rename [<Drive>:][<Path>]<FileName1> <FileName2>
Syntax of move from Microsoft Docs
move [{/y | /-y}] [<Source>] [<Target>]
Secondly, the variables #FDATE and #FTIME are locale-dependent. Their format will depend on your device's regional formatting, e.g. #FDATE's format could contain / and #FTIME's format contains :. These are reserved characters and could result in an error if misused. This could explain the path error.
A solution using forfiles
If you want to do this with forfiles you will have to use the for command, like this. You can change the date format by extracting substrings like in this answer. You can change the time format by replacing the colon : with a dot ., explained here.
You could use /m once for .jpg and again for .mp4, but you could also try it like this for loop. This means you would have to nest the for in the example in another for loop.
According to the move /?, if you move just one file you can rename it as well.
Example:
setlocal EnableDelayedExpansion
for %%F in (.jpg, .mp4) do (
for /f "tokens=1,2,3,4 delims=|" %%A in (
'forfiles /p "C:\video" /s /m "*%%F" /c "cmd /c echo #PATH^|#EXT^|#FDATE^|#FTIME"'
) do (
set filepath=%%~A
set extension=%%~B
set datestamp=%%~C
set timestamp=%%~D
set parseddate=!datestamp:~6,4!-!datestamp:~3,2!-!datestamp:~0,2!
move "!filepath!" "C:\video\!parseddate!_!timestamp::=.!.!extension!"
)
)
endlocal
If the file count is large, it will take a little while for batch to finish. forfiles has to read the files first and the for loop has to also go through that file list. Also, forfiles is locale-dependent. If the regional date format of your device is 2020/02/10 then the indices of the substring in my example are not correct.
A solution using PowerShell
.NET Framework's LastWriteTime property is locale-independent, as in it does not dependent on your device's regional formatting. There is also a LastWriteTimeUtc property if you prefer to use UTC. You will still have to use a for loop inside a batch file though.
PowerShell's Get-ChildItem returns all children of the parent folder specified by the -Path parameter and with the -Recurse parameter you can do so recursively. If you want the result to include just .jpg and .mp4 files you should use -Include *.jpg, *.mp4. Normally when using -Include you would need to append \* to the path, but since -Recurse is used it's not necessary.
With Get-ItemProperty and the -Name parameter you can list the Fullname, LastWriteTime and Extension properties. To make it more readable for the for loop they have to be called like $var.Property and concatenated. ToString() must be used to convert LastWriteTime to string and change its format.
Example:
for /f "tokens=1,2 usebackq delims=|" %%A in (
`powershell -Command "& {Get-ChildItem -Path 'C:\video\*' -Recurse -Include *.jpg, *.mp4 | ForEach {Get-ItemProperty -Path $_.FullName -Name FullName, LastWriteTime, Extension} | ForEach {$_.FullName+'|'+$_.LastWriteTime.ToString('yyyy-MM-dd_HH.mm.ss')+$_.Extension}}"`
) do (
move "%%~A" "C:\video\%%~B"
)
Note: You can leave out -Path, but then the first argument must be the path.

list of files and folder in a specific way using command prompt

I need list of files and folders in a directory (Note:no sub folder files) using command prompt ,in the format ie. File name - size - file type.
I tried using dir /A/S >AllFiles.txt. But the output is in a format that needs editing.
This isn't possible with only the "dir" command. Powershell's get-childitem will do the trick though.
get-childitem -recurse | Select-Object basename, length, extension
Assuming you are running from cmd you could wrap the above powershell command line this:
powershell -Command "get-childitem -recurse | Select-Object basename, length, extension"
To see all available options for the Select-Object clause you can run
get-childitem | get-member
in the powershell
I'm not sure what you mean by file type. I'll assume you mean the file extension.
There isn't a simple way to get the total size of a folder, so I will list folders as name - <dir>.
The - is valid in file and folder names. You might be better off substituting : instead.
The simple FOR command lists either files, or folders, but not both. So below I use two FOR commands. The advantage of this is it supports unicode in names.
#echo off
(
for %%F in (*) do echo %%F - %%~zF - %%~xF
for /d %%F in (*) do echo %%F - ^<dir^>
)>AllFiles.txt
You can combine DIR /B with FOR /F to get both files and folders in one pass, but it does not support unicode in the names.
#echo off
(
for /f "eol=: delims=" %%F in ('dir /b') do (
if exist "%%F\" (echo %%F - ^<dir^>) else (echo %%F - %%~zF - %%~xF)
)
)>AllFiles.txt

How to grab the names of all sub-folders in a batch script?

I just want to know how can I get all the names of the folders in a current directory. For example in my current directory I have three folders:
stackoverflow
reddit
codinghorror
Then when I execute my batch script all the three folders will print in the screen.
How can I achieve this?
Using batch files:
for /d %%d in (*.*) do echo %%d
If you want to test that on the command line, use only one % sign in both cases.
On Windows, you can use:
dir /ad /b
/ad will get you the directories only
/b will present it in 'bare' format
EDIT (reply to comment):
If you want to iterate over these directories and do something with them, use a for command:
for /F "delims=" %%a in ('dir /ad /b') do (
echo %%a
)
note the double % - this is for use in a batch, if you use for on the command line, use a single %.
added the resetting of default space delims in response to #Helen's comment
With PowerShell:
gci | ? { $_.PSIsContainer }
Old Answer:
With PowerShell:
gci | ? {$_.Length -eq $null } | % { $_.Name }
You can use the result as an array in a script, and then foreach trough it, or whatever you want to do...
For getting all the subfolders of a specific directory and display it in CMD :
#echo off
dir C:\input /s /b /o:n /a:d
Pause&Exit
For getting all the subfolders of a specific directory and save it in a text file :
dir C:\your_directory /s /b /o:n /a:d > output.txt

Resources