Reputation: 45
How can I sort differently in Windows command line? I had a look on a few sites including this one http://ss64.com/nt/dir.html, but I don't see a way to sort the way I want it to. Currently I am using:
dir *.txt /s /o:d /b >> "sorted.txt"
Currently it is sorting like this:
file1.txt
file10.txt
file11.txt
...
file2.txt
file20.txt
file21.txt
I would like it to sort 'normally' like this:
file1.txt
file2.txt
file3.txt
file4.txt
file5.txt
file6.txt
Also I am using /s and was wondering if there was a way to not return the full path, but only the file name as shown above. Thanks!
Upvotes: 2
Views: 1499
Reputation: 11
Windows CMD sorts like 1>10>2>20>3>4>50>6
what you are looking for is called "natural sort",
I'm using a very basic but well designed algorithm,
written in JavaScript,
since it supports UTF-8 natively and together with NodeJS,
it can very easily read, modify and write files.
Here is a basic algorithm for natural sort:
function natural_compare(a, b){
var ax=[], bx=[];
a.replace(/(\d+)|(\D+)/g, function(_, $1, $2){ ax.push([$1 || Infinity, $2 || ""]); });
b.replace(/(\d+)|(\D+)/g, function(_, $1, $2){ bx.push([$1 || Infinity, $2 || ""]); });
while(ax.length > 0 && bx.length > 0){
var an, bn, nn;
an = ax.shift();
bn = bx.shift();
nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}
and you apply it to an array's sort method,
as an alternative to the default sorting behavior.
"use strict";
function natural_compare(a, b){
var ax=[], bx=[];
a.replace(/(\d+)|(\D+)/g, function(_, $1, $2){ ax.push([$1 || Infinity, $2 || ""]); });
b.replace(/(\d+)|(\D+)/g, function(_, $1, $2){ bx.push([$1 || Infinity, $2 || ""]); });
while(ax.length > 0 && bx.length > 0){
var an, bn, nn;
an = ax.shift();
bn = bx.shift();
nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}
const FS = require("fs")
,PATH = require("path")
,resolve = function(path){
path = path.replace(/\"/g,"");
path = path.replace(/\\+/g,"/");
path = PATH.resolve(path);
path = path.replace(/\\+/g,"/");
path = path.replace(/\/\/+/g,"/");
return path;
}
,ARGS = process.argv.filter(function(s){return false === /node\.exe/i.test(s) && false === /index\.js/i.test(s);}).map(function(s){return s.replace(/\"/gm,"");})
,FILE_IN = resolve(ARGS[0])
,FILE_IN_PARTS = PATH.parse(FILE_IN)
,FILE_OUT = resolve(
FILE_IN_PARTS.dir
+ "/"
+ FILE_IN_PARTS.name
+ "_sorted_uniqued"
+ FILE_IN_PARTS.ext
)
;
var tmp = new Object(null)
,content = FS.readFileSync(FILE_IN, {encoding:"utf8"}) //raw text-content
.replace(/[\r\n]+/gm, "\n").replace(/\n+/gm, "\n") //unify newline character, single newline the most.
.split("\n").filter(function(s){return s.length > 1;}) //no empty lines. whitespace is considered a perfectly valid content. if you do wish to remove all-whitespace lines and empty lines pre-include this line before this one: .replace(/^\s*/g, "").replace(/(\s*$|^\s*)/gm, "")
;
//-------------------------------------------------unique
content.forEach(function(s){
tmp[1] = 1;
});
content = Object.keys(tmp);
tmp = undefined;
//---------------------------------------------------------
content = content.sort(natural_compare)
.join("\r\n")
;
FS.writeFileSync(FILE_OUT, content, {flag:"w", encoding:"utf8"}); //overwrite
@echo off
chcp 65001 1>nul 2>nul
call "%~sdp0node.exe" "%~sdp0index.js" %*
exit /b %ErrorLevel%
result-file's lines will be joined using Windows EOL (CR+LF).
and it works perfectly for me for few years now,
skipping all the other batch file specific quirks (..CMD handling raw Unicode.. Brrrr... 😱)
it is also an operation-system-independent solution,
since you don't really need the cmd file to run the NodeJS script,
just specifying the file-path as an argument to NodeJS,
and download your operation-system version of NodeJS
(and changing the EOL character if you so wish).
you can look for other solutions in:
https://github.com/search?q=natural+sort+batch+file&type=Repositories
but there are not a lot of client ready solutions.
Upvotes: 1
Reputation: 49097
This batch code could be helpful to get the file names numeric sorted into the file names list file. Please read the comment lines carefully as there are some restrictions. The comment lines are the lines starting with command rem
.
@echo off
setlocal EnableExtensions
rem Delete the list file with the sorted file names in current directory
rem if this file exists already from a previous run of this batch file.
set "SortedFileNamesListFile=sorted.txt"
if exist "%SortedFileNamesListFile%" del "%SortedFileNamesListFile%"
rem Get recursive from current directory all *.txt file names with full path
rem not enclosed in double quotes sorted alphabetically and not numeric as
rem wanted and pass them to subroutine AddToFileList enclosed in double quotes.
for /F "delims=" %%I in ('dir *.txt /A-D /B /ON /S 2^>nul') do call :AddToFileList "%%I"
rem The file names are now in an environment variables list. Output this
rem file names list and redirect the file name with path to the list file.
rem The split in environment variable and file name with path works only
rem if the file name does not contain itself an equal sign.
for /F "tokens=1* delims==" %%I in ('set FileList[ 2^>nul') do echo %%J>>"%SortedFileNamesListFile%"
rem Delete all local environment variables and restore previous
rem environment with the initial list of environment variables.
endlocal
rem Exit batch processing to avoid an unwanted fall through to the
rem subroutine AddToFileList.
exit /B
rem The subroutine AddToFileList is for adding each file found into an
rem environment variables array based on the file name. As the variable
rem names contain just file name without path and file number at end of
rem the file name if there is one at all, the array can't hold multiple
rem files with same file name in different directories. In other words
rem each file in complete directory tree must be unique in entire tree.
rem This restriction must be taken into account or the batch code is
rem enhanced to work directory by directory to avoid unwanted removal
rem of files with same file name in different directories.
rem And the array works only for files with up to 5 digits in file number,
rem i.e. for file numbers in range 0 to 99999. That should be enough.
:AddToFileList
rem Get just the file name without path and without file extension.
set "FileName=%~n1"
rem In case of name of file has only 1 point and no characters left
rem this point, the file name is the point and the file extension.
rem Such file names are not common on Windows, but exist often on *nix.
if "%FileName%" == "" set "FileName=%~x1"
set "FileNumber="
:GetFileNumber
for /F "delims=0123456789" %%I in ("%FileName:~-1%") do goto AddFileToArray
set "FileNumber=%FileName:~-1%%FileNumber%"
set "FileName=%FileName:~0,-1%"
if not "%FileName%" == "" goto GetFileNumber
:AddFileToArray
set "FileNumber=00000%FileNumber%"
set "FileNumber=%FileNumber:~-5%"
set "FileList[%FileName%_%FileNumber%]=%~1"
rem Exit the subroutine and continue in FOR loop in main batch code block.
goto :EOF
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
del /?
dir /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
rem /?
set /?
setlocal /?
And see also the Microsoft article about Using command redirection operators for an explanation of 2^>nul
which is 2>nul
with redirection operator >
escaped with ^
to get the redirection applied on execution of command DIR and SET. This redirection suppresses the error messages output by DIR and SET if there is no file matching the file name pattern *.txt in current directory tree.
Upvotes: 0