Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I Detect (and Delete) a File if it is Empty using a Windows Batch File?

How do I detect if a file is empty using a Windows batch file? I want to delete error logs if they are empty.

I am aware for for loop solutions such as https://web.archive.org/web/20171110203759/http://anderwald.info/scripting/windows-batch-delete-all-empty-files-in-a-specified-folder/ but I was wondering if there was a more elegant solution if you know the specific single file in question that needs to be tested, as I may not wish to delete other zero byte files in the folder.

like image 805
Xonatron Avatar asked Feb 21 '12 14:02

Xonatron


2 Answers

The link you cited used the following to delete all 0 length files

for /F "delims=" "%I" in ('dir /B') do if not exist "%I\" if %~zI EQU 0 del "%I"

That is more complicated and not as efficient as it could be. It is much simpler to simply use:

for %F in (*) do if %~zF equ 0 del "%F"

You want to delete a specific file if it is zero length. So just substitute your file name for the * wild card.

for %F in ("yourFileName") do if %~zF equ 0 del "%F"

If you are going to use this in a batch file than you need to double all the percents (%%F, %%~zF)

If you don't want to use a FOR loop, you can use a CALL parameter instead - it uses the same modifiers as FOR variables. However, CALL is slower than FOR (probably not significant in your case)

@echo off
call :deleteIfEmpty "yourFileName"
exit /b

:deleteIfEmpty
if %~z1 eq 0 del %1
exit /b

EDIT

I've thought of yet another way. A FINDSTR search string of "^" will match all lines of a file. But an empty file doesn't have any lines. So simply delete the file if FINDSTR fails to match a line.

>nul findstr "^" "yourFileName" || del "yourFileName"
like image 57
dbenham Avatar answered Sep 28 '22 02:09

dbenham


I've managed to make a macro that can actually delete empty files and empty folders within a folder hierarchy.

I took me a while to fiddle it out, but now it works:

@ECHO OFF
SET topLevel=%cd%
FOR /D /R %%D IN (*) DO ( 
  CD %%D
  FOR %%F IN (*) DO IF %%~zF EQU 0 DEL "%%F"
)
CD %topLevel%
FOR /F "usebackq delims=" %%D IN (`"DIR/AD/B/S|SORT/R"`) DO RD "%%D"

It first disables echoing (remove @ECHO OFF if you want to read what actually happens). Then it stores the current folder in topLevel variable. Next is to walk trough all folders "%D" using the FOR command in the current folder and all sub folders. It changes local directory to each of the sub folders found (CD %%D). Within each sub folder using another FOR loop it finds and deletes all files %%F for which the filesize (~z for %%~zF) is 0. When this entire dual loop is done all empty files are effectively killed from the disk. Now a new FOR command is exectuted to perform a RD %%D to remove every directory. Due to DOS being safe here it will delete only EMPTY folders. Folders with files inside keep intact.

But hey, who says you can't improve once more?

I've revamped the script once more, now heavily optimized for speedy processing:

@ECHO OFF
SET topLevel=%CD%
FOR /D /R %%D IN (*) DO (
  CD %%D 
  CALL :innerLoop
)
CD %topLevel%
FOR /F "usebackq delims=" %%D IN (`"DIR /AD/B/S | SORT /R"`) DO RD "%%D"
GOTO :break

:innerLoop
  FOR /F "delims=" %%F IN ('DIR/B/A-D/OS') DO IF %%~zF EQU 0 (DEL "%%F") ELSE (GOTO :break)

:break

The problem with the previous one was that the two nested FOR loops touched every single file. As empty files are rare, there is absolutely no need to touch every file and its a big waste of time. I've tried to run it on a 25 TByte volume with ~5 million files..

So I modified the inner loop to sort files by size in a DIR command using the /OS (Ordered Size) option. The /A-D option does not list directories, so only true files remain (directories are listed as size 0 too, this is why I added this). For every file the size is checked. Once a file larger than 0 bytes is found, the loop is exited using the GOTO :break. As the files are sorted by size, smallest first, its safe to proceed like this. A huge timesafer!

As DOS FOR command has no elegant way to leave the loop I used this strange construct to call the inner loop using GOTO :break.

It seems to run about a few thousand times faster on my large volume than the previous one ;-)

I hope you like it!

Best regards, Axel Mertes

PS: The biggest nightmare in DOS scripting is understanding when you need %% or %, ' or " etc.

like image 26
Axel Mertes Avatar answered Sep 28 '22 01:09

Axel Mertes