Pull to refresh

Замена для FIND и GREP

Reading time4 min
Views37K
Мне кажется, давно зреет тема сравнения возможностей PowerShell и оболочек мира UNIX. Сравнения не в холиварном смысле этого слова, а в позитивно-конструктивном. Линуксовым скриптописателям (не фанатикам), думаю, будет интересно узнать, как делаются те или иные штуки, которые они привыкли делать на bash или zsh, на PowerShell. Пожалуй, я и начну такую тему — и очень надеюсь, что кто-то из моих коллег-повершелловцев (Guderian, ApeCoder) также поддержит эту тему.

В 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);


По-моему, довольно удобно!
Tags:
Hubs:
Total votes 75: ↑44 and ↓31+13
Comments259

Articles