Как оболочка (например, bash) расширяет шаблоны подстановочных знаков?

9

Предположим, что каталог содержит 100 файлов, начинающихся с буквы «а».

Если я сделаю grep <some string> a*из терминала, как оболочка справится с этим?

Будет ли он расширять регулярное выражение, получать список всех файлов, начинающихся с a и grep для каждого из них последовательно? Или есть какой-то другой способ?

Предположим, что у меня есть массив вышеуказанных имен файлов, которые начинаются с «а». Будет ли это занимать больше / меньше времени, если я напишу цикл for и сам выполню итерацию в сценарии оболочки или в программе ac?

harithski
источник
7
Кстати, это globне регулярное выражение. Большая разница.
Аарон Д. Мараско

Ответы:

8

Во-первых, придирка: строка, как a*в обычном синтаксисе оболочки - это глоб, который работает не так, как регулярные выражения.

В высокоуровневом обзоре интерпретатор оболочки (т.е. bash) расширяет строку a*до списка каждого имени файла, соответствующего шаблону a*. Затем они становятся частью параметров командной строки для одного экземпляра grep(для программистов все расширенные слова идут как отдельные строки в argvаргумент main). Эта единственная grepкоманда затем анализирует аргументы любым способом, который она выберет, и она должна grepинтерпретировать эти аргументы как имена файлов, опции, аргументы опций, регулярные выражения и т. Д. И предпринимать соответствующие действия. Все происходит последовательно (AFAIK без grepреализации использует несколько потоков).

Если вы реализуете цикл в сценарии оболочки, чтобы сделать то же самое, он почти гарантированно будет медленнее, чем описанный выше процесс, по следующим причинам. Если вы создадите новый процесс grep для каждого файла, он наверняка будет медленнее из-за ненужного умножения затрат на создание процесса. Если вы сами создали список аргументов в сценарии оболочки и использовали один экземпляр grep, все, что вы делаете в оболочке, все равно будет медленнее, потому что команды оболочки должны интерпретироваться (посредством bash), что добавляет дополнительный слой кода, и вы просто заново реализуйте то, что bash уже делал быстрее внутри в скомпилированном коде.

Что касается написания этого самостоятельно на C, вы, вероятно, можете легко получить производительность, сопоставимую с процессом, описанным в первом абзаце, но вряд ли вам удастся добиться достаточного прироста производительности по сравнению с текущими реализациями grep / bash, чтобы оправдать время не тратя времени на оптимизацию производительности для конкретной машины или жертвуя переносимостью. Может быть, вы могли бы попытаться придумать произвольно распараллеливаемую версию grep, но даже это может не помочь, так как вы скорее связаны с вводом / выводом, чем с процессором. Расширение glob и grep уже «достаточно быстры» для большинства «обычных» целей.

jw013
источник
Спасибо за очень подробный ответ. На самом деле, мне нужно разархивировать сжатые файлы (по несколько ГБ каждый). У меня есть список этих файлов. Теперь у меня есть выбор: создать регулярное выражение (сложное), чтобы сопоставить эти файлы, или перебрать известный список и запустить grep для каждого из них (легко). Отсюда и беспокойство по поводу производительности.
Harithski
попробуйте zcatи zgrep; не нужно распаковывать их один за другим
jw013
Ну конечно; естественно. Я использую zgrep.
Харитски
6

Да, он расширится до списка файлов и передаст полученный список в grepпрограмму. По крайней мере, так man bashговорится в подразделе « Расширение пути» .

Существует другой способ использования расширения в простых случаях, как вы упомянули: написать grep <some_string> aи перед нажатием* нажать ESC. Это расширит список подходящих файлов прямо в командной строке, так что вы можете проверить, что список в порядке, прежде чем нажимать Enter.

Что касается второй части вашего вопроса, это зависит. Если вы хотите написать цикл for, который запускает grep для каждого из файлов по очереди, то это определенно будет медленнее, поскольку программа grep будет запускаться не один раз, а один раз для каждого файла. Однако, что это важно иметь в виду, что существует определенный предел на расширенной длины аргументов командной строки , которые можно использовать, хотя это , как правило , довольно высока. Чтобы увидеть это, вы можете попробовать grep adasdsadf /usr/*/*/* >/dev/null.

rozcietrzewiacz
источник
2
ESC+*это не то же самое, что позволить bash раскрыть *, потому что ESC+*будет вставлять точечные файлы (имена, начинающиеся с a .), тогда как расширение *зависит от dotglob shoptнастроек. Последовательность клавиш для раскрытия и вставки глобусов используется C-x *по умолчанию и соответствует команде readline glob-expand-word.
jw013
1
@ jw013 Спасибо за информацию! Кажется, это не меняет случая a*расширения, но, безусловно, важно в более широком контексте.
rozcietrzewiacz
2
zshпримечание: простое нажатие клавиши табуляции на расширяемых параметрах (шаблонах глобусов, раскладывании скобок, подстановках команд и т. д.) раскроет их.
Стефан Гименес
@ jw013 На самом деле я только что протестировал C-xярлык, и он не расширяет список файлов в моей системе (используя bash).
rozcietrzewiacz
1
@roz Правильно - я почти никогда не использую его, просто хотел указать на (довольно придирчивое) различие :). C-x *только глобусы, которые только делают имена файлов, но на Esc *самом деле делают намного больше, поскольку это так insert-completions, как и во всех возможных дополнениях. Это означает, что использование Esc *в пустой командной строке вставит имя каждого исполняемого файла $PATH, например, в ваш .
jw013