Мне кажется, давно зреет тема сравнения возможностей PowerShell и оболочек мира UNIX. Сравнения не в холиварном смысле этого слова, а в позитивно-конструктивном. Линуксовым скриптописателям (не фанатикам), думаю, будет интересно узнать, как делаются те или иные штуки, которые они привыкли делать на bash или zsh, на PowerShell. Пожалуй, я и начну такую тему — и очень надеюсь, что кто-то из моих коллег-повершелловцев (Guderian, ApeCoder) также поддержит эту тему.
В UNIX для поиска текста в дереве файлов существует довольно популярная связка утилит
Давайте посмотрим, что предлагает нам PowerShell для этих целей.
Прежде всего, давайте попытаемся заменить
Здесь мы ищем все вхождения слова
Идём далее: что, если мы хотим поискать указанную строку в файлах, имена которых могут совпадать с несколькими шаблонами (например, как
Что, если мы хотим найти в этих файлах несколько шаблонов поиска:
На самом деле, оба параметра (и
Но вы ведь помните, что PowerShell оперирует не текстом, а объектами? Давайте посмотрим, объекты какого рода выдаёт нам
Как видите, мы не только можем посмотреть вывод команды, но и буквально разобрать его на части — выделить на какой строке и в каком файле произошло совпадение. Мы можем построить какой нам нужен вывод данных самостоятельно (например, в XML). По-моему, это очень увлекательно и удобно. То, чего часто не хватает в zsh.
Мне кажется, основная идея понятна;
Что очень важно для нашего пользователя,
Посмотрим теперь, как искать файлы в дереве каталогов. В PowerShell для этого используется
Обратите особое внимание — в аналогичных условиях в bash или zsh нам пришлось бы эскейпить звёздочки, т.к. они раскрываются самой оболочкой. В PowerShell несколько иной подход: шаблоны передаются непосредственно коммандлету и он уже при помощи внутреннего окружения PowerShell раскрывает их в нужных местах (я не знаком с внутренней кухней PowerShell, но принцип примерно таков). Это очень удобно для меня, т.к. в подобных случаях в Linux я часто получал нерабочие скрипты, и долго думал, из-за чего они не находят файлы. Но это, конечно, просто моя невнимательность.
Вернёмся к
Мы можем сделать какой-нибудь сумашедший поиск, вроде поиска файлов, совпадающих с шаблоном номер один, и исключения из этого списка тех файлов, которые совпадают с шаблоном номер два:
Если вам нужно ещё больше контроля над поиском, используйте коммандлет
Хорошо, мы увидели возможности и
По-моему, довольно удобно!
В UNIX для поиска текста в дереве файлов существует довольно популярная связка утилит
find и grep. Например, с помощью этих утилит мы можем найти все упоминания ключевого слова class в нашем дереве исходников:$ find -name \*.cpp -o -name \*.hpp -exec grep -Hb class {} \;Давайте посмотрим, что предлагает нам PowerShell для этих целей.
Прежде всего, давайте попытаемся заменить
grep. Для аналогичных целей в окружении PowerShell есть коммандлет Select-String. Попробуем что-нибудь простое:$ Select-String class *.hpp
CustomTimeEdit.hpp:5:class CustomTimeEdit : public QTimeEdit {
DaySelecter.hpp:12: class Model : public QAbstractItemModel
DaySelecter.hpp:43: class View : public QTreeView
[...]Здесь мы ищем все вхождения слова
class во всех файлах с расширением hpp в текущем каталоге. К слову сказать, документация по Select-String даёт нам неверную информацию о порядке следования аргументов: там написано, что первым должно следовать имя файла (параметр -Path), а следом за ним — шаблон поиска (параметр -Pattern). Но на самом деле всё наоборот (уж не знаю, почему). Естественно, если использовать не позиционные, а именованые параметры, то такой проблемы не возникает.Идём далее: что, если мы хотим поискать указанную строку в файлах, имена которых могут совпадать с несколькими шаблонами (например, как
hpp-, так и cpp-файлы):$ Select-String DaySelector *.hpp,*.cpp
DaySelecter.hpp:1:#ifndef __DAYSELECTER_HPP__
DaySelecter.hpp:2:#define __DAYSELECTER_HPP__
DaySelecter.hpp:10:namespace DaySelecter {
[...]Что, если мы хотим найти в этих файлах несколько шаблонов поиска:
$ Select-String DaySelector,MainWindow *.hpp,*.cpp
DaySelecter.hpp:1:#ifndef __DAYSELECTER_HPP__
DaySelecter.hpp:2:#define __DAYSELECTER_HPP__
DaySelecter.hpp:10:namespace DaySelecter {
MainWindow.hpp:1:#ifndef __MAINWINDOW_HPP__
MainWindow.hpp:2:#define __MAINWINDOW_HPP__
MainWindow.hpp:4:#include
[...]На самом деле, оба параметра (и
-Path, и -Pattern) принимают массивы строк (которые в PowerShell задаются при помощи запятой).Но вы ведь помните, что PowerShell оперирует не текстом, а объектами? Давайте посмотрим, объекты какого рода выдаёт нам
Select-String:
$ Select-String DaySelector *.hpp,*.cpp | gm
TypeName: Microsoft.PowerShell.Commands.MatchInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
RelativePath Method string RelativePath(string directory)
ToString Method string ToString(), string ToString(string directory)
Context Property Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename Property System.String Filename {get;}
IgnoreCase Property System.Boolean IgnoreCase {get;set;}
Line Property System.String Line {get;set;}
LineNumber Property System.Int32 LineNumber {get;set;}
Matches Property System.Text.RegularExpressions.Match[] Matches {get;set;}
Path Property System.String Path {get;set;}
Pattern Property System.String Pattern {get;set;}
Как видите, мы не только можем посмотреть вывод команды, но и буквально разобрать его на части — выделить на какой строке и в каком файле произошло совпадение. Мы можем построить какой нам нужен вывод данных самостоятельно (например, в XML). По-моему, это очень увлекательно и удобно. То, чего часто не хватает в zsh.
Мне кажется, основная идея понятна;
Select-String предоставляет нам следующие возможности:- поиск по регулярным выражениям (поведение по-умолчанию),
- поиск по буквальному совпадению (переключатель
-Simple), - поиск только первого совпадения в файле, игнорируя все последующие (переключатель
-List), - или, наоборот, поиск всех совпадений, даже если в одной строке их несколько (переключатель
-AllMatches), - выполнять поиск строк, не совпадающих с шаблоном (переключатель
-NotMatch) — аналог ключа-vутилитыgrep, - кроме непосредственно совпавшей строки, выводить несколько предыдущих и следующих строк (аргумент
-Context) — очень похоже на то, как работает unified diff.
Что очень важно для нашего пользователя,
Select-String поддерживает возможность указания кодировки файла (параметр -Encoding). Но, увы, в силу неких причин список кодировок ограничен юникодными кодировками, а также ANSI (a.k.a. WINDOWS-1251 в наших ОС) и OEM (CP866, «досовская»). Почему не сделали более широкий выбор, до меня не доходит (дотнет же, широта возможностей, всё такое), хотя и этого набора вполне хватает в большинстве случаев. Посмотрим теперь, как искать файлы в дереве каталогов. В PowerShell для этого используется
Get-ChildItem, или ls (опять же, алиас). Я не знаю всех возможностей UNIX'ового ls, но наш коммандлет из PowerShell — довольно мощная штука. Мы можем получить список всех файлов, совпадающих с указанными шаблонами:$ ls -r -inc *.cpp,*.hppОбратите особое внимание — в аналогичных условиях в bash или zsh нам пришлось бы эскейпить звёздочки, т.к. они раскрываются самой оболочкой. В PowerShell несколько иной подход: шаблоны передаются непосредственно коммандлету и он уже при помощи внутреннего окружения PowerShell раскрывает их в нужных местах (я не знаком с внутренней кухней PowerShell, но принцип примерно таков). Это очень удобно для меня, т.к. в подобных случаях в Linux я часто получал нерабочие скрипты, и долго думал, из-за чего они не находят файлы. Но это, конечно, просто моя невнимательность.
Вернёмся к
ls. Попробуем найти файлы, которые не совпадают с шаблоном:$ ls -r -ex *.hpp~Мы можем сделать какой-нибудь сумашедший поиск, вроде поиска файлов, совпадающих с шаблоном номер один, и исключения из этого списка тех файлов, которые совпадают с шаблоном номер два:
$ ls -r -inc *.cpp,*.hpp -ex DaySelecter*Если вам нужно ещё больше контроля над поиском, используйте коммандлет
Where-Object — помните? PowerShell передаёт по пайпам объекты, а не текст:$ ls -r -inc *.cpp,*.hpp -ex DaySelecter* | ? { $_.IsReadOnly }Хорошо, мы увидели возможности и
ls, и Select-String. Как же нам теперь их объединить? Дело в том, что Select-String может получать список файлов как из командной строки (параметр -Path), так и из пайпа. Таким образом, мы можем просто объединить пайпом обе команды и получить нужный результат:$ ls -r -inc *.cpp,*.hpp -ex *DaySelecter* | Select-String DaySelecter
MainWindow.cpp:26: connect(viewDaySelecter->selectionModel(),
MainWindow.cpp:38: viewDaySelecter->setDiary(diaryModel);По-моему, довольно удобно!
