Why does `dir *.*` give me all files and folders?
When I run dir *.*
it produces unexpected results. Even files and folders without any dot in their names are listed. For example
C:\>dir *.*
Volume in drive C is System
Volume Serial Number is AC0A-29DA
Directory of C:\
14-03-2017 05:17 PM 8,192 ntuser.dat
03-01-2017 07:10 PM <DIR> Perl520
28-03-2017 10:13 AM <DIR> Program Files
28-03-2017 10:13 AM <DIR> Program Files (x86)
04-01-2017 03:25 PM <JUNCTION> Python27 [C:\Program Files\Anaconda]
06-02-2017 10:16 PM <DIR> SAP
28-03-2017 04:10 PM 152 useragent.log
03-01-2017 03:04 PM <DIR> Users
16-03-2017 04:24 PM <DIR> VM
22-03-2017 11:13 AM <DIR> Windows
2 File(s) 8,344 bytes
8 Dir(s) 270,172,966,912 bytes free
Why is that? Is there any way to list only files with a dot?
I am writing this answer because OP has emphasized that:
what I'm interested is why
*.*
matches all files, as stated in the question
The DIR
command comes from a time when:
- Period (.) was not allowed as a character in file or folder names
- File and folder names were restricted to 8 characters for name and 3 characters for extensions
Therefore, by that standard, *.*
meant whatever the name and whatever the extension. It did not mean a string containing a ".", which may or may not have characters before or after the ".".
Microsoft policy is preserving backward compatibility. Hence, that interpretation of *.*
is retained.
But in Windows PowerShell, *.*
means a string containing a ".", which may or may not have characters before or after the ".".
Why is that?
One could find an answer to "Why is that?" in Wildcards article:
The * wildcard will match any sequence of characters (0 or more, including NULL characters) The ? wildcard will match a single character (or a NULL at the end of a filename)
…
Wildcard matching rules
*
Generally matches any 0 or more characters, with one exception (see next rule). The non-greedy wildcard is free to match as many or as few characters as are necessary for the remainder of the mask to match.
*.
At end of mask matches any 0 or more characters except for {dot}. In actuality, the rule applies with any number of {dot} and {space} characters between the * and terminal {dot}. The regular expression for this term is"[*][. ]*[.]$"
?
Match 0 or one character, except for {dot}. The only time it matches 0 characters is when it matches the end of the name, or the position before a {dot}. The question mark can also be used more than once to match more than one character.
Implication. The last {dot} in a file/folder name separates base name and extension. So
-
dir *.
displays all items with no extension, and -
dir *.*
displays all items with extension of zero or more characters.
Strictly speaking, dir *.
displays all items with no period (.
) in name. (BTW, Naming Files, Paths, and Namespaces MSDN article says explicitly that "it is acceptable to specify a period as the first character of a name".)
Is there any way to list only files with a dot?
I don't think so. However, there is a workaround with a fitting regular expression.
PowerShell (full scope solution if used in a Powershell console):
:: PowerShell - no extension, full syntax
PowerShell -c "Get-ChildItem | Where-Object {$_.Name -match '^.[^\.]*$'}"
:: PowerShell - extension, alias syntax
PowerShell -c "dir | ? {$_.Name -match '^..*\...*$'}"
Cmd (an idea only, might require some elaboration):
:: CMD/batch - no extension
for /F "delims=" %%G in ('dir /OGN /B ^| findstr "^.[^\.]*$"') do @echo %%~tG %%~aG %%~zG %%~nxG
:: CMD/batch - extension
for /F "delims=" %%G in ('dir /OGN /B ^| findstr "^..*\...*$"') do @echo %%~tG %%~aG %%~zG %%~nxG
Addendum: a bonus and explanation
An intuitive guess that Name
is concatenated BaseName
and Extension
does not hold. The following script proves it using cmd
and PowerShell
core features, and the weird ^..*\...*$
regex is derived from it's results.
@ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_workingDirectory=%~1"
if "%_workingDirectory%"=="%tmp%\tests_SU_1193102" (
>NUL 2>&1 (
mkdir "%_workingDirectory%"
pushd "%_workingDirectory%"
rem make directories
mkdir .Fldr-Ext
mkdir aFldr-Ext
mkdir .Fldr.Ext
mkdir aFldr.Ext
rem create files
copy NUL .File-Ext
copy NUL aFile-Ext
copy NUL .File.Ext
copy NUL aFile.Ext
popd
)
) else if "%_workingDirectory%"=="" set "_workingDirectory=%CD%"
pushd "%_workingDirectory%"
set "_first=ItemName Attributes BaseName Extension"
echo ON
:: dir /OGN | findstr "Ext$"
for /F "delims=" %%G in ('dir /OGN /B') do @((if defined _first (echo %_first%&echo(&set "_first="))&echo %%~nxG %%~aG %%~nG %%~xG)
:: Get-ChildItem | Select-Object -Property Mode, BaseName, Extension, Name
PowerShell -c "dir | select -pr Name, Mode, BaseName, Extension | sort -pr @{Expression='Mode';Descending=$true}, @{Expression='Name';Descending=$false}"
Output:
==> D:\bat\BaseName_vs_Extension.bat "%tmp%\tests_SU_1193102"
==> for /F "delims=" %G in ('dir /OGN /B') do @((if defined _first (echo ItemName Attributes BaseName Extension & echo( & set "_first=" ) ) & echo %~nxG %~aG %~nG %~xG )
ItemName Attributes BaseName Extension
.Fldr.Ext d---------- .Fldr .Ext
.Fldr-Ext d---------- .Fldr-Ext
aFldr.Ext d---------- aFldr .Ext
aFldr-Ext d---------- aFldr-Ext
.File.Ext --a-------- .File .Ext
.File-Ext --a-------- .File-Ext
aFile.Ext --a-------- aFile .Ext
aFile-Ext --a-------- aFile-Ext
==> PowerShell -c "dir | select -pr Name, Mode, BaseName, Extension | sort -pr @{Expression='Mode';Descending=$true}, @{Expression='Name';Descending=$false}"
Name Mode BaseName Extension
---- ---- -------- ---------
.Fldr.Ext d----- .Fldr.Ext .Ext
.Fldr-Ext d----- .Fldr-Ext .Fldr-Ext
aFldr.Ext d----- aFldr.Ext .Ext
aFldr-Ext d----- aFldr-Ext
.File.Ext -a---- .File .Ext
.File-Ext -a---- .File-Ext
aFile.Ext -a---- aFile .Ext
aFile-Ext -a---- aFile-Ext
Compare definition of BaseName
property, different for files and folders:
PS D:\PShell> Get-ChildItem | Get-Member -Name BaseName | Format-List -property TypeName, Definition
TypeName : System.IO.DirectoryInfo
Definition : System.Object BaseName {get=$this.Name;}
TypeName : System.IO.FileInfo
Definition : System.Object BaseName {get=if ($this.Extension.Length -gt
0){$this.Name.Remove($this.Name.Length -
$this.Extension.Length)}else{$this.Name};}
My original answer was based on unforgivable misunderstanding:
Read dir /?
, use dir /A:-D
:
/A Displays files with specified attributes. attributes D Directories R Read-only files H Hidden files A Files ready for archiving S System files I Not content indexed files L Reparse Points - Prefix meaning not
Another approach: apply findstr
regex as dir *.* | findstr /V "<.*>"