2010-01-15 15 views
38

Je suis en train de supprimer la première ligne d'environ 5000 fichiers texte avant de les importer.Supprimer la ligne supérieure du fichier texte avec PowerShell

Je suis encore très jeune avec PowerShell, donc je ne sais pas trop quoi rechercher ni comment l'aborder. Mon concept actuel à l'aide de pseudo-code:

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

Cependant, je ne peux pas sembler comprendre comment faire quelque chose comme contient.

Répondre

30

Il n'est pas le plus efficace dans le monde, mais cela devrait fonctionner:

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

Lorsque j'essaie de lancer ceci, il semble qu'il y ait des erreurs sur le -skip. Cela pourrait-il provenir d'une version différente? – percent20

+1

-Skip est nouveau dans Select-Object dans PowerShell 2.0. En outre, si les fichiers sont tous ascii, vous pouvez utiliser set-content -enc ascii. Si les encodages sont mélangés, cela devient plus compliqué à moins que vous ne vous souciez pas de l'encodage du fichier. –

+0

J'ai installé PowerShell 2.0 et cela fonctionne maintenant. – percent20

10

En utilisant la notation variable, vous pouvez le faire sans un fichier temporaire:

${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 "invalid filename" 
    } 

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

skip` n » ai pas travail t, donc ma solution de contournement est

$LinesCount = $(get-content $file).Count 
get-content $file | 
    select -Last $($LinesCount-1) | 
    set-content "$file-temp" 
move "$file-temp" $file -Force 
29

alors que j'admire vraiment la réponse de @hoge à la fois une technique très concise et une fonction wrapper pour le généraliser et j'encourage les upvotes pour cela, je suis obligé de commenter les deux autres réponses qui utilisent des fichiers temporaires (ça me ronge comme des ongles sur un tableau!).

En supposant que le fichier est énorme, vous pouvez forcer la conduite à fonctionner dans des sections distinctes - évitant ainsi la nécessité d'un fichier temporaire - avec une utilisation judicieuse des parenthèses:

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

... ou sous forme courte:

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

Je devais faire la même tâche, et gc | select ... | sc a pris plus de 4 Go de RAM   sur ma machine lors de la lecture d'un fichier de 1,6 Go  . Il n'a pas fini pendant au moins 20 minutes après avoir lu le fichier entier (comme indiqué par Read Bytes dans Process Explorer), à quel point je devais le tuer.

Ma solution consistait à utiliser une approche plus .NET: StreamReader + StreamWriter. Voir cette réponse pour une excellente réponse discuter PERF: In Powershell, what's the most efficient way to split a large text file by record type?

Ci-dessous est ma solution. Oui, il utilise un fichier temporaire, mais dans mon cas, il n'a pas d'importance (ce fut une énorme création de la table SQL et insérer fichier des déclarations flippe):

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 

Il est revenu:

188.1224443 
+0

IIRC Cela est dû au fait que les parenthèses autour de gc | select signifient qu'il lit le fichier entier en mémoire avant de l'acheminer.Sinon, le flux ouvert provoque l'échec du contenu set. Pour les gros fichiers, je pense que votre approche est probablement la meilleure – Alex

+0

Merci, @AASoft, pour votre excellente solution! Je me suis permis de l'améliorer légèrement en supprimant l'opération de comparaison dans chaque boucle accélérant le processus de quelque chose comme 25% - voir [ma réponse] (http://stackoverflow.com/a/24746158/177710) pour plus de détails. – Oliver

1
$x = get-content $file 
$x[1..$x.count] | set-content $file 

Juste cela. Une longue explication ennuyeuse suit. Get-content renvoie un tableau. Nous pouvons "indexer" des variables de tableau, comme démontré dans this et other Postes de scripteurs.

Par exemple, si nous définissons une variable de tableau comme celui-ci,

$array = @("first item","second item","third item") 

donc $ array retourne

first item 
second item 
third item 

alors nous pouvons "index dans" ce tableau pour récupérer uniquement son 1er élément

$array[0] 

ou seulement sa 2ème

$array[1] 

ou un range de valeurs d'index du 2ème jusqu'au dernier.

$array[1..$array.count] 
3

Je viens d'apprendre d'un site Web:

Get-ChildItem *.txt | ForEach-Object { (get-Content $_) | Where-Object {(1) -notcontains $_.ReadCount } | Set-Content -path $_ } 

Ou vous pouvez utiliser les alias pour faire court, comme:

gci *.txt | % { (gc $_) | ? { (1) -notcontains $_.ReadCount } | sc -path $_ } 
+0

Merci beaucoup pour cette solution. Pourriez-vous indiquer le site Web que vous avez mentionné? – giordano

-1

Pour les fichiers plus petits que vous pouvez utiliser ceci:

& C: \ windows \ system32 \ more +1 oldfile.csv> nouveaufichier.csv | out-null

... mais ce n'est pas très efficace pour traiter mon fichier d'exemple de 16 Mo. Il ne semble pas se terminer et libérer le verrou sur newfile.csv.

4

Inspiré par AASoft's answer, je suis sorti pour améliorer un peu plus:

  1. Évitez la variable de boucle $i et la comparaison avec 0 dans chaque boucle
  2. Wrap l'exécution dans un try..finally bloquer pour toujours fermer les fichiers en cours d'utilisation
  3. Faire fonctionner la solution pour un nombre arbitraire de lignes à supprimer depuis le début du fichier
  4. Utilisez un $p variable pour référencer le répertoire courant

Ces changements conduisent au code suivant:

$p = (Get-Location).Path 

(Measure-Command { 
    # Number of lines to skip 
    $skip = 1 
    $ins = New-Object System.IO.StreamReader ($p + "\test.log") 
    $outs = New-Object System.IO.StreamWriter ($p + "\test-1.log") 
    try { 
     # Skip the first N lines, but allow for fewer than N, as well 
     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 

Le premier changement a le temps de traitement pour mon 60 Fichier MB vers le bas de 5.3s à 4s. Le reste des changements est plus cosmétique.

+0

Vous pouvez ajouter '--and! $ Ins.EndOfStream' au conditionnel de la boucle' for' pour couvrir les cas où le fichier contient moins de lignes que '$ skip'. – AASoft

+0

Merci pour les heads up! Ça a du sens :-) – Oliver

Questions connexes