Если вы пользуетесь терминалом macOS или оболочкой Linux, возможно, вы знакомы с простыми шаблонами с подстановочными знаками. Но большинство людей не знакомы с более продвинутыми шаблонами, которые занимают своего рода преисподнюю между простыми подстановочными знаками и их более сложными кузенами, регулярными выражениями. Из этой статьи вы узнаете об этих закономерностях и их скрытой силе.

В моей статье Думаете, вы понимаете подстановочные знаки? Подумайте еще раз , я исследовал скрытую сложность некоторых очевидных простых шаблонов подстановки, с которыми знакомы многие люди, показывая, что более глубокое понимание того, как оболочка интерпретирует подстановочные знаки, может помочь объяснить иногда сбивающее с толку поведение. Если вы не читали статью, я настоятельно рекомендую взглянуть на нее сейчас, чтобы убедиться, что вы понимаете, как работает расширение с подстановочными знаками, прежде чем продолжить.

В этой статье мы продвигаемся на шаг вперед в этом понимании и исследуем некоторые из менее используемых шаблонов подстановки - формально подстановки - шаблонов, доступных в оболочке Unix.

Вы уже должны быть знакомы с простыми шаблонами подстановочных знаков ? и *. Напомним, что ? соответствует любому одиночному символу, а * соответствует любому количеству символов (включая ноль). Когда оболочка видит любой из этих символов без кавычек и без экранирования в аргументе командной строки, она пытается расширить аргумент, интерпретируя его как путь и сопоставляя подстановочный знак со всеми возможными файлами в пути. Полученный набор путей к файлам затем отправляется целевой команде в виде списка аргументов.

Теперь давайте выйдем за рамки этого и рассмотрим некоторые из более сложных шаблонов.

Набор символов соответствия: […]

Квадратные скобки могут содержать набор символов или диапазон. Этот шаблон соответствует одному символу из указанного набора. Если вы, например, укажете [aeiou], тогда это будет соответствовать любой отдельной гласной.

Вот пример. Предположим, у нас есть эти файлы в текущем каталоге: -

$ ls
cat d0g dawg dg dig dog doug dug

Соответствует одиночному символу из набора _7 _: -

$ echo d[aeiou]g
dig dog dug

Помимо наборов символов, вы также можете указать непрерывные диапазоны, используя форму [start-end]. Диапазон является включительным, как показано ниже: -

$ echo d[a-o]g
dig dog

И скажем, у нас есть каталог, содержащий несколько пронумерованных файлов, например: -

$ ls
index.txt report.txt report1.txt report2.txt report3.txt report4.txt report5.txt

Поскольку числа - это просто символы, для однозначных диапазонов вы также можете сделать это: -

$ echo report[0-9].txt
report1.txt report2.txt report3.txt report4.txt report5.txt

Вы не можете сопоставить больше (или меньше) одного символа, используя запись в квадратных скобках. Чтобы сопоставить файлы с двумя цифрами, вам придется указать диапазон дважды. Это одно из ограничений этого шаблона. Подстановочные знаки далеко не так эффективны, как их более крупные собратья, регулярные выражения, поскольку они предназначены для сопоставления схожих имен файлов, а не для сопоставления с общим текстовым шаблоном.

Соответствует обратному набору символов: [^…] или [!…]

Тесно связанный с предыдущим шаблоном и не менее знакомый, если вы свободно владеете регулярными выражениями, является шаблон соответствия обратного набора символов.

Предположим, вы хотите увидеть все файлы, которые не начинаются с d? Вы можете легко сделать это, используя конструкцию обратного диапазона символов [!...] или [^...], которая соответствует любому символу кроме символов в наборе. Например, в справочнике собак: -

$ ls
cat d0g dawg dg dig dog doug dug

Чтобы найти совпадения формыd?g, но без гласной: -

$ echo d[^aeiou]g
d0g

В остальном обратная форма синтаксически идентична шаблону нормального диапазона. Например, в каталоге отчетов вы можете исключить такие диапазоны: -

$ echo report[!1–3].txt
report4.txt report5.txt

Обратите внимание, что ^ и ! семантически идентичны: они означают одно и то же.

С практической точки зрения, если вы хотите сопоставить все текстовые файлы, которые не заканчиваются числом, этот шаблон сделает свою работу: -

$ echo *[^1-9].txt
index.txt report.txt

Сила здесь проистекает из того факта, что * является «жадным» оператором и будет сопоставлять столько символов, сколько может, чтобы получить совпадение.

Расширение скобок: {…}

Расширение скобок также можно использовать для создания подстановочных знаков, но оно работает принципиально иначе, чем другие шаблоны, которые вы видели до сих пор.

На первый взгляд это похоже. Раскрытие скобок - также называемое чередованием - соответствует любому из заданных вами подшаблонов. Возьмем наш предыдущий пример соответствия гласных: -

$ echo d{a,e,i,u,o}g
dag deg dig dug dog

На первый взгляд это может показаться длинным способом использования диапазона символов, но посмотрите внимательно: в списке появятся два имени файла, которых нет в каталоге, dag и deg. Как так?

Чтобы прояснить ситуацию (надеюсь), запустите тот же шаблон с ls вместо _20 _: -

$ ls d{a,e,i,u,o}g
ls: dag: No such file or directory
ls: deg: No such file or directory
dig dog dug

Итак, ls правильно сообщил об отсутствии такого файла для dag и deg, но почему он вообще их учел? Ответ заключается в том, что расширение фигурных скобок работает иначе, чем обычные подстановочные знаки, в том смысле, что оболочка расширяет фигурные скобки даже до поиска файлов: она фактически генерирует все перестановки указанного вами шаблона, а затем выполняет раскрытие подстановочных знаков для результатов.

Это невероятно полезно для поиска нескольких вариантов (отсюда и другое его название чередование): -

$ echo {cat,dog}
cat dog

Все может немного усложниться, если обычные символы подстановки смешаны с чередованием. Что происходит, когда подстановочный знак * используется с расширением фигурных скобок?

$ echo {cat,d*}
cat dawg dg dig dog doug dug

Вы можете спросить, как echo в конечном итоге показало только действительные файлы, если расширение скобок происходит до поиска файлов? Почему он не показал бесконечные возможности, подразумеваемые d*? Ответ заключается в том, что * является обычным шаблоном подстановки, поэтому он был обработан после раскрытия фигурных скобок и, следовательно, применен к именам файлов в текущем каталоге, прежде чем он попал в команду echo. Это контрастирует с предыдущими примерами, в которых перечислены все перестановки раскрытия фигурных скобок.

Вот еще один полезный паттерн: -

$ echo d{a..z}g
dag dbg dcg ddg deg dfg dgg dhg dig djg dkg dlg dmg dng dog dpg dqg drg dsg dtg dug dvg dwg dxg dyg dzg

При использовании внутри таких фигурных скобок двойная точка является оператором диапазона, как и [a-z], с которым мы встречались ранее. Но поскольку это расширение скобок, все его перестановки расширяются оболочкой. Таким образом, шаблон {a..z} расширяется до полного алфавита в нижнем регистре, независимо от того, соответствует он каким-либо файлам или нет.

Одна вещь, которую это позволяет вам сделать, - это указать более интеллектуальные диапазоны и использовать их по-разному. Возвращаясь к нашему предыдущему каталогу отчетов, вы увидели потенциальные проблемы с созданием подстановочных знаков для файлов с более чем одной цифрой в имени. Напомним, что в каталоге были следующие файлы: -

$ ls
index.txt report.txt report1.txt report2.txt report3.txt report4.txt report5.txt

Допустим, вам нужно создать еще несколько файлов отчета, допустим, оставшееся число до 20. Используя фигурные скобки, легко сгенерировать текст: -

$ echo report{6..20}.txt
report6.txt report7.txt report8.txt report9.txt report10.txt report11.txt report12.txt report13.txt report14.txt report15.txt report16.txt report17.txt report18.txt report19.txt report20.txt

Поскольку при раскрытии фигурных скобок создается текст в соответствии с шаблоном, если вы отправите его вывод другой команде, скажем, touch, он будет рассматриваться как список аргументов, и touch создаст файлы с именами от report6.txt до report20.txt в каталоге: -

$ touch report{6..20}.txt

Теперь, если вы запустите ls, вы увидите, что файлы с report6.txt по report20.txt были созданы.

Вы также можете фильтровать с помощью ls, а числовые диапазоны, которые вы можете использовать, намного более гибкие, чем классы однозначных символов из предыдущих: -

$ ls report{8..12}.txt
report10.txt report11.txt report12.txt report8.txt report9.txt

Такой уровень гибкости невозможен при использовании простых шаблонов подстановки подстановочных знаков.

И, что лучше всего, теперь вы закончили работу с новыми файлами отчетов, вы можете удалить их так же легко: -

$ rm report{6..20}.txt
$ ls
index.txt report.txt report1.txt report2.txt report3.txt report4.txt report5.txt

Было бы разумно всегда запускать echo для раскрытия фигурных скобок, прежде чем передавать его результат деструктивной команде, такой как rm, просто как пробный запуск для проверки глупых ошибок!

Одно из распространенных применений раскрытия фигурных скобок - поиск нескольких вариантов (отсюда и другое название - чередование): -

$ echo {cat,dog}
cat dog

Все может немного усложниться, когда обычные подстановочные знаки смешиваются с чередованием: -

$ echo {cat,d*}
cat dawg dg dig dog doug dug

Вы можете спросить, как echo в конечном итоге показало только действительные файлы, если расширение скобок происходит до поиска файлов? Почему он не показал бесконечные возможности, подразумеваемые d*?

Ответ заключается в том, что, если вы посмотрите на возможные шаблоны, не существует возможных расширений скобок, которые производят бесконечные перестановки. В этом не было бы смысла, хотя бы потому, что это приведет к переполнению системной памяти. В приведенном выше примере показано расширение фигурных скобок, которое дает две перестановки: -

cat

а также

d*

Затем они обрабатываются оболочкой, а второй шаблон d* обрабатывается как обычный подстановочный знак, поэтому он затем сопоставляется с файлами в текущем рабочем каталоге. Процесс показан на рис.1.

Если расширение подстановочного знака не соответствует, список аргументов будет следующим: -

cat
d*

Опять же, это связано с тем, что расширение фигурных скобок всегда будет успешным, независимо от того, допустимы ли результирующие подстановочные знаки или нет.

Обычный вариант использования чередования - поиск файлов разных типов. Допустим, у вас есть следующие файлы: -

$ ls
family1.jpg family2.jpeg family4.mov holiday1.png holiday3.m4v
family1.png family3.jpeg holiday1.jpg holiday2.jpg holiday4.mov

Обратите внимание, что здесь много разных расширений файлов. Если вы хотите искать только файлы изображений, вы можете использовать чередование следующим образом: -

$ ls {*.jpg,*.jpeg,*.png}
family1.jpg family2.jpeg holiday1.jpg holiday2.jpg
family1.png family3.jpeg holiday1.png

Если бы вы пытались быть умным, вы могли бы использовать столь же верный (но более сложный для понимания) шаблон: -

$ ls *.{j{p,pe}g,png}
family1.jpg family2.jpeg holiday1.jpg holiday2.jpg
family1.png family3.jpeg holiday1.png

Это пример вложенного чередования: сначала оцениваются внутренние фигурные скобки {p,pe}, затем внешние фигурные скобки и, наконец, соответствие подстановочным знакам. Опять же, вы можете использовать эхо, чтобы проверить свой шаблон и перечислить результаты раскрытия фигурных скобок: -

$ echo {j{p,pe}g,png}
jpg jpeg png

Вы можете использовать этот вид вложенных шаблонов для создания довольно сложных шаблонов. В приведенном ниже примере относительно краткий и читаемый шаблон генерирует совпадения для различных расширений файлов мультимедиа.

$ echo .{mp{3..4},m4{a,b,p,v}}
.mp3 .mp4 .m4a .m4b .m4p .m4v

Подстановочные знаки и регулярные выражения

Читатели, знакомые с регулярными выражениями, не могут не заметить некоторые заметные сходства между синтаксисом шаблонов подстановочных знаков и регулярных выражений.

Подстановочные знаки изначально назывались шаблонами подстановки, и их происхождение восходит к самой ранней версии Unix в 1971 году. Тогда оболочка не расширяла шаблоны подстановки, а скорее выполняла свою работу команда под названием glob. В то время были распознаны только образцы * и ?.

Наиболее очевидным является простой образец звездочки *, который имеет схожее поведение в обоих синтаксисах. Формально он известен как звезда Клини в честь математика Стивена Клини, который придумал эту концепцию еще в 1950-х годах. В регулярных выражениях a* означает «совпадение a ноль или более раз», но в подстановочном знаке это означает «совпадение любого символа ноль или более раз». Кажется, что значение подстановочного знака должно быть вдохновлено регулярным выражением.

Аналогичным образом, обозначения набора [abc...] квадратных скобок и диапазона [a-z] символов даже больше похожи на их аналоги в регулярных выражениях. Даже использование каретки ^ в качестве символа инверсии в шаблоне совпадения инвертированных символов [^...] кажется взятым непосредственно из регулярных выражений.

? имеет совершенно другое значение между синтаксисом подстановочных знаков и регулярных выражений, а символы { и } также означают нечто совершенно иное.

Подобно передаче сходства между некоторыми человеческими языками, вы знаете, что некоторые синтаксисы подстановочных знаков и регулярных выражений должны иметь общее происхождение, но это скорее любопытство, имеющее небольшую практическую ценность.

Этот пост адаптирован из серии статей о командной строке macOS для будущей электронной книги Ли Даутуэйта. Будьте в курсе, посетив мой сайт.