Поиск команд с which и type
which, type и command -v находят команды, обыскивая $PATH и собственную таблицу поиска оболочки. type также раскрывает, является ли имя встроенной командой, функцией или псевдонимом — which находит только внешние исполняемые файлы.
Ты устанавливаешь новую версию Python, запускаешь python3 --version — и он всё равно сообщает старую версию. Почему? Потому что оболочка находит другой python3 раньше в $PATH. Понимание того, как оболочка находит команды — и инструменты, раскрывающие этот поиск — необходимо для отладки проблем окружения, управления несколькими версиями инструментов и написания надёжных скриптов.
К концу урока ты будешь знать, как $PATH управляет поиском команд, и чем which, type и command -v по-разному освещают этот процесс.
После этого урока ты сможешь объяснить, как оболочка использует $PATH для поиска исполняемых файлов, использовать which для поиска пути внешней команды, использовать type для различия встроенных команд, функций, псевдонимов и внешних команд, и использовать command -v как переносимую альтернативу which в скриптах.
$PATH — это список каталогов, разделённых двоеточиями. Когда ты вводишь имя команды без пути, оболочка обыскивает каждый каталог в $PATH слева направо и запускает первое найденное совпадение:
echo $PATHТипичный вывод:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbinОболочка проверяет /usr/local/bin первым. Если там есть совпадающий исполняемый файл — он используется, всё остальное в списке игнорируется. Именно поэтому pyenv и nvm добавляют свои каталоги-прокладки в начало $PATH: они перехватывают поиск до системного умолчания.
which находит путь к внешнему исполняемому файлу. Он обыскивает $PATH по порядку и выводит первое совпадение:
which python3Вывод:
/usr/bin/python3which node/home/alice/.nvm/versions/node/v20.0.0/bin/nodewhich смотрит только в файловую систему — он не знает о встроенных командах оболочки, функциях или псевдонимах. Если ввести which cd, он выведет пустоту или неверный результат, потому что cd — встроенная команда оболочки и не имеет пути на диске.
type раскрывает полную правду об имени. type — встроенная команда bash, которая обращается к собственной таблице поиска оболочки: встроенные команды, функции, псевдонимы, а затем $PATH:
type cdcd is a shell builtintype lsls is aliased to 'ls --color=auto'type python3python3 is /usr/bin/python3type llll is a function
ll ()
{
ls -alF
}Используй type -a имя, чтобы увидеть все совпадения по порядку поиска, а не только первое:
type -a python3python3 is /usr/local/bin/python3
python3 is /usr/bin/python3Это раскрывает, что /usr/local/bin/python3 затеняет /usr/bin/python3.
command -v — POSIX-переносимая альтернатива для скриптов. which не стандартизирован POSIX и ведёт себя по-разному на разных системах. В скриптах оболочки вместо него используй command -v:
command -v git/usr/bin/gitВ скрипте идиоматичный шаблон для проверки доступности команды:
if ! command -v docker &>/dev/null; then
echo "docker не установлен" >&2
exit 1
ficommand -v возвращает код выхода 0, если команда найдена, и ненулевой — иначе. Он также распознаёт встроенные команды и функции. Псевдонимы он не выводит — это намеренно для целей написания скриптов.
Просмотр и изменение $PATH. Чтобы добавить каталог в $PATH для текущей сессии:
export PATH="/opt/myapp/bin:$PATH"Новый каталог добавляется в начало, поэтому ищется первым. Чтобы сделать это постоянным, добавь ту же строку в ~/.bashrc (bash) или ~/.zshrc (zsh).
Чтобы увидеть, откуда загружается команда, и диагностировать конфликты версий:
type -a node # все исполняемые node в порядке PATH
which -a node # GNU which -a: то же, только для внешних файловДиагностика неверной версии Python.
После установки Python 3.12 через pyenv команда python3 --version всё равно сообщает 3.10. Расследование:
which python3/usr/bin/python3Побеждает системный Python. Проверяем, что pyenv добавил в $PATH:
echo $PATH | tr ':' '\n' | head -5/home/alice/.pyenv/shims
/home/alice/.pyenv/bin
/usr/local/bin
/usr/bin
/binПрокладки pyenv там есть. Но which нашёл /usr/bin/python3 — значит прокладка pyenv для python3 не создана:
type -a python3python3 is /usr/bin/python3Только одна запись. Прокладка отсутствует, потому что pyenv нужно указать, какую версию использовать:
pyenv global 3.12.0
type -a python3python3 is /home/alice/.pyenv/shims/python3
python3 is /usr/bin/python3Теперь прокладка стоит первой. python3 --version сообщает 3.12.0.
▸Почему это работает
which на macOS против Linux. На macOS which — это /usr/bin/which и по умолчанию не поддерживает -a (некоторые версии поддерживают, некоторые нет). На Linux с GNU which флаг -a перечисляет все совпадения. В zsh (оболочка macOS по умолчанию) which — встроенная команда, которая показывает псевдонимы и функции — ведя себя как type. Эта непоследовательность — причина, почему command -v предпочтительнее в переносимых скриптах: он определён POSIX, доступен в каждой POSIX-оболочке и ведёт себя согласованно.
▸Частая ошибка
Использование which для проверки наличия команды в скриптах. which на некоторых системах возвращает 0 даже когда ничего не выводит. Правильная, переносимая идиома:
# НЕВЕРНО — ненадёжно на разных системах
if which mycommand; then …
# ВЕРНО
if command -v mycommand &>/dev/null; then …&>/dev/null подавляет и stdout, и stderr, чтобы проверка была тихой. command -v надёжно возвращает ненулевой код, когда команда не найдена.
Ты запускаешь: which cd. Вывод пустой. Почему?
Оболочка разрешает имя команды по порядку: псевдонимы → функции → встроенные команды → $PATH. which ищет только внешние исполняемые файлы в $PATH и ничего не знает о встроенных командах, функциях или псевдонимах. type (встроенная команда bash) показывает полную картину — к какой категории относится имя и его значение; type -a перечисляет все совпадения по порядку. command -v — POSIX-переносимый эквивалент для скриптов: возвращает 0, если команда найдена (любым способом), и ненулевой код — иначе. Когда команда разрешается в неверную версию, type -a и echo $PATH вместе раскрывают, какой каталог выигрывает поиск.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.