Powershell3: распознавать и отображать последние строки из файла ASCII - PullRequest
0 голосов
/ 05 июля 2018

Я думаю, это должно быть просто. Я записываю вывод журнала xcopy в простой текстовый файл с ежедневным разделителем (буквально) "++++++++++++++++++++Tue 07/03/2018 0900 PM" добавляется в файл журнала перед каждым ежедневным резервным копированием. Таким образом, последние строки в файле обычно выглядят так:

daily delimiter

Новый день добавляет новую разделительную линию и так далее.

Я хочу отобразить разделитель LAST и следующие за ним строки до eof.

Схема, которую я пробовал GET-Content, Select-String -Context 0,20 не работает,

PS говорит, что моя строка поиска ++++++++++++++++++++ не является регулярным выражением, не распознает путь и т. Д. и т.д. Любая помощь?

Память и время не имеют значения . Извините, если это слишком просто.

Ответы [ 4 ]

0 голосов
/ 05 июля 2018

Другой способ использования RegEx для разделения файла на разделы.

  • используйте Get-Content с параметром -Raw, чтобы иметь одну строку, а не массив строк
  • используйте непотребляющий положительный прогноз , чтобы разбить файл на разделы, начинающиеся с
    20 * + -split '(?=\+{20})', которые не пусты -ne ''
  • используйте индекс [-1], чтобы получить последний раздел.

Пример вывода

PS> ((Get-Content '.\LogFile.txt' -raw) -split '(?=\+{20})' -ne '')[-1]
++++++++++++++++++++Mon 07/03/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups Mon 07/02/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups\OutlookBak Mon 07/02/2018 0900 PM
0 голосов
/ 05 июля 2018

полезный ответ msjqu объясняет необходимость экранирования + символов. как \+ в регулярном выражении для этих символов. трактоваться как литералы .

Таким образом, регулярное выражение для соответствия строке заголовка - 20 + символов. в начале строки (^) - это: ^\+{20}

Тем не менее, если достаточно обнаружить строки заголовка по 20 + знакам, Get-Content -Delimiter - который поддерживает только литералы в качестве разделителей - предлагает простой и эффективное решение (PSv3 +; предполагается, что входной файл some.log в текущем каталоге ./):

 $headerPrefix = '+' * 20  # -> '++++++++++++++++++++'
 $headerPrefix + (Get-Content ./some.log -Delimiter $headerPrefix -Tail 1)

-Delimiter использует указанную сигнатуру строки заголовка, чтобы разбить файл на «строки» (текст между экземплярами разделителя, которые здесь составляют блоков строк), а -Tail 1 возвращает последнее » line "(block) путем его поиска из end файла. Кончик шляпы mjsqu за помощь в поиске решения.


Следующие альтернативные решения являются на основе регулярных выражений , что обеспечивает более сложное сопоставление строк заголовка.

Примечание. Хотя ни одно из приведенных ниже решений не требует считывания файла журнала в память в целом , они читают весь файл , а не только с конца .


Мы можем использовать это в операторе switch -regex -file , чтобы обработать все строки файла журнала, чтобы собрать строки, которые начинаются и следуют за последним ^\+{20} соответствием ; код предполагает путь к входному файлу ./some.log:

# Process all lines in the log file and 
# collect each block's lines along the way in 
# array $lastBlockLines, which means that after 
# all lines have been processed, $lastBlockLines contains
# the *last* block's lines.
switch -regex -file ./some.log {
  '^\+{20}' { $lastBlockLines = @($_) } # start of new block, (re)initialize array
  default   { $lastBlockLines += $_ }   # add line to block
}

# Output the last block's lines.
$lastBlockLines

В качестве альтернативы , , если вы хотите принять фиксированное максимальное количество строк в блоке , решение для одного конвейера с использованием Select-String возможно:

Select-String '^\+{20}' ./some.log -Context 0,100 | Select-Object -Last 1 | 
  ForEach-Object { $_.Line; $_.Context.PostContext }
  • Select-String '^\+{20}' ./some.log -Context 0,100 соответствует всем строкам заголовка в файле ./some.log и, благодаря -Context 0, 100, включает (до) 100 строк, следующих за совпадающей строкой в ​​излучаемом объекте сопоставления (0) означает, что никакие строки, которые предшествуют соответствующей строке, не должны быть включены).

  • Select-Object -Last 1 пропускает только последний матч.

  • ForEach-Object { $_.Line; $_.Context.PostContext } затем выводит строку соответствия последнего матча, а также до 100 строк, следующих за ней.


Если вы не против прочитать файл дважды , вы можете объединить Select-String с Get-Content ... | Select-Object -Skip:

Get-Content ./some.log | Select-Object -Skip (
    (Select-String '^\+{20}' ./some.log | Select-Object -Last 1).LineNumber - 1
  )

Это использует тот факт, что объекты сопоставления, испускаемые Select-String, имеют свойство .LineNumber, отражающее номер строки, на которой было найдено данное совпадение. Передав номер строки последнего совпадения минус 1 в Get-Content ... | Select-Object -Skip, затем выводится строка совпадения, а также все последующие.

0 голосов
/ 05 июля 2018

Лично я бы изменил этот формат ведения журнала, чтобы он был более дружественным к объектам и использовался как обычно.

Однако, исходя из того, что вы опубликовали. Вот один из способов, я уверен, что есть более элегантные, но это вопросы и ответы (быстрые и грязные). Кроме того, в качестве военного ветеринара (более 20 лет), который все еще живет и работает в военное время, 09:00 - это 9:00, а 21:00 - 9:00. 8 ^} ... Просто говорю ...

# Get the lines in the file
($DataSet = Get-Content -Path '.\LogFile.txt')

# Results

++++++++++++++++++++Mon 07/02/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups Mon 07/02/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups\OutlookBak Mon 07/02/2018 0900 PM
++++++++++++++++++++Mon 07/03/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups Mon 07/02/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups\OutlookBak Mon 07/02/2018 0900 PM



 # Get the index of the LastDateEntry, using a string match (RegEx)
($LastDateEntry = (Get-Content -Path '.\LogFile.txt' | %{$_ | Select-String -Pattern '[+].*'}) | Select -Last 1)

# Results

++++++++++++++++++++Mon 07/03/2018 0900 PM


# Get the LastDateEntryIndex
($DateIndex = (Get-Content -Path '.\LogFile.txt').IndexOf($LastDateEntry))

# Results

5



 # Get the data using the index
ForEach($Line in $DataSet)
{
    If ($Line.ReadCount -ge $DateIndex)
    {
    Get-Content -Path '.\LogFile.txt' | Select-Object -Index ($Line.ReadCount)
    }
}

# Results

++++++++++++++++++++Mon 07/03/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups Mon 07/02/2018 0900 PM
0 Files(s) copied
 Xcopy SUCCEEDED K:\ to J:\MyUSBBackups\OutlookBak Mon 07/02/2018 0900 PM
0 голосов
/ 05 июля 2018

TLDR; Выйдите из поиска +, используйте "\ + \ + \ +" и т. Д.

Фон

К сожалению + - зарезервированный символ в мире регулярных выражений.

Что означает + в регулярном выражении?

Он указывает движку на совпадение с предыдущим оператором поиска (символ, диапазон или код, представляющий группу символов, таких как \ d - цифры) один или несколько раз. Вы можете увидеть больше информации об этой ошибке в Powershell, выполнив следующее:

[regex]$x = "++++"

Возвращает:

Cannot convert value "++++" to type "System.Text.RegularExpressions.Regex". Error: "parsing "++++" - Quantifier {x,y} following nothing."
At line:1 char:1
+ [regex]$x = "++++"
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

Он говорит, что квантификатор (+) ничего не следует.

Итак, нам нужно сбежать от +, используя \:

[regex]$x = "\+\+\+\+"

$x.Match('++++')

Возвращает следующее безошибочное совпадение:

Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 0
Length   : 4
Value    : ++++

Улучшение

Если вы знаете, сколько +, вы можете сопоставить на "\+{20}", если их 20. Или из предыдущего примера:

[regex]$x = "\+{4}"

$x.Match('++++')
...