Powershell排序txt文件中的IP地址

kpbpu008  于 2023-06-29  发布在  Shell
关注(0)|答案(8)|浏览(162)

我有一个纯文本文件,其中包含一些像这样的IP:

194.225.0.0 - 194.225.15.255
194.225.24.0 - 194.225.31.255
62.193.0.0 - 62.193.31.255
195.146.53.128 - 195.146.53.225
217.218.0.0 - 217.219.255.255
195.146.40.0 - 195.146.40.255
85.185.240.128 - 85.185.240.159
78.39.194.0 - 78.39.194.255
78.39.193.192 - 78.39.193.207

我想按IP地址对文件进行排序。我的意思是只有第一部分是重要的。
我在谷歌上找到了一些程序,但我想知道是否有可能通过PowerShell没有其他应用程序。
我有一个像这样的Linux方式,但无法在Windows中达到它:

sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 file

更新1

@TheMadTechnician,这是我运行你的命令时的输出:

85.185.240.128 - 85.185.240.159
195.146.40.0 - 195.146.40.255
78.39.193.192 - 78.39.193.207
78.39.194.0 - 78.39.194.255
217.218.0.0 - 217.219.255.255
194.225.24.0 - 194.225.31.255
194.225.0.0 - 194.225.15.255
195.146.53.128 - 195.146.53.225
62.193.0.0 - 62.193.31.255
7fyelxc5

7fyelxc51#

使用RegEx-replace的简单解决方案:为了使IP地址可排序,我们只需要在左侧填充每个八位字节,以便它们都具有相同的宽度。然后进行一个简单的字符串比较,即可得到正确的结果。

对于PS 6+:

Get-Content IpList.txt | Sort-Object {
    $_ -replace '\d+', { $_.Value.PadLeft(3, '0') }
}

对于PS 5.x:

Get-Content IpList.txt | Sort-Object {
    [regex]::Replace( $_, '\d+', { $args.Value.PadLeft(3, '0') } )
}
  • -replace操作符尝试在给定字符串中查找 * 正则表达式 * 模式的匹配项,并将其替换为给定的值。
  • 对于PS 5.x,我们需要不同的语法,因为-replace不支持 scriptblock。使用.NET Regex.Replace方法,我们可以实现相同的功能。
  • 第一个$_表示文本文件的当前行。
  • \d+是匹配每个IP地址的每个八位字节的模式。有关详细说明,请参见regex101中的示例。
  • {}定义了一个输出替换值的 scriptblock
  • 这里$_表示当前匹配(八位字节)。我们取它的值,并在左边填充零,所以每个八位字节总共是3个字符(e。例如,2变为00292变为092)。最终的IP可能看起来像194.225.024.000

使用Tuple类的另一种解决方案。它稍长,但更干净,因为它实际上比较数字而不是字符串。

Get-Content IpList.txt | Sort-Object {
    # Extract the first 4 numbers from the current line
    [int[]] $octets = [regex]::Matches( $_, '\d+' )[ 0..3 ].Value
    
    # Create and output a tuple that consists of the numbers
    [Tuple]::Create( $octets[0], $octets[1], $octets[2], $octets[3] )  
}
  • 使用[regex]::Matches(),我们找到当前行的所有数字。从返回的MatchCollection中,我们获取前四个元素。然后我们使用member access enumeration创建一个字符串数组,其中包含每个MatchCollection元素的Value成员。
  • 通过简单地将字符串数组分配给具有[int[]]类型约束的变量(int s的数组),PowerShell会自动将字符串解析为整数。
  • 排序之所以有效,是因为Tuple实现了IComparable接口,Sort-Object在可用时使用该接口。元组按lexicographically排序,正如预期的那样。
  • 使用dynamic method invocation,我们可以像这样缩短[Tuple]::Create调用(最多适用于8个元素1):
[Tuple]::Create.Invoke( [object[]] $octets )

请注意到[object[]]的转换,否则[Tuple]::Create将只使用一个参数调用,即$octets数组。
[1]实际上,大于8个元素的元组可以通过创建嵌套元组来创建(为剩余的元素创建一个元组,并将其存储在基本元组的最后一个元素中)。一般来说,要做到这一点,需要递归或反向循环,首先创建最嵌套的元组。

yeotifhr

yeotifhr2#

这个答案最初是作为我的评论发表在不同的answer
您可以将IP地址从string对象转换为version对象,该对象“巧合地”与IP地址具有相同的格式(由.分隔的4组数字)

Get-Content .\abc.txt | Sort-Object { [System.Version]($_).split("-")[1] }
mtb9vblg

mtb9vblg3#

只要第一个二进制八位数中的范围起始地址不同,TheMadTechnician's answer就可以工作。为了让它按多个八位字节排序,Sort-Object看起来不会按单个[ScriptBlock]返回的数组中的连续值排序;为此,您需要为每个八位字节传递[ScriptBlock]Santiago Squarzon's answer展示了如何做到这一点,而无需重复定义四个几乎相同的[ScriptBlock]
相反,一个[ScriptBlock]可以将每个八位字节组合成一个[UInt32]来进行排序。

使用[Math]::Pow()生成可排序值

Get-Content -Path 'IPv4AddressRanges.txt' |
    Sort-Object -Property {
        # Split each line on a hyphen surrounded by optional whitespace
        $rangeStartAddress = ($_ -split '\s*-\s*')[0]
        # Split the start address on a period and parse the resulting [String]s to [Byte]s
        [Byte[]] $octets = $rangeStartAddress -split '.', 0, 'SimpleMatch'

        #TODO: Handle $octets.Length -ne 4
        # Alternative: [Byte[]] $octets = [IPAddress]::Parse($rangeStartAddress).GetAddressBytes()

        [UInt32] $sortValue = 0
        # $sortValue = (256 ^ 3) * $octets[0] + (256 ^ 2) * $octets[1] + 256 * $octets[2] + $octets[3]
        for ($i = 0; $i -lt $octets.Length; $i++)
        {
            $octetScale = [Math]::Pow(256, $octets.Length - $i - 1)
            $sortValue += $octetScale * $octets[$i]
        }

        return $sortValue
    }

。输出。。

62.193.0.0 - 62.193.31.255
78.39.193.192 - 78.39.193.207
78.39.194.0 - 78.39.194.255
85.185.240.128 - 85.185.240.159
194.225.0.0 - 194.225.15.255
194.225.24.0 - 194.225.31.255
195.146.40.0 - 195.146.40.255
195.146.53.128 - 195.146.53.225
217.218.0.0 - 217.219.255.255

为了更好的衡量,你可以把第一行改为...

@('255.255.255.255', '0.0.0.0') + (Get-Content -Path 'IPv4AddressRanges.txt') |

...并查看它是否正确排序,而不会使排序值溢出。

使用[BitConverter]生成可排序值

您可以通过使用[BitConverter] class将IP地址字节直接转换为[UInt32]...

Get-Content -Path 'IPv4AddressRanges.txt' |
    Sort-Object -Property {
        # Split each line on a hyphen surrounded by optional whitespace
        $rangeStartAddress = ($_ -split '\s*-\s*')[0]
        # Split the start address on a period and parse the resulting [String]s to [Byte]s
        [Byte[]] $octets = $rangeStartAddress -split '.', 0, 'SimpleMatch'

        #TODO: Handle $octets.Length -ne 4
        # Alternative: [Byte[]] $octets = [IPAddress]::Parse($rangeStartAddress).GetAddressBytes()

        # [IPAddress]::NetworkToHostOrder() doesn't have an overload for [UInt32]
        if ([BitConverter]::IsLittleEndian)
        {
            [Array]::Reverse($octets)
        }

        return [BitConverter]::ToUInt32($octets, 0)
    }

在PowerShell类中实现[IComparable],定义自己的排序

一个更复杂的解决方案是将我们的地址存储在一个实现[IComparable] interface的类型中,这样Sort-Object就可以直接对地址进行排序,而不需要指定[ScriptBlock]。当然,[IPAddress]是存储IP地址最自然的.NET类型,但它没有实现任何排序接口。相反,我们可以使用PowerShell classes来实现我们自己的可排序类型。

# Implement System.IComparable[Object] instead of System.IComparable[IPAddressRange]
# because PowerShell does not allow self-referential base type specifications.
# Sort-Object seems to only use the non-generic interface, anyways.
class IPAddressRange : Object, System.IComparable, System.IComparable[Object]
{
    [IPAddress] $StartAddress
    [IPAddress] $EndAddress

    IPAddressRange([IPAddress] $startAddress, [IPAddress] $endAddress)
    {
        #TODO: Ensure $startAddress and $endAddress are non-$null
        #TODO: Ensure the AddressFamily property of both $startAddress and
        #      $endAddress is [System.Net.Sockets.AddressFamily]::InterNetwork
        #TODO: Handle $startAddress -gt $endAddress

        $this.StartAddress = $startAddress
        $this.EndAddress = $endAddress
    }

    [Int32] CompareTo([Object] $other)
    {
        if ($null -eq $other)
        {
            return 1
        }

        if ($other -isnot [IPAddressRange])
        {
            throw [System.ArgumentOutOfRangeException]::new(
                'other',
                "Comparison against type ""$($other.GetType().FullName)"" is not supported."
            )
        }
        
        $result = [IPAddressRange]::CompareAddresses($this.StartAddress, $other.StartAddress)
        if ($result -eq 0)
        {
            $result = [IPAddressRange]::CompareAddresses($this.EndAddress, $other.EndAddress)
        }

        return $result
    }

    hidden static [Int32] CompareAddresses([IPAddress] $x, [IPAddress] $y)
    {
        $xBytes = $x.GetAddressBytes()
        $yBytes = $y.GetAddressBytes()

        for ($i = 0; $i -lt 4; $i++)
        {
            $result = $xBytes[$i].CompareTo($yBytes[$i])
            if ($result -ne 0)
            {
                return $result
            }
        }

        return 0
    }
}

[IPAddressRange]类型存储范围的开始和结束地址,因此它可以表示输入文件的整行。CompareTo method逐字节比较每个StartAddress,只有当它们相等时,它才逐字节比较每个EndAddress。执行这个…

(
    '127.0.0.101 - 127.0.0.199',
    '127.0.0.200 - 127.0.0.200',
    '127.0.0.100 - 127.0.0.200',
    '127.0.0.100 - 127.0.0.101',
    '127.0.0.199 - 127.0.0.200',
    '127.0.0.100 - 127.0.0.199',
    '127.0.0.100 - 127.0.0.100',
    '127.0.0.101 - 127.0.0.200'
) + (Get-Content -Path 'IPv4AddressRanges.txt') |
    ForEach-Object -Process {
        $startAddress, $endAddress = [IPAddress[]] ($_ -split '\s*-\s*')

        return [IPAddressRange]::new($startAddress, $endAddress)
    } |
    Sort-Object

...按预期顺序对127.0.0.*范围进行排序...

StartAddress   EndAddress     
------------   ----------
62.193.0.0     62.193.31.255
78.39.193.192  78.39.193.207
78.39.194.0    78.39.194.255
85.185.240.128 85.185.240.159
127.0.0.100    127.0.0.100    
127.0.0.100    127.0.0.101
127.0.0.100    127.0.0.199
127.0.0.100    127.0.0.200
127.0.0.101    127.0.0.199
127.0.0.101    127.0.0.200
127.0.0.199    127.0.0.200
127.0.0.200    127.0.0.200
194.225.0.0    194.225.15.255
194.225.24.0   194.225.31.255
195.146.40.0   195.146.40.255
195.146.53.128 195.146.53.225
217.218.0.0    217.219.255.255

请注意,我们只为Sort-Object添加了对[IPAddressRange]示例进行排序的功能,而没有添加其单个属性。这些仍然是[IPAddress]类型,它不提供自己的顺序,所以如果我们尝试类似... | Sort-Object -Property 'EndAddress'的东西,它不会产生预期的结果。

fdbelqdn

fdbelqdn4#

一种简单的方法是在.上分割每一行,取第一部分(范围内每个IP的第一个八位字节),然后将其转换为整数并进行排序。

Get-Content .\MyFile.txt | Sort-Object {$_.Split('.')[0] -as [int]}
6ojccjat

6ojccjat5#

scottwang提供了一种聪明的方式来排序评论中的IP,使用实现IComparable InterfaceVersion Class
下面是另一种选择,显然效率较低,使用hash tableIPAddress Class和表达式数组:

$ips = Get-Content ipfile.txt

$iptable = @{}
foreach($line in $ips) {
    if($ip = $line -replace ' -.+' -as [ipaddress]) {
        $iptable[$line] = $ip.GetAddressBytes()
    }
}

$expressions = foreach($i in 0..3) {
    { $iptable[$_] | Select-Object -Index $i }.GetNewClosure()
}

$ips | Sort-Object $expressions -Descending

使用advanced functionanonymous function可以在单个管道中执行相同操作:

Get-Content ipfile.txt | & {
    begin {
        $iptable = @{}
        $expressions = foreach($i in 0..3) {
            { $iptable[$_] | Select-Object -Index $i }.GetNewClosure()
        }
    }
    process {
        if ($ip = $_ -replace ' -.+' -as [ipaddress]) {
            $iptable[$_] = $ip.GetAddressBytes()
        }
    }
    end { $iptable.PSBase.Keys | Sort-Object $expressions -Descending }
}
ttp71kqs

ttp71kqs6#

我的prior answer效率很低,因此我决定提供另一种替代方案,使用Class实现IComparable Interface并持有IpAddress的示例:

class IpComparer : IComparable, IEquatable[object] {
    [ipaddress] $IpAddress

    IpComparer([ipaddress] $IpAddress) {
        $this.IpAddress = $IpAddress
    }

    [string] ToString() {
        return $this.IpAddress.ToString()
    }

    [int] GetHashCode() {
        return $this.IpAddress.GetHashCode()
    }

    [bool] Equals([object] $IpAddress) {
        return [IpComparer]::Equals($this, [IpComparer] $IpAddress)
    }

    hidden static [bool] Equals([IpComparer] $LHS, [IpComparer] $RHS) {
        return $LHS.IpAddress.Equals($RHS.IpAddress)
    }

    [int] CompareTo([object] $IpAddress) {
        return [IpComparer]::CompareTo($this, [IpComparer] $IpAddress)
    }

    hidden static [int] CompareTo([IpComparer] $LHS, [IpComparer] $RHS) {
        $x = $LHS.IpAddress.GetAddressBytes()
        $y = $RHS.IpAddress.GetAddressBytes()

        for($i = 0; $i -lt 4; $i++) {
            if($ne = $x[$i].CompareTo($y[$i])) {
                return $ne
            }
        }
        return 0
    }

    hidden static [IpComparer] op_Explicit([string] $IpAddress) {
        return [IpComparer]::new([ipaddress] $IpAddress)
    }
}

现在示例可以Comparable

[IpComparer] '194.225.0.0' -lt '194.225.15.255'  # => True
[IpComparer] '194.225.15.255' -lt '194.225.0.0'  # => False
[IpComparer] '194.225.0.0' -gt '194.225.15.255'  # => False
[IpComparer] '194.225.15.255' -gt '194.225.0.0'  # => True

测试平等

[IpComparer] '194.225.15.25' -ge '194.225.15.25' # => True
'194.225.15.25' -le [IpComparer] '194.225.15.25' # => True

$hs = [Collections.Generic.HashSet[IpComparer]]::new()
$hs.Add('194.225.0.0') # => True
$hs.Add('194.225.0.0') # => False

([IpComparer[]]('194.225.0.0', '194.225.0.0') | Select-Object -Unique).Count # => 1
([IpComparer] '194.225.15.255').Equals('194.225.15.255') # => True

因此,可排序

Get-Content ipfile.txt | Sort-Object { $_ -replace ' -.+' -as [IpComparer] } -Descending
6mw9ycah

6mw9ycah7#

可能是这样的。

Get-Content .\abc.txt |ForEach-Object {($_).split("-")[1]}|Sort-Object
sz81bmfz

sz81bmfz8#

我将使用一个简单的ip地址列表作为示例。[ipaddress]类型的address long integer属性通常是反向字节主机顺序(little endian),因此必须通过HostToNetworkOrder()将其更改为从左到右的网络字节顺序(big endian)。

$list =
-split '2.3.1.1
1.3.1.1
1.2.1.1'

$list | sort { [ipaddress]::HostToNetworkOrder(([ipaddress]$_).address) }

1.2.1.1
1.3.1.1
2.3.1.1

注意右侧零填充。'255.255.255.255'不适合[int]。

[ipaddress]::HostToNetworkOrder(([ipaddress]'1.2.3.4').address) | % tostring x

102030400000000

相关问题