Search code examples
arraysbatch-filecmdduplicatesunique

Windows Batch - How do I create an array with a unique set of values?


I'm messing around in Batch and writing code that loops through files in a directory and adds the last four characters of the file name to an array. The problem is that there are duplicates in my array. Is there a way to only add the value to an array if it doesn't exist yet?

@echo off
setlocal enableDelayedExpansion
set i=0
for %%f in (*00.DIF) do (
  set fname=%%~nf
  set year=!fname:~-4!
  ::echo !year!
  set Obj[!i!].name=!year!
  set /a i=!i!+1
)
set lastindex=!i!

for /L %%f in (0,1,!lastindex!) do ( 
  echo !Obj[%%f].name!
)

Solution

    1. As already noted, within a block statement (a parenthesised series of statements), REM statements rather than the broken-label remark form (:: comment) should be used because labels terminate blocks, confusing cmd. :: is a broken label because you cannot reach :: with a goto, but it's a label nonetheless.

    2. Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. Use set /a var=value to set numeric values.

    3. I prefer to avoid ADFNPSTXZ (in either case) as metavariables (loop-control variables) ADFNPSTXZ are also metavariable-modifiers which can lead to difficult-to-find bugs (See for/f from the prompt for documentation)

    4. The !var! syntax is only required to access the modified value of var where var is varied within the code block (parenthesised sequence of commands).

    Hence, rewritten code:

    @ECHO OFF
    setlocal enableDelayedExpansion
    set /A i=0
    SET "chosen="
    REM for %%e in (*00.DIF) do (
    for %%e in (1234 0011 4567 7890 0011 4183) do (
      set fname=%%~ne
      set year=!fname:~-4!
      REM echo !year!
      set Obj[!i!].name=!year!
      set /a i+=1
      SET "unaltered=Y"
      FOR %%y IN (!chosen!) DO IF DEFINED unaltered IF "%%y"=="!year!" (
      SET "unaltered="
       set /a i-=1
       set "Obj[!i!].name="
      )
      IF DEFINED unaltered SET "chosen=!year! !chosen!"
    )
    set /a lastindex=i-1
    
    for /L %%e in (0,1,%lastindex%) do ( 
      echo !Obj[%%e].name!
    )
    GOTO :EOF
    

    New variable chosen is set to nothing.

    I've replaced your first for loop with a loop that simply processes a series of numbers for testing purposes. I've also change the metavariable throught from f to e.

    Use more modern syntax to increment i. see set/? from the prompt or endless SO items for docco.

    New loop on %%y to search chosen for previously-assigned values. Note the use of unaltered as a Boolean - It's either defined as Y or undefined. If it's defined, simply reverse the increment of i and delete the "array" string-value assigned. Using Boolean

    Then, if the value in year is new, unaltered is defined and the new value of year is appended to the list of values in chosen.

    After the entire list is processed, lastindex is set to i - 1 since i points to the next element to be filled.

    --- edit -----

    Having recaffienated, using Stephan's findstr idea...

    @ECHO OFF
    setlocal enableDelayedExpansion
    set /A i=0
    SET "chosen="
    REM for %%e in (*00.DIF) do (
    for %%e in (1234 0011 4183 4567 7890 0011 7890 7890 7890 4183) do (
      set fname=%%~ne
      set year=!fname:~-4!
      REM echo !year!
      ECHO !year!|FINDSTR ": !chosen!"  >NUL
      IF ERRORLEVEL 1 (
       set Obj[!i!].name=!year!
       set /a i+=1
       SET "chosen=!year! !chosen!"
      )
    )
    set /a lastindex=i-1
    
    for /L %%e in (0,1,%lastindex%) do ( 
      echo !Obj[%%e].name!
    )
    GOTO :EOF
    

    Here, if year is not in the string chosen then findstr will set errorlevel to 1; if it is then errorlevel will be set to 0.

    findstr by default will find any of the space-separated strings, but it doesn't like an empty string, so a colon is added to the list as a dummy; a colon is impossible in the name part of a filename.

    Only if yesr is missing from the list will it be added.

    We don't need the actual output of findstr, just that it sets errorlevel, so the output is sent to nowhere by the >nul.