Мне кажется, давно зреет тема сравнения возможностей 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);
По-моему, довольно удобно!