Окружение: операционная система «Windows 10», программа-оболочка «PowerShell» версии 7.
Получив большие куски данных от программы-сервера ЖЖ («Живого Журнала»), я их сохранил и захотел проанализировать. Конечно, можно выгрузить данные в какие-нибудь
электронные таблицы вроде всем известных «
Microsoft Excel» или «
Google Таблиц». Но мне захотелось начать анализ средствами программы-оболочки «PowerShell».
Проблема со структурой данных
Сначала я стал прикидывать, какие структуры данных могут подойти для хранения таблиц. Что такое «таблица»? Это, грубо говоря, двумерный массив.
В программе-оболочке «PowerShell» есть одномерный массив; есть одномерный массив, похожий на многомерный (по-английски «
jagged array», то есть по-русски «рваный» или «неровный» из-за того, что вложенные массивы могут быть разной длины) и есть действительно многомерный массив. (
Тут подробнее.) Например:
$oneDim = 1, 2, 3 # одномерный
$jagged = @( # одномерный, похожий на двумерный
(1, 2, 3),
(1, 2, 3),
(1, 2, 3)
)
$twoDim = [int[,]]::New(3,3) # действительно двумерный
$twoDim[0,0] = 1; $twoDim[0,1] = 2; $twoDim[0,2] = 3
$twoDim[1,0] = 1; $twoDim[1,1] = 2; $twoDim[1,2] = 3
$twoDim[2,0] = 1; $twoDim[2,1] = 2; $twoDim[2,2] = 3
Еще можно использовать массив хеш-таблиц (неупорядоченных или упорядоченных).
Я не буду тут углубляться в тонкости использования этих структур данных, пост не об этом. Да, в этих структурах данных можно хранить данные табличного типа, но вот в результате вывода в окно программы-оболочки «PowerShell» этих структур данных по умолчанию в окне не получается таблицы с данными. В случае массива (хоть одномерного, хоть многомерного) выводится столбик со значениями элементов (его можно повернуть горизонтально, в строку, если сложить с пустой строкой). В случае массива хеш-таблиц (хоть упорядоченных, хоть неупорядоченных) выводится таблица, но не с теми колонками, которые нужны мне, а только с двумя колонками: «Name» (ключ) и «Value» (значение).
Подходящая структура данных
Решение я
нашел на сайте «Stack Overflow». Многие командлеты возвращают данные в окно программы-оболочки «PowerShell» в табличном виде. При этом используется структура данных примерно следующего вида:
$table = @(
[PSCustomObject]@{кол1=1; кол2=2; кол3=3}
[PSCustomObject]@{кол1=1; кол2=2; кол3=3}
[PSCustomObject]@{кол1=1; кол2=2; кол3=3}
)
Вывод в окно программы-оболочки «PowerShell»:
$table
кол1 кол2 кол3
---- ---- ----
1 2 3
1 2 3
1 2 3
Это то, что мне нужно. Что представляет собой эта структура данных? В переменной $table хранится обычный одномерный массив объектов, каждый из которых представляет строку таблицы. Сущность PSCustomObject - это так называемый «
type accelerator» (по-русски дословно «ускоритель типа», имеется в виду, что это выражение можно использовать для короткой ссылки на определенное название типа значения или класса объекта), указывающий на класс «
System.Management.Automation.PSCustomObject». Таким образом, каждая строка нашей таблицы представляет собой объект этого класса.
Справа от выражения [PSCustomObject], определяющего класс объекта (строки таблицы), располагается хеш-таблица, в которой мы определяем названия колонок и значения, хранящиеся в ячейках таблицы. Такой синтаксис является
одним из способов создания объектов в программе-оболочке «PowerShell».
Отмечу, что числа в ячейках полученной таблицы при выводе в окно программы-оболочки автоматически выравниваются вправо (это видно в блоке кода выше). Если в ячейку таблицы поместить строку, то она автоматически будет выравниваться по левому краю ячейки.
Создание новых строк в таблице
Часто в программе (скрипте) вы знаете, сколько и каких колонок будет в вашей таблице, но вы заранее не знаете, сколько в таблице понадобится строк. Да если и знаете, значения в больших таблицах неудобно задавать способом, показанным выше. Поэтому в программе как-то придется создавать новые строки и добавлять их к таблице.
Для хранения таблицы, конечно, можно использовать обычный одномерный массив объектов типа System.Object[] (производный класс от базового класса «
System.Array»). Такая структура данных позволяет добавлять элементы в массив с помощью оператора +=. Такой способ работы подходит для небольших массивов, но,
как пишут, он не предназначен для работы со множеством операций добавления элементов в большие массивы. Такие массивы являются неизменяемыми (по-английски «immutable»), а сложение организовано созданием нового массива нужной длины, в который переписываются элементы старого массива и добавляемый элемент. Понятно, что такая организация работы тратит слишком много времени и оперативной памяти.
Есть другие реализации массива, которые являются изменяемыми. Для хранения таблицы я решил использовать класс «
System.Collections.ArrayList». Это тоже массив, но этот массив создан для структур данных с изменяемой длиной, что мне и требуется. Создание таблицы при использовании этого класса несколько отличается:
$table = [System.Collections.ArrayList]::new()
Добавление строки таблицы с пустыми ячейками в конец таблицы:
$row = [PSCustomObject]@{кол1=$null;кол2=$null;кол3=$null;кол4=$null}
$i = $table.Add($row)
Как видно в блоке кода выше, метод Add возвращает индекс добавленной строки. Теперь этот индекс можно использовать для заполнения ячеек добавленной строки:
$table[$i].кол1 = "яблоко"
$table[$i].кол3 = "груша" # и так далее
$table
кол1 кол2 кол3 кол4
---- ---- ---- ----
яблоко груша
Для создания строки таблицы можно использовать командлет «
Select-Object». Вообще этот командлет обычно используют для других целей, но программисты на языке «PowerShell» приспособили его следующим образом:
$row = "" | Select-Object "кол1","кол2","кол3","кол4"
это аналог приводившегося выше кода:
$row = [PSCustomObject]@{кол1=$null;кол2=$null;кол3=$null;кол4=$null}
Если в таблице много колонок
В вышеприведенных примерах я специально не использовал количество колонок в таблице большее четырех. Посмотрим, что произойдет, если в таблице больше четырех колонок:
"" | Select-Object "кол1","кол2","кол3","кол4"
кол1 кол2 кол3 кол4
---- ---- ---- ----
"" | Select-Object "кол1","кол2","кол3","кол4","кол5"
кол1 :
кол2 :
кол3 :
кол4 :
кол5 :
Как видно из блока выше, если количество колонок в таблице больше четырех, то по умолчанию данные уже не будут отображены в окне программы-оболочки в виде таблицы. В этом случае свойства каждой строки-объекта выводятся в столбик, а не в строку. Чтобы вернуть отображение такой таблицы в виде таблицы, можно использовать командлет «
Format-Table». Например:
"" | Select-Object "кол1","кол2","кол3","кол4","кол5" | Format-Table
кол1 кол2 кол3 кол4 кол5
---- ---- ---- ---- ----
Манипуляции с таблицей для анализа данных
Поскольку наша таблица является одномерным массивом строк-объектов, мы легко можем отправить ее по конвейеру таким командлетам, как «
Sort-Object» (для сортировки строк в таблице), «
Where-Object» (для отбора нужных строк в таблице), «
ForEach-Object» (для манипуляций со строками таблицы) и так далее, есть много подходящих командлетов. Описание всего этого вместить в один пост не получается, поэтому я буду по мере необходимости описывать манипуляции с таблицей в следующих постах.
Отмечу только, что при манипуляциях с таблицей имеет смысл внимательно следить за тем, что возвращает какой-либо из перечисленных выше командлетов. К примеру, командлеты «Sort-Object», «Where-Object» и «ForEach-Object» принимают и возвращают объект (или коллекцию объектов), их результат сам по себе можно сохранить в переменную и использовать как таблицу, если на входе в эти командлеты тоже была таблица. При этом командлет «Format-Table» работает иначе: он принимает объект (или коллекцию объектов), а вот возвращает не те объекты, которые он принял на входе, а объекты специального класса «
Microsoft.PowerShell.Commands.Internal.Format». Эти объекты предназначены для отображения в окне программы-оболочки, но они не подойдут, если вы рассчитываете получить строки таблицы как структуры данных, описанной выше.
Таким образом, командлет «Format-Table» имеет смысл использовать только в конце конвейера при выводе данных в окно программы-оболочки, но не в конце конвейера при сохранении в переменную! То есть вот так делать нормально (это уже было показано выше), вывод в окно программы-оболочки:
"" | Select-Object "кол1","кол2","кол3","кол4","кол5" | Format-Table
А вот так лучше не делать, если вы не понимаете точно, зачем вам это нужно (сохранение в переменную):
$row = "" | Select-Object "кол1","кол2","кол3","кол4","кол5" | Format-Table
Я сделал в скрипте что-то похожее, рассчитывая, что в переменной $row окажется нужная мне таблица (или в данном случае - одна строка таблицы). После чего долго разбирался, почему скрипт работает не так, как я планировал, и в чем же может быть дело.