PowerShell: таблицы для анализа данных

Feb 24, 2023 09:29

Окружение: операционная система «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 окажется нужная мне таблица (или в данном случае - одна строка таблицы). После чего долго разбирался, почему скрипт работает не так, как я планировал, и в чем же может быть дело.

Инструмент, Образование, Программирование, Английский язык

Previous post Next post
Up