Strange behaviour of file mask in DIR command

Microsoft Windows
Post Reply
User avatar
MigrationUser
Posts: 336
Joined: 2021-Jul-12, 1:37 pm
Contact:

Strange behaviour of file mask in DIR command

Post by MigrationUser »

14 Jan 2010 14:12
anandr

Hi all

I have a program that creates series of images named, say, "Photo1.bmp", "Photo2.bmp", etc.
When number of files is more than 10 or 100, there is an issue with sorting these files, because
"Photo10.bmp" appears after "Photo1.bmp" and before "Photo2.bmp":

Code: Select all

C:\TEMP\TEST>dir /b
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo101.bmp
Photo102.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo3.bmp
Photo4.bmp
Photo5.bmp
Photo6.bmp
Photo7.bmp
Photo8.bmp
Photo9.bmp
The same sorting issue arises in the windows explorer.

I need to rename these files adding padding zeros like this (more padding zeros is also OK):
Photo001.bmp
Photo002.bmp
Photo003.bmp
Photo004.bmp
Photo005.bmp
Photo006.bmp
Photo007.bmp
Photo008.bmp
Photo009.bmp
Photo010.bmp
Photo011.bmp
Photo012.bmp
Photo013.bmp
Photo100.bmp
Photo101.bmp
Photo102.bmp
I tried to write a batch file that scans sequentially through one-, two-, three-digit files and adds padding zeroes.
But I stuck with unexpected behavior of the file mask in DIR command.
To be short here is a batch file that presents it:

Code: Select all

@Echo off
rem    % 1    file prefix
rem    % 2    file extension
Echo One-digit files:
FOR /F %%i IN ('dir /b %~1?.%~2') DO @(    Echo %%i    )
Echo.
Echo Two-digit files:
FOR /F %%i IN ('dir /b %~1??.%~2') DO @(    Echo %%i    )
Echo.
Echo Three-digit files:
FOR /F %%i IN ('dir /b %~1???.%~2') DO @(    Echo %%i    )
Echo.
When I run it like this

C:\TEMP\TEST>RenameFilesTo_6digits.cmd Photo bmp

I have the following output:
One-digit files:
Photo1.bmp
Photo2.bmp
Photo3.bmp
Photo4.bmp
Photo5.bmp
Photo6.bmp
Photo7.bmp
Photo8.bmp
Photo9.bmp

Two-digit files:
Photo1.bmp
Photo10.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo3.bmp
Photo4.bmp
Photo5.bmp
Photo6.bmp
Photo7.bmp
Photo8.bmp
Photo9.bmp

Three-digit files:
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo101.bmp
Photo102.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo3.bmp
Photo4.bmp
Photo5.bmp
Photo6.bmp
Photo7.bmp
Photo8.bmp
Photo9.bmp
I can not understand, why dir /b Photo?.bmp matches "Photo1.bmp" ... "Photo9.bmp", but
dir /b Photo??.bmp matches not just two-digit files, but also one-digit ones: "Photo1.bmp" ... "Photo9.bmp"?

Does anybody have an explanation of this?

I'm using WinXP SP2 PRO

C:\TEMP\TEST>ver
Microsoft Windows XP [Version 5.1.2600]

P.S.
It would be nice if someone can suggest a better idea how to rename such sequence of files.
By the way, sorting them in creation/modification order do not always work,
because some of them should be processed by other programs,
so their creation/modification time is altered.

Last edited by anandr (14 Jan 2010 15:08)

Everything will be OK at the end. If it is not OK -- it is not the end.

----------------------------

#2 14 Jan 2010 19:14
bluesxman


Yeah I don't think you will get around that easily, so a different approach is probably advisable.

How about something like this instead?

Code: Select all

@echo off

set "prefix=%~1"
set "ext=%~2"
set "pad.length=6"
set "pad="

for /l %%a in (0,1,%pad.length%) do call set "pad=%%pad%%0"

for /f "usebackq tokens=*" %%a in (`dir /b "%prefix%*.%ext%"`) do (

    set "number=%%~na"
    call set "number=%pad%%%number:%prefix%=%%"
    call set "newname=%prefix%%%number:~-%pad.length%%%%%~xa
    call rename "%%~a" "%%newname%%"

)
pause
Last edited by bluesxman (14 Jan 2010 22:10)

cmd | *sh | ruby | chef

----------------------------

#3 15 Jan 2010 14:10
Drewfus


anandr,
try this;

Code: Select all

@echo off
setlocal
for /F %%A in ('dir /B Photo*.bmp') do call :bmp %%~nA
goto :eof

:bmp
set bmp=%*
set bmp=%bmp:Photo=%
set bmp=000%bmp%
ren %*.bmp Photo%bmp:~-4%.bmp
goto :eof
Now sorting the files will work as you want...

dir /B *.bmp | sort

----------------------------

#4 15 Jan 2010 15:01
bluesxman


Er Drewfus, you've just used the same basic method as I suggested, but implemented it in a less flexible and robust way hmm

cmd | *sh | ruby | chef

----------------------------

#5 15 Jan 2010 16:36
anandr


Thank you both, bluesxman and Drewfus, for your replies.
I agree with bluesxman that his solution is more flexible.

Still, what's wrong with the file mask in DIR command?
Is this another example how MS makes documentation that reflects how things should work instead of how things really work?

Just one more question concerning the script: sometimes I have files named like this: "1.jpg", "2.jpg", ..., "10.jpg", "11.jpg", "1.jpg".
In this case file prefix is empty.
I tried to handle this and here is a script which satisfies me by 99.9%.
I'm only 99.9% satisfied with it, because I can not figure out how to treat "0.jpg" file in such sequence, so I'm checking for it presence manually, not through the main loop.
May be someone has an idea how to do this in nicer way?

Code: Select all

@echo off
@setlocal enabledelayedexpansion

set "Pad.Length.Default=6"

:: no parameters at all -- print usage and exit
if  [%~1%~2%~3] EQU [] goto PrintUsage

:: one parameter only -- print usage and exit
if  [%~2%~3] EQU [] goto PrintUsage

set "Prefix=%~1"
set "Ext=%~2"
set "Pad.Length=%~3"

if  [%Pad.Length%] EQU [] call set "Pad.Length=%%Pad.Length.Default%%"

set "Pad="
for /l %%a in (0,1,%Pad.Length%) do call set "Pad=%%Pad%%0"

::DEBUG: Echo                 Pad = "%Pad%"
::DEBUG: Echo              Prefix = "%Prefix%"
::DEBUG: Echo                 Ext = "%Ext%"
::DEBUG: Echo  Pad.Length.Default = "%Pad.Length.Default%"
::DEBUG: Echo          Pad.Length = "%Pad.Length%"

if  [%Prefix%]     EQU [] goto ProcessEmptyPrefix

:ProcessNonEmptyPrefix
for /f "usebackq tokens=*" %%a in (`dir /b "%Prefix%*.%Ext%"`) do (
    set "Number=%%~na"
    call set "Number=%Pad%%%Number:%Prefix%=%%"
    call set "NewName=%Prefix%%%Number:~-%Pad.Length%%%%%~xa"
    call Echo renaming    "%%~a"    "%%NewName%%"
    call rename "%%~a" "%%NewName%%"
)
goto :EOF

:ProcessEmptyPrefix
:: Currently I have no idea how to handle "0.%Ext%" file automatically, so
IF exist "0.%Ext%" (
        call set "Number=%Pad%"
        call set "NewName=%%Number:~-%Pad.Length%%%.%%Ext%%"
        call Echo renaming    "0.%%Ext%%"    "%%NewName%%"
        call rename "0.%%Ext%%" "%%NewName%%"
)
for /f "usebackq tokens=*" %%a in (`dir /b "*.%Ext%"`) do (
    set "Number=%%~na"
    SET /A NextNumber=1 + Number
    SET /A AnotherNumber=NextNumber + Number
:: Uncomment next line to see which files are accepted
::DEBUG:     Echo Number = "!Number!"; NextNumber = "!NextNumber!"; AnotherNumber = "!AnotherNumber!"; If NextNumber ^< AnotherNumber -- then we have number-named file
    IF  !NextNumber! LSS !AnotherNumber! (
        call set "Number=%Pad%%%Number%%"
        call set "NewName=%%Number:~-%Pad.Length%%%%%~xa"
        call Echo renaming    "%%~a"    "%%NewName%%"
        call rename "%%~a" "%%NewName%%"
    )
)

:PrintUsage
Echo.
Echo This script scans current directory for files named "<FilePrefix>#.<Extension>" (# is a number)
Echo and adds padding zeros to the number.
Echo.
Echo Usage:
Echo     %~n0 ^<FilePrefix^> ^<Extension^> [^<PaddingLength^>]
Echo.
Echo If ^<PaddingLength^> is omitted, then default value "%Pad.Length.Default%" is used.
Echo.
Echo To process files without prefix (like "1.jpg", "2.jpg", "13.jpg", etc.), run the script like this:
Echo     %~n0 "" ^<Extension^> [^<PaddingLength^>]
goto :EOF
Last edited by anandr (15 Jan 2010 16:46)

Everything will be OK at the end. If it is not OK -- it is not the end.

----------------------------

#6 15 Jan 2010 17:33
bluesxman


Amending my own code (shouldn't be too difficult to shoehorn this back into yours):

Code: Select all

@echo off

set "prefix=%~1"
set "ext=%~2"
set "pad.length=6"
set "pad="

for /l %%a in (0,1,%pad.length%) do call set "pad=%%pad%%0"

for /f "usebackq tokens=*" %%a in (`dir /b "%prefix%*.%ext%"`) do (

    set "number=%%~na"
    REM pad to 6 characters
    if defined prefix (call set "number=%pad%%%number:%prefix%=%%") ELSE (call set "number=%pad%%%number%%")
    call set "newname=%prefix%%%number:~-%pad.length%%%%%~xa
    call echo:rename "%%~a" "%%newname%%"

)

pause
You'd still have to specify a blank parameter with "", as you already instruct the user.

As for "dir" behaviour, I can see what it's doing -- it's treating "?" as zero or one occurrence of any character, in much the same way that "*" matches zero or any number of occurrences of any character:

Code: Select all

D:\My Documents\Scripts\ss64>dir Photo?.bmp
 Volume in drive D is Data
 Volume Serial Number is EF78-7FFA

 Directory of D:\My Documents\Scripts\ss64

15/01/2010  16:29                 0 Photo.bmp
15/01/2010  16:29                 0 Photo1.bmp
               2 File(s)              0 bytes
               0 Dir(s)  10,290,286,592 bytes free

D:\My Documents\Scripts\ss64>dir Photo*.bmp
 Volume in drive D is Data
 Volume Serial Number is EF78-7FFA

 Directory of D:\My Documents\Scripts\ss64

15/01/2010  16:29                 0 Photo.bmp
15/01/2010  16:29                 0 Photo1.bmp
15/01/2010  16:29                 0 Photo10.bmp
               2 File(s)              0 bytes
               0 Dir(s)  10,290,286,592 bytes free

D:\My Documents\Scripts\ss64>
cmd | *sh | ruby | chef

----------------------------

#7 15 Jan 2010 17:38
avery_larry


? wildcard doesn't always work like expected. It has quirks:

https://ss64.com/nt/syntax-wildcards.html

----------------------------

#8 15 Jan 2010 18:52
anandr


Thank you for the suggestion, bluesxman. I really like it :clap:
avery_larry wrote:

? wildcard doesn't always work like expected. It has quirks:
http://ss64.com/nt/syntax-wildcards.html
It seems that it has not just quirks, but QUIRKS.

First I create several files with this batch file:

Code: Select all

Copy %0 Photo0.bmp
Copy %0 Photo1.bmp
Copy %0 Photo2.bmp
Copy %0 Photo3.bmp
Copy %0 Photo10.bmp
Copy %0 Photo11.bmp
Copy %0 Photo12.bmp
Copy %0 Photo13.bmp
Copy %0 Photo100.bmp
Copy %0 Photo101.bmp
Copy %0 Photo102.bmp
Copy %0 Photo103.bmp
Copy %0 Photo1000.bmp
Copy %0 Photo1001.bmp
Copy %0 Photo1002.bmp
Copy %0 Photo1003.bmp
Copy %0 Photo10000.bmp
Copy %0 Photo10001.bmp
Copy %0 Photo10002.bmp
Copy %0 Photo10003.bmp
Copy %0 Photo100000.bmp
Copy %0 Photo100001.bmp
Copy %0 Photo100002.bmp
Copy %0 Photo100003.bmp

Copy %0 Photo0_Suffix.bmp
Copy %0 Photo1_Suffix.bmp
Copy %0 Photo2_Suffix.bmp
Copy %0 Photo3_Suffix.bmp
Copy %0 Photo10_Suffix.bmp
Copy %0 Photo11_Suffix.bmp
Copy %0 Photo12_Suffix.bmp
Copy %0 Photo13_Suffix.bmp
Copy %0 Photo100_Suffix.bmp
Copy %0 Photo101_Suffix.bmp
Copy %0 Photo102_Suffix.bmp
Copy %0 Photo103_Suffix.bmp
Copy %0 Photo1000_Suffix.bmp
Copy %0 Photo1001_Suffix.bmp
Copy %0 Photo1002_Suffix.bmp
Copy %0 Photo1003_Suffix.bmp
Copy %0 Photo10000_Suffix.bmp
Copy %0 Photo10001_Suffix.bmp
Copy %0 Photo10002_Suffix.bmp
Copy %0 Photo10003_Suffix.bmp
Copy %0 Photo100000_Suffix.bmp
Copy %0 Photo100001_Suffix.bmp
Copy %0 Photo100002_Suffix.bmp
Copy %0 Photo100003_Suffix.bmp
Then

Code: Select all

dir *.bmp /o:n /b
Produces this:
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo1_Suffix.bmp
Photo10.bmp
Photo10_Suffix.bmp
Photo100.bmp
Photo100_Suffix.bmp
Photo1000.bmp
Photo1000_Suffix.bmp
Photo10000.bmp
Photo10000_Suffix.bmp
Photo100000.bmp
Photo100000_Suffix.bmp
Photo100001.bmp
Photo100001_Suffix.bmp
Photo100002.bmp
Photo100002_Suffix.bmp
Photo100003.bmp
Photo100003_Suffix.bmp
Photo10001.bmp
Photo10001_Suffix.bmp
Photo10002.bmp
Photo10002_Suffix.bmp
Photo10003.bmp
Photo10003_Suffix.bmp
Photo1001.bmp
Photo1001_Suffix.bmp
Photo1002.bmp
Photo1002_Suffix.bmp
Photo1003.bmp
Photo1003_Suffix.bmp
Photo101.bmp
Photo101_Suffix.bmp
Photo102.bmp
Photo102_Suffix.bmp
Photo103.bmp
Photo103_Suffix.bmp
Photo11.bmp
Photo11_Suffix.bmp
Photo12.bmp
Photo12_Suffix.bmp
Photo13.bmp
Photo13_Suffix.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp
Then I execute another batch file testing ? wildcard:

Code: Select all

@Echo off

Echo.
Echo 1-digit files
Echo =============
dir Photo?.bmp /O:N /B
Echo.
dir Photo?_Suffix.bmp /O:N /B

Echo.
Echo 2-digit files
Echo =============
dir Photo??.bmp /O:N /B
Echo.
dir Photo??_Suffix.bmp /O:N /B

Echo.
Echo 3-digit files
Echo =============
dir Photo???.bmp /O:N /B
Echo.
dir Photo???_Suffix.bmp /O:N /B

Echo.
Echo 4-digit files
Echo =============
dir Photo????.bmp /O:N /B
Echo.
dir Photo????_Suffix.bmp /O:N /B

Echo.
Echo 5-digit files
Echo =============
dir Photo?????.bmp /O:N /B
Echo.
dir Photo?????_Suffix.bmp /O:N /B

Echo.
Echo 6-digit files
Echo =============
dir Photo??????.bmp /O:N /B
Echo.
dir Photo??????_Suffix.bmp /O:N /B

Echo.
Echo 7-digit files
Echo =============
dir Photo???????.bmp /O:N /B
Echo.
dir Photo???????_Suffix.bmp /O:N /B
This is what I see:
1-digit files
=============
Photo0.bmp
Photo1.bmp
Photo2.bmp
Photo3.bmp

Photo0_Suffix.bmp
Photo1_Suffix.bmp
Photo2_Suffix.bmp
Photo3_Suffix.bmp

2-digit files
=============
Photo0.bmp
Photo1.bmp
Photo10.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo3.bmp

Photo10_Suffix.bmp
Photo11_Suffix.bmp
Photo12_Suffix.bmp
Photo13_Suffix.bmp

3-digit files
=============
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo1000.bmp
Photo1001.bmp
Photo1002.bmp
Photo1003.bmp
Photo101.bmp
Photo102.bmp
Photo103.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp

Photo100_Suffix.bmp
Photo101_Suffix.bmp
Photo102_Suffix.bmp
Photo103_Suffix.bmp

4-digit files
=============
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo1000.bmp
Photo1001.bmp
Photo1002.bmp
Photo1003.bmp
Photo101.bmp
Photo102.bmp
Photo103.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp

Photo1000_Suffix.bmp
Photo1001_Suffix.bmp
Photo1002_Suffix.bmp
Photo1003_Suffix.bmp

5-digit files
=============
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo1000.bmp
Photo10000.bmp
Photo10001.bmp
Photo10002.bmp
Photo10003.bmp
Photo1001.bmp
Photo1002.bmp
Photo1003.bmp
Photo101.bmp
Photo102.bmp
Photo103.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp

Photo10000_Suffix.bmp
Photo10001_Suffix.bmp
Photo10002_Suffix.bmp
Photo10003_Suffix.bmp

6-digit files
=============
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo1000.bmp
Photo10000.bmp
Photo100000.bmp
Photo100001.bmp
Photo100002.bmp
Photo100003.bmp
Photo10001.bmp
Photo10002.bmp
Photo10003.bmp
Photo1001.bmp
Photo1002.bmp
Photo1003.bmp
Photo101.bmp
Photo102.bmp
Photo103.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp

Photo100000_Suffix.bmp
Photo100001_Suffix.bmp
Photo100002_Suffix.bmp
Photo100003_Suffix.bmp

7-digit files
=============
Photo0.bmp
Photo0_Suffix.bmp
Photo1.bmp
Photo10.bmp
Photo100.bmp
Photo1000.bmp
Photo10000.bmp
Photo100000.bmp
Photo100001.bmp
Photo100002.bmp
Photo100003.bmp
Photo10001.bmp
Photo10002.bmp
Photo10003.bmp
Photo1001.bmp
Photo1002.bmp
Photo1003.bmp
Photo101.bmp
Photo102.bmp
Photo103.bmp
Photo11.bmp
Photo12.bmp
Photo13.bmp
Photo2.bmp
Photo2_Suffix.bmp
Photo3.bmp
Photo3_Suffix.bmp

File Not Found
It makes me really mad to see "Photo1000.bmp" among "3-digit files".
I can not understand how

Code: Select all

dir Photo???.bmp /O:N /B
could match "Photo1000.bmp" file.

Anyway, as one can see ? works OK if it is not at the end of file name.
Only now I fully understand full meaning of this phrase (https://ss64.com/nt/syntax-wildcards.html):

The ? wildcard will match a single character (or a NULL at the end of a filename)

The case is closed. Thanks everyone for their attention and assistance.

Perhaps it might be a good idea to expand description of the ? wildcard a little bit with few more examples like the ones above.

Last edited by anandr (15 Jan 2010 18:56)
Post Reply