Sort logically like in Windows Explorer (Powershell hybrid)

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Sort logically like in Windows Explorer (Powershell hybrid)

#1 Post by aGerman » 11 Aug 2019 06:11

If an Explorer list view is sorted by name then digits are taken as numeric values rather than text. Furthermore the sorting ignores the case of characters.
The API function used to compare the file names is StrCmpLogicalW which is not accessible in Batch. However, we can use Batch to call Powershell that wraps a C# class that imports Windows API functions, and use it.

References:
https://docs.microsoft.com/en-us/window ... mplogicalw
https://docs.microsoft.com/en-us/dotnet ... comparer-1

Fully commented macro code:

Code: Select all

:: %logicalsort_asc% and %logicalsort_desc% macros
:: sort a list of lines in an Explorer-like manner
:: strings are read from standard input and written to standard output
set logicalsort_asc=powershell -nop -ep Bypass -c ^"try{ %======================== invoke Powershell with the code specified after -c =%^
%=% Add-Type ' %================================================================== add a .NET class to the Powershell session =%^
%=====% using System.Runtime.InteropServices; %=================================== namespace used for platform invokation =%^
%=====% public class SCmpLgcl : System.Collections.Generic.IComparer^<string^>{ %= custom comparer class for a List of string =%^
%=========% [DllImport(\"shlwapi.dll\",CharSet=CharSet.Unicode)] %================ import StrCmpLogicalW which is the comparison function used =%^
%=========%  static extern int StrCmpLogicalW(string a,string b); %===============   to sort Windows Explorer list views =%^
%=========% public SCmpLgcl(){} %================================================= public constructor of the comparer class =%^
%=========% public int Compare(string a,string b){return StrCmpLogicalW(a,b);} %== public Compare method of the comparer that calls StrCmpLogicalW =%^
%=====% }'; %===================================================================== end of the .NET class =%^
%=% $list=[System.Collections.Generic.List[string]]@($Input); %=================== cast the standard input to a List of string =%^
%=% $list.Sort([SCmpLgcl]::new()); %============================================== invoke the Sort method of the List using an instance of the comparer class =%^
%=% $list; %====================================================================== write the sorted List to the standard output =%^
%=% exit 0;}catch{exit 1;}^" %==================================================== return a suitable errorlevel =%
(set logicalsort_desc=%logicalsort_asc:(a,b)=(b,a)%) %============================ reversed parameters passed to StrCmpLogicalW lead to descending sorting =%
Example (comments at the end of lines removed for readability reasons):

Code: Select all

@echo off &setlocal

:: %logicalsort_asc% and %logicalsort_desc% macros
:: sort a list of lines in an Explorer-like manner
:: strings are read from standard input and written to standard output
set logicalsort_asc=powershell -nop -ep Bypass -c ^"try{^
%=% Add-Type '^
%=====% using System.Runtime.InteropServices;^
%=====% public class SCmpLgcl : System.Collections.Generic.IComparer^<string^>{^
%=========% [DllImport(\"shlwapi.dll\",CharSet=CharSet.Unicode)]^
%=========%  static extern int StrCmpLogicalW(string a,string b);^
%=========% public SCmpLgcl(){}^
%=========% public int Compare(string a,string b){return StrCmpLogicalW(a,b);}^
%=====% }';^
%=% $list=[System.Collections.Generic.List[string]]@($Input);^
%=% $list.Sort([SCmpLgcl]::new());^
%=% $list;^
%=% exit 0;}catch{exit 1;}^"
(set logicalsort_desc=%logicalsort_asc:(a,b)=(b,a)%)


:: write an example file
>"_to_sort.txt" (
  echo a3b4
  echo a11b15
  echo a5b
  echo a100b
  echo a3b18
  echo x1b
  echo a11b6
  echo a3b
  echo a2b
  echo a9b
)


echo   just print to screen (ascending sorted)
<"_to_sort.txt" %logicalsort_asc%
echo(

echo   or process the output in a FOR /F loop (descending sorted)
for /f "delims=" %%i in ('^<"_to_sort.txt" %logicalsort_desc%') do echo %%i
echo(

echo   or redirect to a file (ascending once again)
<"_to_sort.txt" >"_sorted.txt" %logicalsort_asc%
type "_sorted.txt"
echo(


:: clean up
del "_to_sort.txt" "_sorted.txt"

pause

Output:

Code: Select all

  just print to screen (ascending sorted)
a2b
a3b
a3b4
a3b18
a5b
a9b
a11b6
a11b15
a100b
x1b

  or process the output in a FOR /F loop (descending sorted)
x1b
a100b
a11b15
a11b6
a9b
a5b
a3b18
a3b4
a3b
a2b

  or redirect to a file (ascending once again)
a2b
a3b
a3b4
a3b18
a5b
a9b
a11b6
a11b15
a100b
x1b

Drücken Sie eine beliebige Taste . . .
Steffen

Eureka!
Posts: 136
Joined: 25 Jul 2019 18:25

Re: Sort logically like in Windows Explorer (Powershell hybrid)

#2 Post by Eureka! » 11 Aug 2019 08:53

Thank you for this!

Post Reply