Search code examples
loopsbatch-filecmdglob

batch script delete files when file name does not match list of patterns


I want to delete all files in a directory except those whose name matches a certain pattern. A typical example of the kind of files in this directory is as follows:

  • Bookmarks.xml ---- to delete
  • DownloadMeta.xml ---- to delete
  • logfile.log ---- to delete
  • 1745388844.idx ---- to delete
  • TS1.c.pickle ---- to delete
  • TS1.prm.bak ---- to delete
  • !clear.bat ---- keep
  • .gitignore ---- keep
  • BookFlight.c ---- keep
  • CheckItinerary.c ---- keep
  • combined_TS1.c ----keep
  • pre_cci.c ---- keep
  • TS1.prm ---- keep
  • TS1.usr ---- keep
  • vuser_end.c ---- keep
  • vuser_init.c ---- keep
  • globals.h ---- keep
  • ScriptUploadMetadata.xml ---- keep

i want to keep files with names in a list of patterns:

list_to_ignore = ["!clear.bat", ".gitignore", "*.usr", "default.cfg", 
                    "default.usp", "*.c", "*lobals.h", "*custom_body.h", 
                    "*body_variables.txt", "*loadMetadata.xml", "*.prm" ]

For example:

  • file TS2.c.pickle does not match any item in the list, because it ends with .pickle extension. It should be deleted.

  • file somefile.c matches "*.c" pattern, because it ends with .c. It should be kept.

  • file Metadata.xml does not match any pattern, because it lacks prefix "load" at the beginning. It should be deleted.

  • file Globals.h matches "lobals.h pattern. It should be kept.

Here is what I tried:

@echo off
FOR /d %%a in ("./*") DO rd "%%a" /q /s
FOR %%i in (*.*) DO ^
if not "%%i"=="!clear.bat" ^
if not "%%i"==".gitignore" ^
if not "%%i"=="*.usr" ^
if not "%%i"=="default.cfg" ^
if not "%%i"=="default.usp" ^
if not "%%i"=="*.c" ^
if not "%%i"=="*lobals.h" ^
if not "%%i"=="*custom_body.h" ^
if not "%%i"=="*body_variables.txt" ^
if not "%%i"=="*ploadMetadata.xml" ^
if not "%%i"=="*.prm" ^
DEL /s /q "%%i"
pause

Here is a list of all files in a local directory with check marks

Hope this makes sense. If this is not an appropriate question let me know.

Many thanks


Solution

  • This could be done with the following command line in the batch file:

    @for /F "eol=| delims=" %%I in ('dir "%~dp0" /A-D /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /X /V /C:"!clear.bat" /C:".gitignore" /C:"BookFlight.c" /C:"CheckItinerary.c" /C:"combined_TS1.c" /C:"pre_cci.c" /C:"TS1.prm" /C:"TS1.usr" /C:"vuser_end.c" /C:"vuser_init.c" /C:"globals.h" /C:"ScriptUploadMetadata.xml" /C:"%~nx0"') do @del /A /F "%~dp0%%I"
    

    This command line results in starting one more command process in background with %ComSpec% /c and the command line between ' appended as additional arguments. So executed is with Windows installed to C:\Windows and full qualified file name of batch file being C:\Temp\Test.bat:

    C:\Windows\System32\cmd.exe /c dir "C:\Temp\" /A-D /B 2>nul | C:\Windows\System32\findstr.exe /I /L /X /V /C:"!clear.bat" /C:".gitignore" /C:"BookFlight.c" /C:"CheckItinerary.c" /C:"combined_TS1.c" /C:"pre_cci.c" /C:"TS1.prm" /C:"TS1.usr" /C:"vuser_end.c" /C:"vuser_init.c" /C:"globals.h" /C:"ScriptUploadMetadata.xml" /C:"Test.bat"
    

    DIR outputs to handle STDOUT (standard output)

    • just the names of files because of option /A-D (attribute not directory)
    • matching the default wildcard pattern * (any file name)
    • found in specified directory C:\Temp
    • in bare format because of option /B which means just file name and file extension.

    It is not really possible that command DIR outputs an error message in this case because of no directory entry found matching these criteria to handle STDERR (standard error) as there must be the batch file in this directory. But 2>nul would redirect this error message to handle STDERR of the command process started in background to suppress it.

    The output of DIR is redirected with | to STDIN (standard input) of FINDSTR which searches

    • case-insensitive because of option /I
    • and literally because of option /L
    • for lines which completely match because of option /X
    • one of the search strings specified with the options /C:
    • and outputs to handle STDOUT of background command process the inverted result because of option /V which means all lines NOT being completely ANY of the searched strings.

    Read also the Microsoft article about Using command redirection operators for an explanation of 2>nul and |. The redirection operators > and | 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 findstr with using a separate command process started in background.

    FOR captures the output to handle STDOUT of background command process and processes this output line by line after started cmd.exe terminated itself after finishing execution of the command line.

    FOR skips all empty lines which do not occur here. FOR would next split up each line into substrings using the characters normal space and horizontal tab as string delimiters. A line would be ignored by FOR on first substring starting with ; being the default end of line character. Otherwise just the first space/tab delimited string would be assigned to loop variable I for further processing.

    This line splitting behavior is not wanted here as file names can contain one or more spaces and can start with a semicolon after 0 or more leading spaces. For that reason option eol=| is used to define vertical bar as end of line character which no file name can contain ever and option delims= is used to define an empty list of string delimiters to turn off splitting the file names up into substrings.

    Therefore each file name output by DIR not being one of the strings specified as search strings for FINDSTR is assigned completely to the loop variable I and FOR executes the command DEL which deletes the file independent on being a hidden file because of using option /A and even on being a read-only file because of option /F.

    The command line rewritten for using FINDSTR with regular expressions to filter out file names matching one of the search patterns:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "BatchFileName=%~nx0"
    set "BatchFileName=%BatchFileName:.=\.%"
    for /F "eol=| delims=" %%I in ('dir "%~dp0" /A-D /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R /X /V /C:"!clear\.bat" /C:"\.gitignore" /C:"^.*\.usr" /C:"default\.cfg" /C:"default\.usp" /C:"^.*\.c" /C:"^.*lobals\.h" /C:"^.*custom_body\.h" /C:"^.*body_variables\.txt" /C:"^.*ploadMetadata\.xml" /C:"^.*\.prm" /C:"%BatchFileName%"') do @del /A /F "%~dp0%%I"
    endlocal
    

    Note: FINDSTR option /R is used instead of /L for a regular expression search which requires . being escaped with \ to be interpreted as literal character and * being modified to ^.* to match any character from beginning of line 0 or more times.

    The same can be achieved with batch file name not containing a space character also by using:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "BatchFileName=%~nx0"
    set "BatchFileName=%BatchFileName:.=\.%"
    for /F "eol=| delims=" %%I in ('dir "%~dp0" /A-D /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R /X /V "!clear\.bat \.gitignore ^.*\.usr default\.cfg default\.usp ^.*\.c ^.*lobals\.h ^.*custom_body\.h ^.*body_variables\.txt ^.*ploadMetadata\.xml ^.*\.prm %BatchFileName%"') do @del /A /F "%~dp0%%I"
    endlocal
    

    FINDSTR interprets a space in a search string specified with just "..." as OR expression while a space in a search string specified with /C:"..." is interpreted literally as space character.

    To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

    • call /? ... explains %~dp0 ... drive and path of argument 0 which is always the full path of the batch file currently executed by Windows command processor and ending always with a backslash and %~nx0 ... file name with extension of the batch file.
    • del /?
    • dir /?
    • endlocal /?
    • findstr /?
    • for /?
    • set /?
    • setlocal /?