Search code examples
windowsbatch-file

How can I set a text value according to a position


I have several files in a folder; for each file, I need to parse its filename according to a specific format. The filenames can be something like: .\dir1\value1-value2-value3-2.45.13.EXT .\dir1\value1-value2-value4-value5-1.17.04.EXT In short, the format for each file I need:

  1. Remove the relation path
  2. Remove the first 2 words including the hyphens.
  3. Remove the file extension.
  4. Split the remaining to 2 text values, one is the remaining text without the numbers, and the second text is the remaining numbers. So, the output for the 2 filenames should be:
| value3 | 2.45.13 |
| value4-value5 | 1.17.04 |

I tried the following code:

@echo off
setlocal enabledelayedexpansion

for %%p in (
    .\dir1\*.EXT
) do (
    rem This is the full name of the file (relative path + file extension )
    set "relationFileName=%%p"

    rem This is the full name of the file without its relative path
    for %%f in ("!relationFileName!") do set filename=%%~nxf
    rem Removing the file extension
    set "stripped_file=!filename:~0,-4!"

    rem Getting the length of the filename - function is located below
    call :strlen filename_length stripped_file 
    rem echo file=!stripped_file!(!filename_length!^)

    set "delimiter=-"

    rem Iterate over the string from the end to the beginning
    for /l %%i in (0, 1, !filename_length!) do (
    set "index=%%i"
    set char=!stripped_file:~%%i,1!
    rem echo !stripped_file![!index!] = !char!
    if "!char!"=="!delimiter!" set lastIndex=!index!

    )
    set /a nextIndex=lastIndex + 1
    rem reducing the prefix of `value1-value2-` which we don't want to print out.
    set "prefixLength=14"
    set /a packageNameLength = lastIndex - prefixLength

    rem breaking the filename where the last hyphen character was found
    set packageName=!stripped_file:~%prefixLength%,%packageNameLength%!
    set packageVersion=!stripped_file:~%nextIndex%!

    echo ^| !packageName! ^| !packageVersion! ^|
)

endlocal

REM ********* function *****************************
:strlen <resultVar> <stringVar>
(   
    setlocal EnableDelayedExpansion
    (set^ tmp=!%~2!)
    if defined tmp (
        set "len=1"
        for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
            if "!tmp:~%%P,1!" NEQ "" ( 
                set /a "len+=%%P"
                set "tmp=!tmp:~%%P!"
            )
        )
    ) ELSE (
        set len=0
    )
)

The output that I am getting is:

| | value1-value2-value3-2.45.13 |
| | value1-value2-value4-value5-1.17.04 |

The problem is that the 2 lines that stores the position where the text should be split do not evaluate in the set command (that splits the text). You can see in the code that for prefixLength, packageNameLength & nextIndex, their values are not being evaluated in the 2 set commands:

    set packageName=!stripped_file:~%prefixLength%,%packageNameLength%!
    set packageVersion=!stripped_file:~%nextIndex%!

Just to be sure, it outputs the correct values when I am echoing these variables (prefixLength, packageNameLength & nextIndex). It also works correctly when I am hardcoding the values directly into the set command. But, when I put these variables into the set command, they don't work. Does anyone know how can I fix this?


Solution

  • 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)

    @ECHO Off
    SETLOCAL enabledelayedexpansion
    
    :: for testing, a set of filenames
    
    SET "filenames=.\dir1\value1-value2-value3-2.45.13.EXT .\dir1\value1-value2-value4-value5-1.17.04.EXT"
    
    FOR %%e IN (%filenames%) DO (
     FOR /f "tokens=2,* delims=-" %%b IN ("%%~ne") DO (
      rem %%~ne removes the extension & relpath. %%c has the first 2 values removed
      SET "remainder=%%c"
      SET "remainderelements=!remainder:-= !"
      FOR %%o IN (!remainderelements!) DO SET "numpart=%%o"&SET "valuepart=!remainder:-%%o=!"
      ECHO %%e --^> !valuepart! + !numpart!
     )
    )
    GOTO :EOF
    

    Given that %%e contains the filename, eg. .\dir1\value1-value2-value3-2.45.13.EXT,

    %%~ne will be the string containing simply the name portion of the string, ie value1-value2-value3-2.45.13 (see for /? from the prompt for docco). [procedure REM comments corrected]

    for /f then tokenises that string (quoted without wildcards) using - as a delimiter so that
    token 1 contains value1
    token 2 contains value2
    token * contains value3-2.45.13 (the remainder of the string)

    Since tokens 2 and * are selected, token 2 is assigned to %%b (the chosen metavariable) and token * to %%c (next-alphabetical variable)

    remainder is then assigned value3-2.45.13 since %%c cannot be substringed.

    remainderelements is then assigned value3 2.45.13 by replacing each - with a space.(see set /? from the prompt for docco)

    %%o is then set to value3 & 2.45.13 in turn, since remainderelements contains a simple space-separated list.

    numpart is set to each element in that list in turn, so it remains set to the last element, 2.45.13 when the loop ends.

    valuepart similarly is set to the value of remainder (value3-2.45.13) with - + the last element, 2.45.13 replaced by nothing on the last iteration of the for %%o loop.

    Bingo!