Listing non symbolic link on Windows - windows

I'm trying to list non-symbolic links in an specific directory and then delete them.
non-symbolic links definition: any file besides the ones that have been created using the command MKLINK /H
To identify those non-symbolic links I used the following approach:
#> fsutil hardlink list %file% | find /c /v ""
When the specified %file% is a symbolic link it returns a number bigger than 1, when it is just a simple file, it returns 1. So far so good!
My problem starts when I need to automate this process and get such return to compare if it is bigger than 1
That's is the code I'm trying to get running property:
#echo off
set some_dir=C:\TEMP\
for /f %%a in ('dir /b %some_dir%') do (
set count=fsutil hardlink list %%a | find /c /v ""
if %count% -EQU 1 del /Q %%a
)
Would someone explain me how such attribution and comparison on %count% variable could be done, please?

I'm trying to list non-symbolic links in an specific directory and then delete them.
There are some issues with your code.
for /f %%a in ('dir /b %some_dir%') do (
The dir /b doesn't work because it doesn't return a file name with a complete path (which is required as input for fsutil)
Use dir /b /s instead.
set count=fsutil hardlink list %%a | find /c /v ""
This doesn't set count to anything sensible.
Use another for /f and parse the output of fsutil so you can set a variable.
if %count% -EQU 1 del /Q %%a
This has two mistakes. You need to use delayed expansion to evaluate count correctly. Replace %count% with !count!. Also remove the -. Replace -EQU with EQU.
Try the following batch file.
test.cmd:
#echo off
setlocal enabledelayedexpansion
set some_dir=C:\TEMP\
for /f %%a in ('dir /b /s %some_dir%') do (
for /f %%b in ('fsutil hardlink list %%a ^| find /c /v ""') do (
set count=%%b
if !count! EQU 1 echo del /Q %%a
)
)
endlocal
Notes:
Remove the echo when you happy with what the result will be.
Example usage and output:
I have used f:\test\folder1 as my test directory. hard is a hardlink to 1.txt.
F:\test>dir f:\test\folder1
Volume in drive F is Expansion
Volume Serial Number is 3656-BB63
Directory of f:\test\folder1
29/08/2016 21:40 <DIR> .
29/08/2016 21:40 <DIR> ..
21/08/2016 09:46 0 1.txt
21/08/2016 09:46 0 2.txt
21/08/2016 09:46 0 3.txt
21/08/2016 09:46 0 4.txt
21/08/2016 09:46 0 5.txt
29/08/2016 21:38 <SYMLINK> file [f:\d]
21/08/2016 09:46 0 hard
29/08/2016 21:38 <SYMLINKD> test [f:\d]
7 File(s) 0 bytes
3 Dir(s) 1,764,846,960,640 bytes free
F:\test>test
del /Q f:\test\folder1\2.txt
del /Q f:\test\folder1\3.txt
del /Q f:\test\folder1\4.txt
del /Q f:\test\folder1\5.txt
del /Q f:\test\folder1\file
del /Q f:\test\folder1\test
Further Reading
An A-Z Index of the Windows CMD command line - An excellent reference for all things Windows cmd line related.
dir - Display a list of files and subfolders.
enabledelayedexpansion - Delayed Expansion will cause variables to be expanded at execution time rather than at parse time.
for /f - Loop command against the results of another command.

There are some problems in your code:
you need delayed expansion because you are setting (writing) and expanding (reading) the variable count within the same parenthesised block of code (namely the for /F %%a loop);
in your for /F %%a loop you need to state options "eol=| delims=" in order not to run into trouble with files whose names begin with ; (such would be ignored due to the default eol=; option) and those which have white-spaces in their names (you would receive only the postion before the first white-space because of the default delims SPACE and TAB and the default option tokens=1 (see for /? for details about that);
dir /B returns file names only, so %%a actually points to files in the current directory rather than to C:\TEMP\; to fix that, simply change to that directory first by cd;
to capture the output of a command (line) and assign it to a variable, use another for /F loop and set; this loop is going to iterate once only, because find /C returns only a single line; note the escaped pipe ^| below, which is required to not execute it immediately;
there is no comparison operator -EQU, you need to remove the - to check for equality;
it is a good idea to use the quoted set syntax as it is most robust against poisonous characters;
file and directory paths should generally be quoted since they might contain token delimiters or other poisonous characters;
Here is the fixed script:
#echo off
setlocal EnableDelayedExpansion
pushd "C:\TEMP\" || exit /B 1
for /F "eol=| delims=" %%a in ('dir /B "."') do (
for /F %%b in ('
fsutil hardlink list "%%a" ^| find /C /V ""
') do (
set "count=%%b"
)
if !count! EQU 1 del "%%a"
)
popd
endlocal
This can even be simplified:
#echo off
pushd "C:\TEMP\" || exit /B 1
for /F "eol=| delims=" %%a in ('dir /B "."') do (
for /F %%b in ('
fsutil hardlink list "%%a" ^| find /C /V ""
') do (
if %%b EQU 1 del "%%a"
)
)
popd
Since the inner for /F loop iterates always once only, we can move the if query inside, thus avoiding the definition of an auxiliary variable which is the only one we needed delayed expansion for.

Simplified method:
#Echo Off
PushD X:\YourDirectory
For %%a In (*.*) Do (
FSUtil HardLink List %%a|FindStr/VIC:"%%~pnxa">Nul||(Echo=Del "%%~a"))
Pause
When you're satisfied with the output Remove line 5 and also 'Echo=' from line 4

Related

Write a script to recursively list directories and files within it in Batch script

I am trying to write a batch script that recursively lists all directories and their files with *.js type in the below format:
For example, if I start with the C:\project directory
c:\project
project.js
project_time.js
c:\project\core
core.js
core_render.js
core_application.js
I tried to implement the above logic in code as follows:
#echo off
for /r %%f in (*.js) do (
echo %%f >> names.txt
)
pause
I was not able to print the directory under which the files are listed.
#echo off
setlocal disabledelayedexpansion
set "lastdir="
( for /r %%A in (*.js) do (
set "nextdir=%%~dpA"
setlocal enabledelayedexpansion
if /i not "!lastdir!" == "!nextdir!" (
rem Empty line and directory path.
if defined lastdir #echo(
#echo !nextdir!
)
endlocal
rem Filename.
#echo %%~nxA
set "lastdir=%%~dpA"
)
) > "names.txt"
The lastdir variable is to record the last directory path so it is echoed only once.
If lastdir is different to %%~dpA:
If lastdir is defined, then an empty line will be echoed.
Directory path of found file is echoed.
Filename is always echoed.
for modifiers dp is the drive and path. nx is the name and extension.
setlocal enabledelayedexpansion is used only where needed so paths with ! are not vulnerable.
I am not going to suggest a command line solution as it would be very long. Instead suggest use of tree command if the output format is suitable.
Here's an untested example, (I'm not expecting it to be quick if your base directory is large):
#Echo Off
(
For /F "Delims=" %%G In ('Dir /B /S /A:D "C:\Project" 2^> NUL') Do (
%__AppDir__%where.exe /Q "%%G":*.js 1> NUL 2> NUL
If Not ErrorLevel 1 (
Echo/
Echo %%G
For /F "EOL=| Delims=" %%H In ('%__AppDir__%where.exe "%%G":*.js')Do (
Echo %%~nxH
)
)
)
) 1> "names.txt"
Pause
If you prefer to run something from the Command Prompt, then try this version:
(For /F "Delims=" %G In ('Dir /B/S/AD "C:\Project" 2^>NUL')Do #(%__AppDir__%where.exe /Q "%G":*.js >NUL 2>&1&&(Echo/&Echo %G&For /F "EOL=|Delims=" %H In ('%__AppDir__%where.exe "%G":*.js')Do #Echo %~nxH)))>"names.txt"
Two simple ways to do this:
dir /S *.js
You get the answers, just as you requested.
FORFILES /S /M *.js /C "cmd /c echo #path"
You get complete path for every file.

Batch file to count file types in sub directories

I need a batch file that counts certain file types in sub directories. In this case it's .txt and .xls files. At the end of the file it reports this back.
#echo off
REM get script directory
set scriptdir=%~dp0
REM change directory to script directory
cd /d %scriptdir%
setlocal
set txtcount=0
set xlscount=0
for %%x in ('dir *.txt /s ) do set /a txtcount+=1
for %%x in ('dir *.xls /s ) do set /a xlscount+=1
echo %txtcount% text files
echo %xlscount% .xls files
endlocal
pause
My batch file doesn't report back the correct count of files. I thought it might be continually counting but I've set the count variables to local so I'm not sure what's up.
There's three problems with your script:
You want to iterate over command output, so you need FOR /F
You're not using the "bare" /B format, i.e. DIR outputs more than just filenames
You're missing the closing single quote twice
Replace your loops with this:
FOR /F %%X IN ('DIR /S /B *.txt') DO SET /A "txtcount+=1"
FOR /F %%X IN ('DIR /S /B *.xls') DO SET /A "xlscount+=1"
You are very close, but you are missing the switch /B of the dir command, so the output contains a header and a footer, which are both included in the count. Therefore, change dir *.txt /s to dir *.txt /s /b (same for *.xls) and the count is going to be correct.
Besides that, there are typos: the closing ' is missing -- for %%x in ('dir *.txt /s') do .... In addition, the /F switch at for is missing, which is needed to capture the output of a command. (I assume these things are present in the true script as you would receive errors otherwise.)
Alternative Approach
To count the number of files, you could also do the following:
for /F "delims=" %%X in ('dir /B /S "*.txt" ^| find /C /V ""') do set "COUNT=%%X"
find /V "" searches for non-empty lines, hence all lines returned by dir /B /S are considered as matches; the /C switch simply lets find return the total amount of matching lines.

Batch file that counts the number of files in EVERY folder in a directory, and outputs results to a text file

What I'm trying to do is make a batch file that'll recursively go into every folder and count the number of files in each folder. However, I've spent the last hour trying various things and it isn't working.
I want the output to look like:
X:Y where X is the folder name and Y is the # of files in X.
setlocal EnableDelayedExpansion
set current=blank
FOR /D %%G in ("*") DO set current=%%G && call:count
:count
set count=0
for %%A in (*) do set /a count+=1
echo !current!:!count!>>"D:\User\Some\Directory\count.txt"
But this doesn't work. The output is giving the same number for each folder. The number it's outputting is the number of files in the directory itself, which I think is the problem.
Specifically, if I'm in C:\User\Example and it has three folders, A, B, and C, I want the number of files in C:\User\Example\A and so on, but it's giving me the number of files in C:\User\Example. Hope that makes sense.
Note: In my use case, the folders will not contain sub-directories.
A little different approach.
#echo off
FOR /D %%G in ("*") DO (
PUSHD "%%G"
FOR /F "delims=" %%H in ('dir /a-d /b * ^|find /C /V ""') DO echo %%G %%H>>"..\count.txt"
POPD
)
this works:
note that you have to collate set current=%%G&& call:count or current value is "A " (trailing space, not that you want)
And when you scan the subdir, you have to prefix * by your current dir
#echo off
del count.txt
setlocal EnableDelayedExpansion
set current=blank
FOR /D %%G in ("*") DO set current=%%G&& call:count
goto end
:count
set count=0
for %%A in (!current!\*) do set /a count+=1
echo !current!:!count!>>"count.txt"
:end
The answers to this question looks like a collection of different ways to do the same thing! Here it is another one:
#echo off
setlocal EnableDelayedExpansion
for /F "tokens=1,2 delims=\" %%a in ('findstr /M /S "^" *.*') do (
if "%%b" neq "" (
set "name=%%a"
set /A "count[!name: =_!]+=1"
)
)
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do (
set "name=%%a"
echo !name:_= !:%%b
)) > count.txt
This method look complicated because the management of spaces in the folders names. If folders don't have spaces, 6 lines (and Delayed Expansion) could be removed; that is:
#echo off
setlocal
for /F "tokens=1,2 delims=\" %%a in ('findstr /M /S "^" *.*') do (
if "%%b" neq "" set /A "count[%%a]+=1"
)
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do echo %%a:%%b) > count.txt
Here is a PowerShell script to handle it:
Get-ChildItem -Path . -Recurse -ErrorAction SilentlyContinue -Force
| Where-Object { $_.Attributes -eq "Directory"}
| ForEach-Object {Write-Host $_.Name, $_.GetFiles().Count }
The prints to the screen, but writing to a file is simple.
The scans all the way down. If you just want the first layer of folders, remove the "-Recurse"
The advantage is that you are doing real actions on real objects, instead of trying to trick the OS into doing things it's not really intended for, by parsing text.
Here, it should be relatively simple to figure out what this is doing, even if you have little knowledge of PowerShell (Get a list of children, from this path, which are directories, and for each of them, print it's name, and the count of files in it.)

batch file to count all files in a folder and subfolder files and write output file in folder name using batch file

How to count files in a folder and subfolder files also. folder name and file count and sub folder name and file count.
Ex:
test1 : 2
test1\test11 :3
newfolder : 5
newfolder : 10
like this.
robocopy "x:\target\folder" "x:\target\folder" /l /nocopy /is /e /nfl /njh /njs
or
pushd "x:\target\folder" && (
robocopy . . /l /nocopy /is /e /nfl /njh /njs
popd
)
This will simply call robocopy, but instead of copying anything, we will request that nothing will be copied and only return the list of what should be processed (/nocopy /l). We request to copy from the current folder (a previous pushd command is used) to the current folder, including identical files in the process (/is), processing empty subfolder (/e, to include folders with 0 files), but only process two levels (/lev:2 the current folder and the one below), without generating a file list (/nfl), no job header (/njh) and no job summary (/njs)
The result will be the same list but with the folder name and the number of files in changed columns
To keep the original output format
#echo off
setlocal enableextensions disabledelayedexpansion
pushd "x:\target\folder" && (
for /f "tokens=1,*" %%a in ('
robocopy . . /l /nocopy /is /e /nfl /njh /njs
') do echo %%~fb : %%a
popd
)
This will use a for /f to process the previous robocopy command, splitting the line in two tokens, the first will contain the number of files and will be stored in %%a, and the second the rest of the line and will be stored in %%b. For each line in the output of the inner robocopy command, the code in the do clause is executed: just echo to console the two tokens in reverse order.
If robocopy can not be used (OS older than Vista), then
#echo off
setlocal enableextensions disabledelayedexpansion
for /d /r "x:\target\folder" %%a in (*) do for /f "tokens=1,5" %%b in ('
dir /a-d "%%~fa.\*" 2^> nul ^| findstr /b /c:" " ^|^| echo 0
') do if "%%c"=="" echo %%~fa : %%b
This will
For each folder under the start directory (for /r /d) grab a reference and store it in %%a replaceable parameter
Run a dir command with the full path of the folder %%~fa
Use a pipe (|) to filter the list to only retrieve the lines that start with two spaces (the footer lines)
If no lines are found (that is, the dir command failed) output a 0
The lines generated by the dir | findstr are handled with a for /f command. We will read the first token (the number of files in the adecuated line) and the fifth (only present in the footer line with the directories info)
If the fifth element is empty, this line has information about the files, not the folders, so, echo the folder path and the files inside it
At the end, restore the previous active directory
The problem with this approach is that the dir | findstr is executed for each of the subfolders. This will make this slower than the robocopy solution.
It can be faster, but more code is needed
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=%cd%"
set "folder="
for %%r in ("%root%") do (
set "rootDrive=%%~dr\"
if not defined rootDrive set "rootDrive=\\"
for /f "delims=" %%a in ('
dir /s /a "%%~fr.\*" 2^>nul ^| findstr /r /c:"^ " /c:"^ .*\\."
') do for /f "tokens=1,* delims=\" %%b in ("%%~a") do if not "%%c"=="" (
set "folder=%%c"
) else if defined folder (
for /f %%d in ("%%~a") do (
setlocal enabledelayedexpansion
echo(!rootDrive!!folder! : %%d
endlocal
)
set "folder="
)
)
In this case, we will execute only one dir command to retrieve the full list and filter with findstr the list to only retrieve the lines with the name of the folder and the footer for each folder.
The code will iterate (for /f %%a) over this list. When a folder line is found (it contains a backslash and can be splitted by the for /f %%b), the folder name is saved in a variable. When the corresponding footer line is readed (it was not splitted by a backslash), the line is splitted to get the number of files (for /f %%d) and the folder name and the number of files are echoed to console (here delayed expansion is needed to retrieve the variable contents, as they were set inside the same loop)
Note: Using a for /f to process a large list of data generated by executing a command can be really slow (there is a bad behaviour in how the command handles memory). For this cases it is necessary to first execute the command that generates the data (dir | findstr in this case) sending its output to a file and then use the for /f to process the file.
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=%~1"
if not defined root for %%a in (.) do set "root=%%~fa"
set "folder="
for %%t in ("%temp%\%~nx0.%random%%random%.tmp") do for %%r in ("%root%") do (
set "rootDrive=%%~dr\"
if not defined rootDrive set "rootDrive=\\"
( dir /s /a "%%~fr.\*" 2^> nul | findstr /r /c:"^ " /c:"^ .*\\." ) > "%%~ft"
for /f "usebackq delims=" %%a in ("%%~ft") do (
for /f "tokens=1,* delims=\" %%b in ("%%~a") do if not "%%c"=="" (
set "folder=%%c"
) else if defined folder (
for /f %%d in ("%%~a") do (
setlocal enabledelayedexpansion
echo(!rootDrive!!folder! : %%d
endlocal
)
set "folder="
)
)
del /q "%%~ft"
)
edited to adapt to comments: robocopy solution changed to include total number of files
#echo off
setlocal enableextensions disabledelayedexpansion
set "total=0"
pushd "x:\target\folder" && (
for /f "tokens=1,*" %%a in ('
robocopy . . /l /nocopy /is /e /nfl /njh /njs
') do (
echo %%~fb : %%a
set /a "total+=%%a"
)
popd
)
echo Total files: %total%

How to create file in partially known folder name using batch file

I want to create a 0 byte file names dblank in a specific directory C:\Users\myUser\*.data\.
echo. 2>"C:\Users\myUser\*.data\dblank.txt"
The * sign in the above command refers to any letters or numbers. I do not know. How can I refer to any letters or numbers in my batch code?
Maybe this:
setlocal enableextensions
for /D %%i in (C:\Users\myUsers\*.data) do copy nul "%%~i\dblank.txt"
endlocal
You can omit setlocal/endlocal if command extensions are already enabled (cmd /E:on).
This works on every existing *.data folder, if any.
#echo off
for /f "delims=" %%f in ('dir /b /s /ad C:\Users\myUser\*.data') do echo. 2>"%%f\dblank.txt"
EDIT
Filter results:
#echo off
for /f "delims=" %%f in ('dir /b /s /ad C:\Users\myUser\*.data^|findstr /r "\\[0-9a-zA-Z]*\.data$"') do (
echo. 2>"%%f\dblank.txt"
)

Resources