Для меня всегда было загадкой, что же делать бедным программистам, живущим на Windows? Конечно, всегда можно поставить cygwin и использовать те же самые bash, sed и awk. Можно сразу взять быка за рога и установить Perl или Python. Но всё это какие-то ... неродные что ли решения. Вот были бы какие-нибудь стандартные программы для таких операций. С помощью стандартного коммандного процесора cmd заниматься текстовыми преобразованиями весьма проблематично...
Итак, где же решение? Довольно давно на горизонте Windows замаячил инновационный шелл под скромным названием PowerShell. От всех остальных шеллов в мире он отличается объектной-ориентированностью и строгой динамической типизацией. Как это выглядит на практике, объяснять довольно долго, и об этом прекрасно рассказано в книге "PowerShell in Action". Получилось непривычно, но вполне эффективно. Ладно, шелл шеллом, а как быть с обработкой текстовых данных? Способен ли этот шелл заменить собой AWK для Windows? Как выяснилось, ответ положительный.
Перед нами простой AWK скрипт, который обновляет два определения в rpm .spec-файле:
awk '
$1 " " $2 == "%define name" {
printf "%s %- 17s %s\n", $1, $2, "'$PKG_NAME'"
next
}
$1 " " $2 == "%define _requires" {
printf "%s %- 17s %s\n", $1, $2, "'$REQUIRED_PKG_NAME'"
next
}
{
print $0
}' $SPEC_FILE > $SPEC_FILE.newОн не претендует на шедевральность, страдает повторами, но своё дело делает исправно. Как он работает? Awk читает $SPEC_FILE построчно и каждую строку передаёт в скрипт, предварительно разбив её на поля по пробелам. Каждое поле доступно в виде переменной вида $n, где n - число от 0 до количества полей. Так, строка "Hello World" будет разбита следующим образом: $0 = "Hello World", $1 = "Hello", $2 = "World". Сам скрипт организован в виде последовательности шаблонов и соответствующих им действий. Если шаблон соответствует текущей строке, то соответствующее действие выполняется. Действие без шаблона выполняется для каждой строки, если до него доходит управление. Шаблон в awk понятие довольно общее - это может быть регулярное выражение или условное выражение, которое может сопровождаться побочными эффектами, например, выделением подгрупп из поля с помощью регулярного выражения.Предоположим, что у нас есть следующая спека:
# skipped %define name PKG_NAME %define _requires REQUIRED_PKG_NAME # skipped
После применения скрипта, полагая что
PKG_NAME=NEW_PACKAGE и REQUIRED_PKG_NAME=OLD_PACKAGE,получим:
# skipped %define name NEW_PACKAGE %define _requires OLD_PACKAGE # skipped
Как достичь аналогичного результата в PowerShell? Как оказалось, очень просто. В PowerShell есть интересный оператор
switch. Может он неожиданно много. На первый взгляд, он ничем принципиально не отличается от аналогичных операторов в языках типа C. Но в PowerShell этот оператор гораздо более гибок. Нас интересуют следующие его свойства:- Он может содержать в качестве шаблонов произвольные типы PowerShell, в частности строки;
- Он может принимать параметры (оператор, который принимает параметры, с ума сойти), которые влияют на интерпретацию шаблонов и анализируемого выражения. Мы применим:
-regex: интерпретирует шаблон-строку как регулярное выражение и проверяет анализируемое значение на соответствие ему;-file: интерпретирует анализируемое выражение как имя файла, и построчно (звучит знакомо :) ) передаёт его в операторswitch.
Итак, аналогичный скрипт на PowerShell:
$(switch -regex -file ($SPEC_FILE) {
'%define name' {
"{0,-25} {1}" -f $matches[0], $PKG_NAME
}
'%define _requires' {
"{0,-25} {1}" -f $matches[0], $REQUIRED_PKG_NAME
}
default {
$_
}
})
Он удивительно похож на исходный. Впрочем, это, скорее, не удивительно, потому что, по утверждению создателей PowerShell, они учли весь тридцатилетний опыт Unix-шеллов и опыт современных скриптовых языков как Perl и Python. Хочется отметить следующие особенности:- Автоматическая переменная
$matches, которая чудесным образом появляется после выполнения операции соответствия регулярному выражению. - Другая чудо-переменная:
$_- обозначает анализируемое выражение, то есть всю текущую строку в данном случае. - Получившиеся строки не печатаются (print, echo, write) и не возвращаются (return), не собираются в массив, а эмитируются (emit). Эмитирование - это неявное возвращение результата. Так, в Bash'е результат выполнения скрипта - это результат последней команды в нём, если нет явного вызова exit. В Groovy, функции возращают результат последнего выражения, если не указано слово return, и если функция не типа void. В PowerShell функция (или любой скрипт-блок как в switch) может эмитировать любое количество результатов, и возвращаются они все. По умолчанию, эмитированые значения выводятся на экран. Но мы перехватываем результат оператора switch c помощью нотации подвыражения
$(<statements>). Таким образом, эмитированый результат оператора становится значением подвыражения, а с этим значением можно сделать всё что угодно - хоть переменной присвоить, хоть в файл вывести.
Выразительные возможности PowerShell и оператора switch вполне соответствуют возможностям языка AWK. Из отличий хочется прежде всего упомянуть отсутствие автоматического разбиения на поля по разделителю. Строка обрабатывается целиком. Но называть это минусом я бы не стал. По моему (впрочем, небольшому) опыту, обработка строки целиком нередко оказывается даже более удобной. Кроме того, строку всегда можно разбить на поля с помощью методов String.split или Regex.split. Не стоит забывать, что ноги у PowerShell растут напрямую из .NET. PowerShell позволяет использовать более органичный синтаксис для перехвата групп регулярного выражения, чем AWK (там пришлось бы использовать такой шаблон:
match($0, /%define name/, matches)). PS: Написав всё это, я осознал, что данная задача настолько тривиальна, что можно было бы вполне обойтись двумя заменами регулярных выражений по всему тексту файла. Тем не менее, она неплохо иллюстрирует сходства и различия подхода к обработке текста в AWK и PowerShell.

No comments:
Post a Comment