Глобы и символы подстановки
Глобы — это шаблоны, которые оболочка раскрывает в имена файлов до того, как команда их видит. * соответствует любой строке, ? — одному символу, [...] — набору, а {a,b} генерирует альтернативы.
Тебе нужно удалить все .log-файлы в каталоге или перечислить все .ts-файлы, начинающиеся с test. Вводить каждое имя вручную невозможно. Оболочка предоставляет glob-шаблоны — миниатюрный язык сопоставления, который раскрывается в реальные имена файлов до того, как команда запустится. Неправильное понимание порядка этого раскрытия — главный источник путаницы с глобами.
К концу урока ты будешь знать четыре метасимвола glob, как оболочка их раскрывает и где они тихо ничего не делают.
После этого урока ты сможешь писать glob-шаблоны с *, ?, [...] и {...}, объяснять, что оболочка раскрывает глобы до передачи аргументов команде, и избегать классического подводного камня «нераскавыченный glob передан не тому инструменту».
Оболочка раскрывает глобы, а не команда. Это самый важный факт о глобинге. Когда ты вводишь:
ls *.logОболочка смотрит в текущий каталог, находит каждый файл, имя которого оканчивается на .log, и заменяет *.log этим списком до вызова ls. Программа ls никогда не видит звёздочку — она получает список реальных имён файлов как отдельные аргументы.
Следствие: если ни один файл не совпадает, bash (по умолчанию) передаёт буквальную строку *.log команде, что обычно вызывает ошибку. Установи nullglob, чтобы получить пустой список (рассмотрено во вставке).
* соответствует любой строке символов (включая пустую). Не соответствует ведущей точке (скрытые файлы) и /.
ls *.txt # все .txt файлы в текущем каталоге
ls report_*.csv # report_2024.csv, report_final.csv, …
ls * # все нескрытые файлы в текущем каталогеДва * подряд дают **, который при включённом globstar в bash соответствует через границы каталогов. Без globstar ** ведёт себя как одиночный *.
? соответствует ровно одному символу. Полезно, когда длина фиксирована:
ls file?.txt # file1.txt, fileA.txt — НЕ file10.txt
ls ???.sh # любой .sh файл ровно с 3-символьным базовым именемКаждый ? потребляет ровно один символ. file?.txt не соответствует file.txt (ноль символов перед .txt) или file10.txt (два символа).
[...] соответствует одному символу из набора или диапазона.
ls file[123].txt # file1.txt, file2.txt, file3.txt
ls [a-z]*.sh # любой .sh файл, начинающийся со строчной буквы
ls [!0-9]* # файлы, НЕ начинающиеся с цифры (! инвертирует)Диапазоны используют порядок ASCII. [a-z] — строчные буквы; [A-Z] — прописные; [0-9] — цифры. Смешивание может удивить в не-C локалях — используй явные списки или POSIX-классы типа [[:alpha:]] при сомнениях.
{a,b,…} — раскрытие фигурных скобок, генерирует альтернативы. В отличие от других глобов, раскрытие скобок не требует наличия файлов; оно генерирует строки безусловно:
echo file{1,2,3}.txt # file1.txt file2.txt file3.txt
cp config{,.bak} # копирует config в config.bak (удобный трюк для резервной копии)
mkdir -p src/{en,ru}/cli # создаёт два каталога одной командойРаскрытие скобок происходит до раскрытия глобов. Сначала оболочка раскрывает {a,b} в две строки, затем проверяет каждую против файловой системы.
Очистка артефактов сборки.
Твой каталог проекта содержит:
dist/app.js dist/app.js.map dist/app.css dist/app.css.map dist/README.mdЦель: удалить все .map-файлы и создать резервную копию оригиналов .js и .css.
# Удалить map-файлы
rm dist/*.mapПосле раскрытия глоба оболочка выполнит: rm dist/app.js.map dist/app.css.map
# Резервная копия оригиналов через раскрытие скобок
cp dist/app{.js,.css} /tmp/backup/Скобки раскроются в: cp dist/app.js dist/app.css /tmp/backup/ — два файла, одна команда.
# Проверяем, что осталось
ls dist/app.js app.css README.md▸Почему это работает
Bash nullglob и failglob. По умолчанию glob без совпадений передаётся буквально команде (ls *.xyz → ls: cannot access '*.xyz'). Включи nullglob командой shopt -s nullglob, чтобы несовпадающие глобы раскрывались в пустоту (полезно в скриптах). Включи failglob через shopt -s failglob, чтобы несовпавший glob сразу был ошибкой. Для интерактивной работы поведение по умолчанию обычно нормально; в скриптах всегда выбирай один из вариантов.
На macOS/BSD zsh (оболочка по умолчанию с macOS Catalina) ведёт себя как nullglob по умолчанию через настройку NO_MATCH. Bash на macOS следует тому же POSIX-умолчанию, что и Linux bash.
▸Частая ошибка
Взять glob в кавычки, чтобы передать его команде, которая сама делает сопоставление. find и grep принимают свои шаблоны, но оболочка сначала раскроет нераскавыченные глобы. Эта команда почти всегда неверна:
find . -name *.log # НЕВЕРНО: оболочка может сначала раскрыть *.log
find . -name "*.log" # ВЕРНО: оболочка передаёт буквальное *.log в findЕсли в текущем каталоге есть .log-файлы, первый вариант может случайно сработать. Если их ровно один — find получит имя файла, а не шаблон. Всегда бери в кавычки шаблоны, передаваемые командам, которые интерпретируют их самостоятельно.
Ты запускаешь: rm *.tmp. В каталоге нет .tmp-файлов. Что произойдёт по умолчанию в bash?
Glob-шаблоны раскрываются оболочкой, а не следующей командой. Четыре метасимвола: * (любая строка), ? (один символ), [...] (один символ из набора) и {a,b} (раскрытие скобок — генерирует альтернативы безусловно, до сопоставления с файловой системой). Порядок раскрытия: сначала скобки, затем глобы файловой системы. Всегда бери в кавычки шаблоны, которые передаёшь командам типа find или grep, делающим собственное сопоставление, — иначе оболочка раскроет их первой, и команда получит имена файлов вместо шаблонов.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.