【问题标题】:How to print a certain line of a file with PowerShell?如何使用 PowerShell 打印文件的某一行?
【发布时间】:2013-01-23 10:56:58
【问题描述】:

我在此服务器上没有像样的文本编辑器,但我需要查看导致某个文件第 10 行错误的原因。不过我确实有 PowerShell...

【问题讨论】:

  • (get-content myfile.txt)[9] 呢?
  • 是的,问题是大文件可能真的很慢,因为在返回 [index] 之前读取了所有文件
  • 我在 Windows Powershell 中尝试过 (get-content myfile.txt)[9]

标签: powershell


【解决方案1】:

就像使用 select 一样简单:

Get-Content file.txt | Select -Index (line - 1)

例如得到第 5 行

Get-Content file.txt | Select -Index 4

或者你可以使用:

(Get-Content file.txt)[4]

【讨论】:

  • 这会不会先将整个内容重新存储到内存中,这很糟糕?
  • 查看 C.B. 对性能统计的回答(时间,而不是内存)。如果您正在处理大文件,那么这是低效的。如果您使用的文件很小且只有几个文件,那么性能就不是那么重要了。有时干净的代码更重要。这个答案已有 5 年历史了——powershell 中的情况也发生了变化
【解决方案2】:

这将显示 myfile.txt 的第 10 行:

get-content myfile.txt | select -first 1 -skip 9

-first-skip 都是可选参数,-context-last 在类似情况下可能有用。

【讨论】:

  • 这适用于小文件。除非有什么改变,Get-Content 会将整个文件读入内存。这并不总是适用于大文件。
【解决方案3】:

您可以使用Get-Content cmdlet-TotalCount 参数读取第一个n 行,然后使用Select-Object 仅返回第n 行:

Get-Content file.txt -TotalCount 9 | Select-Object -Last 1;

根据@C.B. 的评论。这应该通过仅读取并包括nth 行而不是整个文件来提高性能。请注意,您可以使用别名 -First-Head 代替 -TotalCount

【讨论】:

    【解决方案4】:

    这是一个直接使用 .NET 的 System.IO 类的函数:

    function GetLineAt([String] $path, [Int32] $index)
    {
        [System.IO.FileMode] $mode = [System.IO.FileMode]::Open;
        [System.IO.FileAccess] $access = [System.IO.FileAccess]::Read;
        [System.IO.FileShare] $share = [System.IO.FileShare]::Read;
        [Int32] $bufferSize = 16 * 1024;
        [System.IO.FileOptions] $options = [System.IO.FileOptions]::SequentialScan;
        [System.Text.Encoding] $defaultEncoding = [System.Text.Encoding]::UTF8;
        # FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions) constructor
        # http://msdn.microsoft.com/library/d0y914c5.aspx
        [System.IO.FileStream] $input = New-Object `
            -TypeName 'System.IO.FileStream' `
            -ArgumentList ($path, $mode, $access, $share, $bufferSize, $options);
        # StreamReader(Stream, Encoding, Boolean, Int32) constructor
        # http://msdn.microsoft.com/library/ms143458.aspx
        [System.IO.StreamReader] $reader = New-Object `
            -TypeName 'System.IO.StreamReader' `
            -ArgumentList ($input, $defaultEncoding, $true, $bufferSize);
        [String] $line = $null;
        [Int32] $currentIndex = 0;
    
        try
        {
            while (($line = $reader.ReadLine()) -ne $null)
            {
                if ($currentIndex++ -eq $index)
                {
                    return $line;
                }
            }
        }
        finally
        {
            # Close $reader and $input
            $reader.Close();
        }
    
        # There are less than ($index + 1) lines in the file
        return $null;
    }
    
    GetLineAt 'file.txt' 9;
    

    调整$bufferSize 变量可能会影响性能。使用默认缓冲区大小且不提供优化提示的更简洁版本可能如下所示:

    function GetLineAt([String] $path, [Int32] $index)
    {
        # StreamReader(String, Boolean) constructor
        # http://msdn.microsoft.com/library/9y86s1a9.aspx
        [System.IO.StreamReader] $reader = New-Object `
            -TypeName 'System.IO.StreamReader' `
            -ArgumentList ($path, $true);
        [String] $line = $null;
        [Int32] $currentIndex = 0;
    
        try
        {
            while (($line = $reader.ReadLine()) -ne $null)
            {
                if ($currentIndex++ -eq $index)
                {
                    return $line;
                }
            }
        }
        finally
        {
            $reader.Close();
        }
    
        # There are less than ($index + 1) lines in the file
        return $null;
    }
    
    GetLineAt 'file.txt' 9;
    

    【讨论】:

    • Overengineering:查看 BACON 的 SO 解决方案,了解读取文本文件的快速方法。 :)
    • 我在寻找如何为 large 文件执行此操作时偶然发现了这个问题 - 正是我所需要的。
    • @Tao 谢谢。很高兴 有人 发现这很有用。有时,内置的 PowerShell cmdlet 无法为您提供所需的控制或效率,尤其是像您所说的那样,在处理大文件时。
    • +1 对于 Northben 对过度工程的(有趣的)解释。 +1 培根的努力。
    【解决方案5】:

    只是为了好玩,这里有一些测试:

    # Added this for @Graimer's request ;) (not same computer, but one with HD little more
    # performant...)
    > measure-command { Get-Content ita\ita.txt -TotalCount 260000 | Select-Object -Last 1 }
    
    
    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 28
    Milliseconds      : 893
    Ticks             : 288932649
    TotalDays         : 0,000334412788194444
    TotalHours        : 0,00802590691666667
    TotalMinutes      : 0,481554415
    TotalSeconds      : 28,8932649
    TotalMilliseconds : 28893,2649
    
    
    > measure-command { (gc "c:\ps\ita\ita.txt")[260000] }
    
    
    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 9
    Milliseconds      : 257
    Ticks             : 92572893
    TotalDays         : 0,000107144552083333
    TotalHours        : 0,00257146925
    TotalMinutes      : 0,154288155
    TotalSeconds      : 9,2572893
    TotalMilliseconds : 9257,2893
    
    
    > measure-command { ([System.IO.File]::ReadAllLines("c:\ps\ita\ita.txt"))[260000] }
    
    
    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 0
    Milliseconds      : 234
    Ticks             : 2348059
    TotalDays         : 2,71766087962963E-06
    TotalHours        : 6,52238611111111E-05
    TotalMinutes      : 0,00391343166666667
    TotalSeconds      : 0,2348059
    TotalMilliseconds : 234,8059
    
    
    
    > measure-command {get-content .\ita\ita.txt | select -index 260000}
    
    
    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 36
    Milliseconds      : 591
    Ticks             : 365912596
    TotalDays         : 0,000423509949074074
    TotalHours        : 0,0101642387777778
    TotalMinutes      : 0,609854326666667
    TotalSeconds      : 36,5912596
    TotalMilliseconds : 36591,2596
    

    获胜者是:([System.IO.File]::ReadAllLines( path ))[index]

    【讨论】:

    • @Bacon 的回答怎么样?因为你已经有一个示例文件:-)
    • @Graimer 已添加 :)。所有这些测试都是为了在大文件中寻找大索引,我认为对于小索引的值,结果可能会有所不同。每个测试都在一个新的 powershell 会话中完成,以避免 HD 预缓存功能。
    • 我真的很惊讶ReadAllLines() 不仅速度更快,而且比Get-Content 的两次使用要快得多。顾名思义,它也在读取整个文件。无论如何,我发布了另一种方法,如果你也想尝试那个方法。此外,每当我使用Measure-Command 对代码进行基准测试时,我通常都会像1..10 | % { Measure-Command { ... } } | Measure-Object TotalMilliseconds -Average -Min -Max -Sum; 这样运行它,这样我就可以从多次测试运行中获得更准确的数字。
    • 我收到此错误:Exception calling "ReadAllLines" with "1" argument(s): "Array dimensions exceeded supported range." At line:1 char:1
    【解决方案6】:

    为了减少内存消耗并加快搜索速度,您可以使用 Get-Content cmdlet (https://technet.microsoft.com/ru-ru/library/hh849787.aspx) 的 -ReadCount 选项。

    当您处理大文件时,这可能会节省数小时。

    这是一个例子:

    $n = 60699010
    $src = 'hugefile.csv'
    $batch = 100
    $timer = [Diagnostics.Stopwatch]::StartNew()
    
    $count = 0
    Get-Content $src -ReadCount $batch -TotalCount $n | %  { 
        $count += $_.Length
        if ($count -ge $n ) {
            $_[($n - $count + $_.Length - 1)]
        }
    }
    
    $timer.Stop()
    $timer.Elapsed
    

    这会打印第 $n 行和经过的时间。

    【讨论】:

      【解决方案7】:

      我知道这是一个老问题,但尽管是该主题中观看次数最多的问题之一,但没有一个答案让我完全满意。 Get-Content 易于使用,但它在处理非常大的文本文件(例如 >5 GB)时显示出其局限性。

      我想出了一个不需要将整个文件加载到主内存中的解决方案,它比Get-Content 快得多(在Linux 上几乎与sed 一样快,例如this):

      [Linq.Enumerable]::ElementAt([System.IO.File]::ReadLines("<path_to_file>"), <index>)

      在我的机器上找到大约 4.5 GB 文件中间的一行大约需要 4 秒,而 (Get-Content -Path <path_to_file> -TotalCount <index>)[-1] 大约需要 35 秒。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多