Igor
Igor

Reputation: 15

batch file remove files by the specified extension

I need to delete all files with extensions which are not specified in the arguments (except .sys files). The problem is with an assignment of my variable.

Here are my commands (apologies for duplication, wasn't sure how to use or statement here):

@echo off
cls
cd test
for %%f in (*.*) do (
    set exist = false
    for %%a in (%*) do (
        if  %%~xf == .%%a set exist = true
        if  %%~xf == .sys set exist = true
    )
    if  %exist% == false ( 
        del %%f
    )
)
pause

Would be very grateful for any suggestions on how to get this code working as expected by me.

Upvotes: 0

Views: 291

Answers (1)

MC ND
MC ND

Reputation: 70923

The main problem with your code is the variable expansion. In batch files, when a command or a block of commands (several commands enclosed in parenthesis) are readed, they are parsed and then executed. During the parse phase, the variable read operations (%var%) are removed from the code being replaced with the value inside the variable. Once this has been done, the command or block is executed.

All this means that when the code is executed, if you change a variable (exists in your code), the changed value can not be retrieved while inside the same command/block, as there is not any variable read operation, only the value in the variable before starting the execute phase.

This can be avoided using setlocal enabledelayedexpansion. This command allows you to change where needed the syntax used to read a variable from %var% into !var!, indicating to the parser that the variable expansion must be delayed until the command is executed, not during parse phase.

So, your code could be something like

@echo off
    setlocal enabledelayedexpansion
    cls
    cd test
    for %%f in (*.*) do (
        set "exist=false"
        for %%a in (%*) do (
            if /i "%%~xf" == ".%%a" set "exist=true"
            if /i "%%~xf" == ".sys" set "exist=true"
        )
        if  "!exist!"=="false" ( 
            del "%%f"
        )
    )
    pause       

Note that

  • set var = value has been changed into set var=value. Spaces are important, you were defining a variable called var[space] with a value [space]value

  • all the variable assignments are quoted to prevent unneeded ending spaces included in the variable value. If spaces are included, the == test could fail. Being careful this is not needed, but it is a good habit.

  • quotes are being included to prevent problems with spaces in file names or extensions

  • the %exists% has been changed to !exists! so the changed value can be retrieved while inside the same block (for %%f)

  • the if extension comparisons include the /i switch for case insensitive tests.

You should also note that when having delayed expansion enabled, the ! character becomes a problem as the parser will see it as part of a variable read operation. If you have files or extensions that could include this character, delayed expansion is a problem. It can be done but requires to only enable delayed expansion when really needed and disable again when not required.

@echo off
    setlocal disabledelayedexpansion
    cls
    cd test
    for %%f in (*.*) do (
        set "exist=false"
        for %%a in (%*) do (
            if /i "%%~xf" == ".%%a" set "exist=true"
            if /i "%%~xf" == ".sys" set "exist=true"
        )
        setlocal enabledelayedexpansion
        if  "!exist!"=="false" ( 
            endlocal
            del "%%f"
        ) else (
            endlocal 
        )
    )
    pause

But, as in your case you are only changing the variable value between two values and the values are irrelevant, it is only a flag, you can ignore delayed expansion and the real value inside the variable and use an alternative syntax, if defined to test if the variable contains or not a value. Your true/false can be a defined/not defined test

@echo off
    setlocal enableextensions disabledelayedexpansion

    cd test
    for %%f in (*) do (
        set "removeFile=1"
        for %%x in (sys %*) do (
            if /i "%%~xf"==".%%~x" set "removeFile="
        )
        if defined removeFile del "%%f"
    ) 
    pause

If the extension matches any of the arguments, the variable content is removed, that is, now the variable is undefined.

edit to adapt to Magoo's comments

When dealing with the filesytem elements, it is possible to see unexpected behaviours in the file/folders selection.

Files and folders that don't follow the 8.3 traditional naming (up to 8 characters for the name, up to 3 for the extensions, no special characters, no spaces) have what is known as a long name, but they also have a short 8.3 name associated to the long name. It can be seen using the /x switch in the dir command.

While not directly visible, these short names are checked when a wildcard search is done, resulting in files/folders selected with long file names or extensions not matching the requested wildcard expresion but with short names or extensions that do match the wildcard.

In case the short names or extensions needs to be processed inside for loops, the modifiers used in for replaceable parameters (ex. %%f) to retrieve them are

%%~snxf    Short name and short extension of the element being referenced by %%f
%%~snf     Short name of the element being referenced by %%f
%%~sxf     Short extension of the element being referenced by %%f

Upvotes: 3

Related Questions