Jump to content

List (and, optionally Delete) zero-byte files


CarlD
 Share

Recommended Posts

List (and, optionally, Delete) 0-byte files in current or specified directory; optionally, recurse through subdirectories.

The heart of the matter is func _ListZeroByteFiles().

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Outfile=ListZero.exe
#AutoIt3Wrapper_UseUpx=y
#AutoIt3Wrapper_Change2CUI=y
#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d
#AutoIt3Wrapper_Run_Au3Stripper=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
;
; List (and, optionally, Delete) Zero-byte Files
;  in current|specified directory
; Optionally, recurse through subdirs
; CLD rev. 2021-06-20
;
#cs

Usage
-----
ListZero d:\path1[ d:\path2 ...] [/R] [/-A] [/V] [/D[R]] /X:exclude_file [/?|/H]

d:\path  Path(s) to search (at least one is *required*)
/R       Recurse through subdirectories
/-A      Omit check for alternative data streams (ADS)
/V       Verbose: list subdirectory names in addition to files
/D       Delete listed files (NOT recommended on system drive)
/DR      Move listed files to Recycle bin
/X:exclude_file
         Exclude files with *extensions* listed in exclude_file
           (one extension per line)
/?|/H    Help

#ce

#include <Misc.au3>
#include <WinAPIFiles.au3>

Global $sDir = "."
Global $bSubDirs = 0, $sMsg = "", $iDeles = 0, $iFiles = 0, $bQuiet = 1
Global $sWinDrive = StringLeft(@WindowsDir, 2), $sTestStr = $sDir
Global $vDele = 0, $sDeleWord = "", $sLastWord = "Deleted"
Global $sExtFn = "", $aExts, $sExExts = ""
Global $bADS = 1, $bNTFS = 0, $iRecurseLimit = 0

; Test for at least one dir in command
Global $p = $CmdLine[0]
If $p Then
    For $i = 1 To $CmdLine[0]
        If StringInStr($CmdLine[$i], "/") = 1 Then $p -= 1
    Next
EndIf

If $p < 1 Or StringInStr($CmdLineRaw, "/?") Or StringInStr($CmdLineRaw, "/H") Then Exit Consolewrite("List (and, optionally, Delete) Zero-byte Files [CLD rev.2021-02-07]" & @CRLF & @CRLF & StringTrimRight(@ScriptName, 4) & " d:\path1[ d:\path2 ...] [/R] [/-A] [/V] [/D[R]] /X:exclude_file [/?|/H]" & @CRLF & @CRLF & "d:\path  Path(s) to search (at least one is *required*)" & @CRLF & "/R       Recurse through subdirectories" & @CRLF & "/-A      Omit check for alternate data streams (ADS)" & @CRLF & "/V       Verbose: list subdirectory names in addition to files" & @CRLF & "/D       Delete listed files (NOT recommended on system drive)" & @CRLF & "/DR      Move listed files to Recycle bin" & @CRLF & "/X:exclude_file" & @CRLF & "         Exclude files with *extensions* listed in exclude_file" & @CRLF & "           (one extension per line)" & @CRLF & "/?|/H    Help" & @CRLF)

If StringInStr($CmdLineRaw, "/R") Then $bSubDirs = 1
If StringInStr($CmdLineRaw, "/-A") Then $bADS = 0
If StringInStr($CmdLineRaw, "/D") Then
    $vDele = 2
    $sDeleWord = "Delet"
    If StringInStr($CmdLineRaw, "/DR") Then
        $vDele = 1
        $sDeleWord = "Recycl"
    EndIf
    $sDeleWord &= "ed ==> "
EndIf
If StringInStr($CmdLineRaw, "/V") Then $bQuiet = 0

If StringInStr($CmdLineRaw, "/X:") Then
    $sExtFn = StringTrimLeft($CmdLineRaw, 2 + StringInStr($CmdLineRaw, "/X:"))
    While StringInStr($sExtFn, " ") = 1
        $sExtFn = StringTrimLeft($sExtFn, 1)
    WEnd
    If StringInStr($sExtFn, """") = 1 Then
        $sExtFn = StringTrimLeft($sExtFn, 1)
        If StringInStr($sExtFn, """") Then $sExtFn = _
            StringTrimRight($sExtFn, StringLen($sExtFn) - StringInStr($sExtFn, """"))
    Else
        If StringInStr($sExtFn, " ") Then $sExtFn = StringTrimRight($sExtFn, StringLen($sExtFn) - StringInStr($sExtFn, " "))
    EndIf
    If FileExists($sExtFn) Then
        Global $h1 = FileOpen($sExtFn)
        $sExExts = FileRead($h1)
        FileClose($h1)
        If $sExExts Then
            If StringInStr($sExExts, @CRLF) Then
                $aExts = StringSplit($sExExts, @CRLF)
                $sExExts = "."
                For $i = 1 To $aExts[0]
                    If $aExts[$i] Then $sExExts &= $aExts[$i] & "."
                Next
            Else
                $sExExts = "." & $sExExts & "."
            EndIf
        EndIf
    EndIf
EndIf

If $vDele = 2 And StringInStr($CmdLineRaw, $sWinDrive) Then
    ConsoleWrite("Warning: Deleting zero-byte files in the system drive (" & $sWinDrive & ") is not recommended." & @CRLF & "Are you sure you want to continue? (y|N)" & @CRLF)
    Global $hDLL = DLLOpen("user32.dll")
    While 1
        If _IsPressed("59", $hDLL) Then
            ConsoleWrite(@CRLF)
            ExitLoop
        ElseIf _IsPressed("1B", $hDLL) Or _IsPressed("4E", $hDLL) Then
            DLLClose($hDLL)
            ConsoleWrite(@CRLF & "Quitting..." & @CRLF)
            Exit
        Else
            Sleep(20)
        EndIf
    WEnd
    DLLClose($hDLL)
EndIf

If $bSubDirs Then ConsoleWrite("Working... (Ctrl+C quits)" & @CRLF)
For $i = 1 To $CmdLine[0]
    If StringInStr($CmdLine[$i], "/") = 1 Then ContinueLoop
    If $CmdLine[$i - 1] = "/X" Then ContinueLoop
    If StringRight($CmdLine[$i], 1) <> "\" Then $CmdLine[$i] &= "\"
    $CmdLine[$i] &= "\"
    $bNTFS = _SetbNTFS($CmdLine[$i])
    $iDeles += _ListZeroByteFiles($CmdLine[$i], $bSubDirs)
Next
If $vDele = 1 Then $sLastWord = "Recycled"
Global $iDiff = 7 + StringLen("Deleted") - StringLen($sLastWord) 
$sMsg = "Found   " & StringFormat("%7s", _IntFormat($iFiles)) & " " & _OneMany("file", $iFiles) & @CRLF & $sLastWord & " " & StringFormat("%" & String($iDiff) & "s", _IntFormat($iDeles)) & " " & _OneMany("file", $iDeles)

Exit ConsoleWrite(@CRLF & $sMsg & @CRLF)
;---

Func _ListZeroByteFiles($sPath = ".", $bRecurs = 0)
    ; List|Delete 0-byte files in current or specified directory
    ; Optionally, recurse through subdirectories
    ; Returns number of 0-byte files deleted
    $iRecurseLimit += 1
    If $iRecurseLimit = 200 Then
        $iRecurseLimit = 0
        Return
    EndIf
    If StringRight($sPath, 1) <> "\" Then $sPath &= "\"
    If StringRight($sPath, 1) = "\" Then $sPath &= "*"
    If Not FileExists($sPath) Then Return 0
    Local $sFn = "", $iC = 0, $iC2 = 0, $bEx = 0
    Local $sDirName = _FileGetPath($sPath)
    Local $h = FileFindFirstFile($sPath)
    If $h = -1 Then Return 0
    While 1
        $sFn = $sDirName & "\" & FileFindNextFile($h)
        If @error Then
            FileClose($h)
            ExitLoop
        EndIf
        $bEx = @extended
        If StringInStr($sFn, ":") = 2 Then $sFn = StringUpper(StringLeft($sFn, 1)) & StringTrimLeft($sFn, 1)
        If $bEx = 0 Then; we have a file
            If _FileGetSizeADS($sFn) = 0 Then
                If $sExExts Then
                    If StringInStr($sExExts, "." & _FileGetExt($sFn) & ".") Then ContinueLoop
                EndIf
                $iFiles += 1
                Switch $vDele
                    Case 0
                    Case 1
                        If FileRecycle($sFn) Then $iC += 1
                    Case 2
                        If FileDelete($sFn) Then $iC += 1
                EndSwitch
                ConsoleWrite($sDeleWord & $sFn & @CRLF)
            EndIf
        Else; we have a directory
            If $bRecurs Then
                If $bQuiet = 0 Then ConsoleWrite("Searching " & $sFn & @CRLF)
                $iC2 = _ListZeroByteFiles($sFn, 1)
                If $iC2 > 0 Then $iC += $iC2
                $iC2 = 0
            EndIf
        EndIf
    WEnd
    Return $iC
EndFunc   ;==>_ListZeroByteFiles

Func _FileGetExt($sFn)
; Parse ext from [d:\path\]filename.ext
    Local $sExt = "", $aA
    If StringInStr($sFn, ".") Then
        If StringInStr(FileGetAttrib($sFn), "D") Then Return $sExt
        $aA = StringSplit($sFn, ".")
        $sExt = $aA[$aA[0]]
    EndIf
    Return $sExt
EndFunc  ;==>_FileGetExt

Func _FileGetPath($sFn)
; Parse directory from path\file; final "\" is trimmed
    Local $sDirr = ""
    Local $aA = StringSplit($sFn, "\")
    Local $iS = $aA[0] - 1
    If Not StringInStr(FileGetAttrib($aA[$aA[0]]), "D") Then $iS += 1
    For $i = 1 To $iS
        $sDirr &= $aA[$i] & "\"
    Next
    While StringRight($sDirr, 1) = "\"
        $sDirr = StringTrimRight($sDirr, 1)
    WEnd
    Return $sDirr
EndFunc  ;==>_FileGetPath

Func _FileGetSizeADS($sFile)
    Local $sErrW, $sFnTmp, $aFnTmp
    Local $iSize = FileGetSize($sFile)
    If Not ($bADS And $bNTFS) Then Return $iSize
    $iSize = 0
    Local $pData = _WinAPI_CreateBuffer(1024)
    Local $tFSD = DllStructCreate($tagWIN32_FIND_STREAM_DATA)
    Local $hSearch = _WinAPI_FindFirstStream($sFile, $tFSD)
    While Not @error
        $iSize += DllStructGetData($tFSD, 'StreamSize')
      _WinAPI_FindNextStream($hSearch, $tFSD)
    WEnd
    Switch @extended
        Case 38 ; ERROR_HANDLE_EOF
    Case Else
            $sErrW = _WinAPI_GetErrorMessage(@extended)
            $aFnTmp = StringSplit($sFile, "\")
            $sFnTmp = $aFnTmp[$aFnTmp[0]]           
        If Not StringRight($sErrW, 13) = "successfully." Then _
                ConsoleWrite("--> " & $sFnTmp & ": " & $sErrW & @CRLF)
    EndSwitch
    _WinAPI_FindClose($hSearch)
    _WinAPI_FreeMemory($pData)
    Return $iSize
EndFunc  ;==>_FileGetSizeADS 

Func _IntFormat($n, $s = ",", $sd = ".")
; Insert commas|specified separator into integer
    If StringIsInt($n) Then
        Local $a, $d = "", $x
        If StringInStr($n, $sd) Then
            $a = StringSplit($n, $sd)
            $n = $a[1]
            $d = $sd & $a[2]
        EndIf
        $x = $n
        $n = ""
        While StringLen($x) > 3
            $n = $s & StringRight($x, 3) & $n
            $x = StringTrimRight($x, 3)
        WEnd
        $n = $x & $n
    EndIf
    Return ($n & $d)
EndFunc  ;==>_IntFormat

Func _OneMany($sSingular, $iCount, $sPlural = "")
; Returns singular or plural depending on count
; Plural appends S to singular (ES if it ends in S)
;  unless $sPlural is supplied
    Local $sEss = "s"
    If StringRight($sSingular, 1) = "s" Then $sEss = "es"
    If StringUpper($sSingular) == $sSingular Then $sEss = StringUpper($sEss)
    If Not $sPlural Then $sPlural = $sSingular & $sEss
    If Abs($iCount) = 1 Then
        Return $sSingular
    Else
        Return $sPlural
    EndIf
EndFunc  ;==>_OneMany

Func _SetbNTFS($sPath)
    If DriveGetFileSystem(StringLeft($sPath, 3)) = "NTFS" Then
        Return 1
    Else
        Return 0
    EndIf
EndFunc  ;==>_SetbNTFS

 

Edited by CarlD
Update
Link to comment
Share on other sites

What's creating these 0-byte files? I'm not sure that I've seen any outside of empty text files before I edit them.

All my code provided is Public Domain... but it may not work. ;) Use it, change it, break it, whatever you want.

Spoiler

My Humble Contributions:
Personal Function Documentation - A personal HelpFile for your functions
Acro.au3 UDF - Automating Acrobat Pro
ToDo Finder - Find #ToDo: lines in your scripts
UI-SimpleWrappers UDF - Use UI Automation more Simply-er
KeePass UDF - Automate KeePass, a password manager
InputBoxes - Simple Input boxes for various variable types

Link to comment
Share on other sites

On 2/2/2021 at 4:44 PM, seadoggie01 said:

What's creating these 0-byte files? I'm not sure that I've seen any outside of empty text files before I edit them.

It seems that all sorts of programs create (and, if they're nice, maybe eventually delete) these files. I would NOT delete 0-byters on the system drive, ever. Only delete them in data directories where you know what you're deleting. Or just ignore this script entirely -- it's kind of an idle curiosity. But if you're curious and just want to list the files without deleting anything, do DelZero C:\ /R /N /Q. 

Link to comment
Share on other sites

Turned this around so that, by default, it only lists 0-byters, does not delete them. Use the new option /D to delete the listed files. Compare:

ListZero C:\ /R /Q

  with:

ListZero C:\ /R /Q /D  <== NOT RECOMMENDED

Edited by CarlD
Update
Link to comment
Share on other sites

@CarlD 

Unfortunately, your script does not consider alternate data streams (ADS).
Here's the proof:
Open the command prompt and enter the following commands:
1.)  echo This is a file with valid data>test.txt:valid_data
2.)  dir / R test.txt

Test.txt is listed as empty file in your script, but it contains 32 bytes. The proof is:

3.)  notepad test.txt:valid_data

App: Au3toCmd              UDF: _SingleScript()                             

Link to comment
Share on other sites

5 hours ago, Exit said:

@CarlD 

Unfortunately, your script does not consider alternate data streams (ADS). ...

@Exit

You are right, of course. Will have to investigate as I've played with ADS exactly once in the past. Thank you for pointing this out.

In the meantime, I changed around some options, and added an option /X exclude_file, where you can list the extensions of files that you want to exclude from the listing.

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...