62 Stimmen

Entfernen Sie die oberste Zeile der Textdatei mit PowerShell

Ich versuche nur, die erste Zeile von etwa 5000 Textdateien zu entfernen, bevor ich sie importiere.

Ich bin immer noch sehr neu in PowerShell, daher weiß ich nicht, wonach ich suchen soll oder wie ich vorgehen soll. Mein aktuelles Konzept mit Pseudocode:

set-content file (get-content unless line contains amount)

Ich kann jedoch anscheinend nicht herausfinden, wie man so etwas wie 'contains' macht.

57voto

Michael Sorens Punkte 33793

Während ich die Antwort von @hoge wirklich bewundere, sowohl für eine sehr prägnante Technik als auch für eine Wrapper-Funktion zur Verallgemeinerung, und ich ermutige dazu, dafür Upvotes zu geben, fühle ich mich veranlasst, zu den anderen beiden Antworten Stellung zu beziehen, die temporäre Dateien verwenden (es nagt an mir wie Fingernägel auf einer Tafel!).

Vorausgesetzt, die Datei ist nicht riesig, können Sie die Pipeline dazu zwingen, in diskreten Abschnitten zu arbeiten - und somit den Bedarf an einer temporären Datei überflüssig machen - durch eine kluge Verwendung von Klammern:

(Get-Content $file | Select-Object -Skip 1) | Set-Content $file

... oder in Kurzform:

(gc $file | select -Skip 1) | sc $file

46voto

Richard Berg Punkte 20483

Es ist nicht das effizienteste auf der Welt, aber das sollte funktionieren:

get-content $file |
    select -Skip 1 |
    set-content "$file-temp"
move "$file-temp" $file -Force

0 Stimmen

Wenn ich versuche, dies auszuführen, scheint es, dass es bei dem -skip einen Fehler gibt. Könnte das vielleicht von einer anderen Version kommen?

2 Stimmen

- Überspringen ist neu für Select-Object in PowerShell 2.0. Außerdem, wenn die Dateien alle ASCII sind, möchten Sie möglicherweise Set-Content -enc ascii verwenden. Wenn die Codierungen gemischt sind, wird es schwieriger, es sei denn, Sie interessieren sich nicht für die Dateicodierung.

13voto

hoge Punkte 183

Unter Verwendung der Variablennotation können Sie dies ohne eine temporäre Datei tun:

${C:\file.txt} = ${C:\file.txt} | select -skip 1

function Remove-Topline ( [string[]]$path, [int]$skip=1 ) {
  if ( -not (Test-Path $path -PathType Leaf) ) {
    throw "ungültiger Dateiname"
  }

  ls $path |
    % { iex "`${$($_.fullname)} = `${$($_.fullname)} | select -skip $skip" }
}

9voto

AASoft Punkte 1346

Ich musste gerade die gleiche Aufgabe erledigen, und gc | select ... | sc hat auf meinem Rechner über 4 GB RAM verwendet, während es eine 1,6 GB große Datei gelesen hat. Es hat mindestens 20 Minuten gedauert, um den gesamten Inhalt der Datei zu lesen (wie im Process Explorer durch die gelesenen Bytes angezeigt), zu diesem Zeitpunkt musste ich es beenden.

Meine Lösung war, einen mehr .NET-basierten Ansatz zu verwenden: StreamReader + StreamWriter. Siehe diese Antwort für eine großartige Diskussion zur Leistung: In Powershell, was ist der effizienteste Weg, eine große Textdatei nach Datensatztyp aufzuteilen?

Im Folgenden ist meine Lösung. Ja, sie verwendet eine temporäre Datei, aber in meinem Fall war es egal (es handelte sich um eine riesige SQL-Tabellenerstellungs- und Einfüge-Anweisungen-Datei):

PS> (measure-command{
    $i = 0
    $ins = New-Object System.IO.StreamReader "in/file/pa.th"
    $outs = New-Object System.IO.StreamWriter "out/file/pa.th"
    while( !$ins.EndOfStream ) {
        $line = $ins.ReadLine();
        if( $i -ne 0 ) {
            $outs.WriteLine($line);
        }
        $i = $i+1;
    }
    $outs.Close();
    $ins.Close();
}).TotalSeconds

Es ergab:

188.1224443

0 Stimmen

Ich glaube, das liegt daran, dass die Klammern um das gc|select bedeuten, dass die gesamte Datei in den Speicher geladen wird, bevor sie durchgeleitet wird. Andernfalls führt der offene Stream dazu, dass set-content fehlschlägt. Für große Dateien ist meiner Meinung nach dein Ansatz wahrscheinlich am besten.

0 Stimmen

Vielen Dank, @AASoft, für Ihre großartige Lösung! Ich habe es mir erlaubt, sie etwas zu verbessern, indem ich die Vergleichsoperation in jeder Schleife weggelassen habe, was den Prozess um etwa 25% beschleunigt hat - siehe meine Antwort für Details.

8voto

Oliver Punkte 8783

Inspiriert durch AASoft's Antwort ging ich hinaus, um sie noch etwas zu verbessern:

  1. Vermeiden Sie die Schleifenvariable $i und den Vergleich mit 0 in jeder Schleife
  2. Wrapper die Ausführung in einen try..finally Block, um die Dateien immer zu schließen, die in Benutzung sind
  3. Die Lösung so gestalten, dass sie für eine beliebige Anzahl von zu entfernenden Zeilen am Anfang der Datei funktioniert
  4. Verwenden Sie eine Variable $p, um das aktuelle Verzeichnis zu referenzieren

Diese Änderungen führen zu folgendem Code:

$p = (Get-Location).Path

(Measure-Command {
    # Anzahl der zu überspringenden Zeilen
    $skip = 1
    $ins = New-Object System.IO.StreamReader ($p + "\test.log")
    $outs = New-Object System.IO.StreamWriter ($p + "\test-1.log")
    try {
        # Überspringe die ersten N Zeilen, erlaube aber auch weniger als N
        for( $s = 1; $s -le $skip -and !$ins.EndOfStream; $s++ ) {
            $ins.ReadLine()
        }
        while( !$ins.EndOfStream ) {
            $outs.WriteLine( $ins.ReadLine() )
        }
    }
    finally {
        $outs.Close()
        $ins.Close()
    }
}).TotalSeconds

Die erste Änderung hat die Verarbeitungszeit für meine 60 MB Datei von 5.3s auf 4s reduziert. Der Rest der Änderungen ist eher kosmetischer Natur.

1 Stimmen

Du könntest -and !$ins.EndOfStream zur for-Schleifenbedingung hinzufügen, um die Fälle abzudecken, in denen die Datei weniger Zeilen als $skip hat.

0 Stimmen

Vielen Dank für die Info! Das ergibt Sinn :-)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X