web-dev-qa-db-fra.com

Obtenez les n dernières lignes ou octets d'un énorme fichier dans Windows (comme la queue d'Unix). Évitez les options fastidieuses

J'ai besoin de récupérer les n dernières lignes de fichiers énormes (1-4 Go), dans Windows 7. En raison des restrictions de l'entreprise, je ne peux exécuter aucune commande qui n'est pas intégrée. Le problème est que toutes les solutions que j'ai trouvées semblent lire l'intégralité du fichier, elles sont donc extrêmement lentes.

Cela peut-il être accompli rapidement?

Remarques:

  1. J'ai réussi à obtenir rapidement les n premières lignes.
  2. C'est ok si j'obtiens les n derniers octets. (J'ai utilisé ceci https://stackoverflow.com/a/18936628/2707864 pour les n premiers octets).

Solutions ici commande équivalente à la queue Unix dans Windows Powershell ne fonctionnait pas. En utilisant -wait ne fait pas vite. Je n'ai pas -tail (et je ne sais pas si cela fonctionnera rapidement).

PS: Il y a pas mal de questions liées à head et tail, mais pas centrées sur la question de la vitesse. Par conséquent, des réponses utiles ou acceptées peuvent ne pas être utiles ici. Par exemple.,

équivalent Windows de la commande 'tail'

Script batch CMD.EXE pour afficher les 10 dernières lignes d'un fichier txt

Extraire N lignes du fichier en utilisant la commande de fenêtres uniques

https://serverfault.com/questions/490841/how-to-display-the-first-n-lines-of-a-command-output-in-windows-the-equivalent

PowerShell pour obtenir les premiers x Mo d'un fichier

https://superuser.com/questions/859870/windows-equivalent-of-the-head-c-command

17
sancho.s

Que diriez-vous de cela (lit les 8 derniers octets pour la démo):

$fpath = "C:\10GBfile.dat"
$fs = [IO.File]::OpenRead($fpath)
$fs.Seek(-8, 'End') | Out-Null
for ($i = 0; $i -lt 8; $i++)
{
    $fs.ReadByte()
}

MISE À JOUR . Pour interpréter les octets comme une chaîne (mais assurez-vous de sélectionner le bon codage - ici UTF8 est utilisé):

$N = 8
$fpath = "C:\10GBfile.dat"
$fs = [IO.File]::OpenRead($fpath)
$fs.Seek(-$N, [System.IO.SeekOrigin]::End) | Out-Null
$buffer = new-object Byte[] $N
$fs.Read($buffer, 0, $N) | Out-Null
$fs.Close()
[System.Text.Encoding]::UTF8.GetString($buffer)

MISE À JOUR 2. Pour lire les M dernières lignes, nous lirons le fichier par portions jusqu'à ce qu'il y ait plus de M séquences de caractères de nouvelle ligne dans le résultat:

$M = 3
$fpath = "C:\10GBfile.dat"

$result = ""
$seq = "`r`n"
$buffer_size = 10
$buffer = new-object Byte[] $buffer_size

$fs = [IO.File]::OpenRead($fpath)
while (([regex]::Matches($result, $seq)).Count -lt $M)
{
    $fs.Seek(-($result.Length + $buffer_size), [System.IO.SeekOrigin]::End) | Out-Null
    $fs.Read($buffer, 0, $buffer_size) | Out-Null
    $result = [System.Text.Encoding]::UTF8.GetString($buffer) + $result
}
$fs.Close()

($result -split $seq) | Select -Last $M

Essayez de jouer avec de plus gros $buffer_size - ceci est idéalement égal à la longueur de ligne moyenne attendue pour effectuer moins d'opérations sur le disque. Faites également attention à $ seq - cela pourrait être \r\n ou juste \n. C'est du code très sale sans aucune gestion d'erreur et optimisation.

14
Aziz Kabyshev

Si vous avez PowerShell 3 ou supérieur, vous pouvez utiliser le -Tail paramètre pour Get-Content pour obtenir les dernières n lignes.

Get-content -tail 5 PATH_TO_FILE;

Sur un fichier texte de 34 Mo sur mon SSD local, cela est revenu en 1 milliseconde contre 8,5 secondes pendant get-content |select -last 5

47
alroc

Avec la réponse impressionnante d'Aziz Kabyshev , qui résout le problème de la vitesse, et avec quelques recherches sur Google, j'ai fini par utiliser ce script

$fpath = $Args[1]
$fs = [IO.File]::OpenRead($fpath)
$fs.Seek(-$Args[0], 'End') | Out-Null
$mystr = ''
for ($i = 0; $i -lt $Args[0]; $i++)
{
    $mystr = ($mystr) + ([char[]]($fs.ReadByte()))
}
$fs.Close()
Write-Host $mystr

que j'appelle à partir d'un fichier batch contenant

@PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '.\myscript.ps1' %1 %2"

(merci à Comment exécuter un script PowerShell à partir d'un fichier batch ).

3
sancho.s

Ce n'est pas une réponse, mais un grand commentaire comme réponse à la réponse de sancho.s.

Lorsque vous souhaitez utiliser de petits scripts PowerShell à partir d'un fichier Batch, je vous suggère d'utiliser la méthode ci-dessous, qui est plus simple et permet de conserver tout le code dans le même fichier Batch:

@PowerShell  ^
   $fpath = %2;  ^
   $fs = [IO.File]::OpenRead($fpath);  ^
   $fs.Seek(-%1, 'End') ^| Out-Null;  ^
   $mystr = '';  ^
   for ($i = 0; $i -lt %1; $i++)  ^
   {  ^
      $mystr = ($mystr) + ([char[]]($fs.ReadByte()));  ^
   }  ^
   Write-Host $mystr
%End PowerShell%
1
Aacini