Команда `which` в Linux: Полное техническое руководство с примерами
Команда `which` в Linux находит абсолютный путь исполняемого файла, сканируя каталоги, перечисленные в переменной окружения `PATH`, и возвращая первое совпадение. Это утилита, близкая к стандарту POSIX, которую системные администраторы, разработчики и DevOps-инженеры используют ежедневно для проверки расположения бинарных файлов, аудита сред выполнения и отладки конфликтов, связанных с PATH.
Когда вы запускаете `which python3`, оболочка не выполняет поиск по всей файловой системе — она проходит только по разделённому двоеточиями списку каталогов, хранящихся в `$PATH`, слева направо, и останавливается на первом совпадении. Это поведение является одновременно её главным преимуществом и наиболее важным ограничением, которое необходимо понимать.
Базовый синтаксис
“`bash
which [options] command_name [command_name …]
“`
- `[options]` — Необязательные флаги, изменяющие поведение вывода (подробно рассмотрены ниже).
- `command_name` — Одно или несколько имён исполняемых файлов, которые нужно найти.
Как работает `which` изнутри
При вызове `which` команда считывает текущее значение переменной окружения `PATH`, разбивает его по разделителям `:` и последовательно перебирает каждый каталог. Для каждого каталога она проверяет, существует ли файл с именем команды и установлен ли у него бит исполнения (права `x`). Первое совпадение выводится на стандартный вывод.
Это означает, что `which` полностью зависит от текущего состояния `$PATH`. Если ваш `PATH` настроен неверно — например, пользовательский каталог стоит после `/usr/bin` вместо того, чтобы стоять перед ним — `which` точно отразит эту неправильную конфигурацию, что и делает его полезным для отладки.
Чтобы просмотреть текущий `PATH`:
“`bash
echo $PATH
Example output:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
“`
Основные варианты использования и примеры
Пример 1: Поиск одного исполняемого файла
Наиболее базовое применение — найти, где находится бинарный файл:
“`bash
which python3
“`
“`
/usr/bin/python3
“`
Это подтверждает, что при вводе `python3` система выполняет `/usr/bin/python3`. Если вы скомпилировали собственную сборку и поместили её в `/opt/python3.12/bin/`, но этот каталог не включён в `PATH`, `which` её не найдёт.
Пример 2: Запрос нескольких команд за один проход
Можно передать несколько имён команд в одном вызове — это удобно при аудите среды сборки:
“`bash
which python3 gcc git curl wget
“`
“`
/usr/bin/python3
/usr/bin/gcc
/usr/bin/git
/usr/bin/curl
/usr/bin/usr/bin/wget
“`
Это особенно полезно в скриптах валидации CI/CD-пайплайнов, где необходимо убедиться в наличии всех необходимых инструментов перед началом сборки.
Пример 3: Поиск всех экземпляров с помощью `-a`
Флаг `-a` указывает `which` продолжать поиск после первого совпадения и сообщать обо всех найденных экземплярах во всех каталогах `PATH`:
“`bash
which -a python3
“`
“`
/usr/bin/python3
/usr/local/bin/python3
“`
Это критически важно в средах, где установлено несколько версий Python — например, системный Python по пути `/usr/bin/python3` и версия, управляемая pyenv, по пути `/usr/local/bin/python3`. Бинарный файл, который стоит первым в `PATH`, и будет выполнен. Если активна неверная версия, этот вывод точно укажет, где возникает конфликт.
Реальный граничный случай: На серверах, где одновременно установлены Node.js из дистрибутива и Node.js, управляемый nvm, `which -a node` нередко выявляет два или три конфликтующих пути. Для решения этой проблемы требуется изменить порядок записей `PATH` в `.bashrc` или `.zshrc`, а не переустанавливать программное обеспечение.
Пример 4: Поведение при разрешении псевдонимов
Поведение `which` с псевдонимами во многом зависит от оболочки и конкретной реализации `which`, установленной в системе.
Во многих дистрибутивах Linux `which` является самостоятельным внешним бинарным файлом (не встроенной командой оболочки), поэтому у него нет доступа к таблице псевдонимов текущей оболочки. Однако в системах, где `which` реализован как функция или псевдоним оболочки (что характерно для конфигураций zsh), он может разрешать псевдонимы:
“`bash
alias ls='ls –color=auto'
which ls
“`
В системе zsh с функциональным `which`:
“`
ls: aliased to ls –color=auto
“`
В системе bash с внешним бинарным файлом `which`:
“`
/bin/ls
“`
Эта непоследовательность является широко известным источником путаницы и одной из основных причин, по которым опытные администраторы предпочитают использовать `type` или `command -v` в скриптах (рассмотрено ниже).
Пример 5: Использование `which` в условной логике скриптов
Распространённый паттерн в скриптах оболочки — использование `which` для проверки зависимости перед продолжением выполнения:
“`bash
if ! which docker > /dev/null 2>&1; then
echo "Docker is not installed or not in PATH. Aborting."
exit 1
fi
“`
Однако более переносимым и POSIX-совместимым подходом для скриптов является `command -v`:
“`bash
if ! command -v docker > /dev/null 2>&1; then
echo "Docker not found."
exit 1
fi
“`
Это различие важно при написании скриптов, предназначенных для работы в нескольких дистрибутивах или оболочках.
Техническое сравнение `which`, `type` и `command -v`
Эти три инструмента решают пересекающиеся, но различные задачи. Выбор неподходящего инструмента приводит к трудноуловимым ошибкам, особенно в скриптах оболочки.
| Функция | `which` | `type` | `command -v` |
|---|
| — | — | — | — |
|---|
| Поиск внешних бинарных файлов | Да | Да | Да |
|---|
| Разрешение псевдонимов оболочки | Зависит от реализации | Да (всегда) | Да (всегда) |
|---|
| Разрешение функций оболочки | Нет | Да | Да |
|---|
| Определение встроенных команд оболочки | Нет | Да | Да |
|---|
| Соответствие стандарту POSIX | Нет | Да | Да |
|---|
| Надёжная работа в скриптах | Рискованно | Рискованно (встроенная команда bash) | Рекомендуется |
|---|
| Формат вывода | Только путь | Описательная строка | Путь или определение |
|---|
| Поиск по всем записям PATH (эквивалент `-a`) | Да (с `-a`) | Да (с `-a`) | Нет |
|---|
| Внешний бинарный файл (не встроенная команда) | Да | Нет (встроенная команда) | Нет (встроенная команда) |
|---|
Практические рекомендации:
- Используйте `which` в интерактивном режиме в терминале, когда нужно быстро найти путь.
- Используйте `type -a`, когда хотите увидеть все формы команды (псевдоним, функция, встроенная команда и бинарный файл).
- Используйте `command -v` в производственных скриптах оболочки для обеспечения переносимости POSIX.
`type` в действии
“`bash
type -a python3
“`
“`
python3 is /usr/bin/python3
python3 is /usr/local/bin/python3
“`
“`bash
type ls
“`
“`
ls is aliased to `ls –color=auto'
“`
`command -v` в действии
“`bash
command -v git
“`
“`
/usr/bin/git
“`
“`bash
command -v ll
“`
“`
ll: aliased to ls -alF
“`
Практические сценарии отладки
Отладка неверной версии Python
Разработчик сообщает, что `python3 –version` возвращает `3.9.x`, хотя он установил `3.11` через собственную сборку. Последовательность диагностики:
“`bash
which python3 # Shows the first match
which -a python3 # Shows all matches
echo $PATH # Reveals directory ordering
ls -la /usr/local/bin/python3 # Checks if the custom build is symlinked correctly
“`
Решение почти всегда сводится либо к отсутствующей символической ссылке, либо к проблеме порядка записей `PATH` в файле инициализации оболочки.
Диагностика отсутствующей команды после установки
Если `which curl` не возвращает никакого вывода, бинарный файл либо не установлен, либо установлен в каталог, не входящий в `PATH`. Чтобы различить эти случаи:
“`bash
which curl # No output = not in PATH
find /usr -name curl -type f 2>/dev/null # Search for the binary outside PATH
apt list –installed 2>/dev/null | grep curl # Check package manager
“`
Проверка путей к инструментам перед развёртыванием
При настройке нового окружения VPS Хостинга стандартный чек-лист перед развёртыванием должен включать запуск `which -a` для каждого критически важного бинарного файла, от которого зависит ваше приложение. Это позволяет выявить расхождения в окружении между разработкой, тестированием и производством до возникновения сбоев во время выполнения.
Известные ограничения `which`
Понимание этих ограничений предотвращает ошибочную диагностику в сложных средах:
- Область видимости только `PATH`: `which` не видит ни одного исполняемого файла, недоступного через `$PATH`. Инструменты, установленные в пользовательских локальных каталогах, например `~/.local/bin`, будут найдены только в том случае, если этот каталог включён в `PATH`.
- Отсутствие осведомлённости о встроенных командах оболочки: Команды `cd`, `echo`, `alias` и `source` являются встроенными командами оболочки. `which cd` вернёт пустой результат или путь к внешнему бинарному файлу `cd`, который редко используется, что даст вводящий в заблуждение результат.
- Таблицы псевдонимов, специфичные для оболочки: `which` как внешний бинарный файл не может читать таблицу псевдонимов вызывающей оболочки. Это делает его ненадёжным для интроспекции псевдонимов в bash.
- Прозрачность символических ссылок: `which` сообщает путь к символической ссылке, а не к разрешённой цели. Если `/usr/bin/python3` является символической ссылкой на `/usr/bin/python3.11`, `which python3` покажет `/usr/bin/python3`. Используйте `readlink -f $(which python3)` для разрешения полной цепочки.
- Контекст `sudo`: Запуск команды с `sudo` использует `PATH` пользователя root, который может существенно отличаться от `PATH` обычного пользователя. `which node` от имени обычного пользователя может вернуть другой путь, чем `sudo which node`.
Продвинутые паттерны
Разрешение полной цепочки символических ссылок
“`bash
readlink -f $(which python3)
Output: /usr/bin/python3.11
“`
Проверка прав на выполнение вместе с путём
“`bash
ls -la $(which nginx)
Output: -rwxr-xr-x 1 root root 1234567 Jan 10 2024 /usr/sbin/nginx
“`
Совместное использование с `xargs` для пакетной проверки
“`bash
echo "python3 gcc git" | xargs -n1 which
“`
Использование в скриптах валидации окружения
На Выделенном сервере, на котором работает сложный стек приложений, скрипт валидации при запуске может выглядеть следующим образом:
“`bash
#!/bin/bash
REQUIRED_BINS="nginx php-fpm mysql redis-cli composer"
MISSING=0
for bin in $REQUIRED_BINS; do
if ! command -v "$bin" > /dev/null 2>&1; then
echo "MISSING: $bin"
MISSING=$((MISSING + 1))
else
echo "OK: $bin -> $(which $bin)"
fi
done
[ "$MISSING" -gt 0 ] && exit 1
exit 0
“`
Примечания о поведении в различных оболочках
Поведение `which` не одинаково во всех средах Linux:
- Bash: `which` обычно является внешним бинарным файлом (`/usr/bin/which`). Он не видит псевдонимы или функции bash, если только они не экспортированы.
- Zsh: Многие конфигурации zsh поставляют `which` как встроенную функцию оболочки, которая разрешает псевдонимы и функции, что делает её вывод более информативным, но и отличным от поведения в bash.
- Fish shell: Fish имеет собственный встроенный эквивалент `which`, а её система псевдонимов (называемых `functions`) обрабатывается иначе.
- Alpine Linux / BusyBox-окружения: Утилита `which` предоставляется BusyBox и может иметь урезанный набор функций по сравнению с пакетом GNU `which`.
Эта вариативность особенно актуальна при управлении контейнеризованными приложениями или настройке Панелей управления VPS, где базовая оболочка может отличаться от вашей локальной среды разработки.
Соображения безопасности
В средах с повышенными требованиями к безопасности `which` можно использовать как лёгкий инструмент аудита:
- Убедитесь, что привилегированные бинарные файлы, такие как `sudo`, `su` или `passwd`, разрешаются в ожидаемые системные пути, а не в каталоги, доступные для записи пользователем и стоящие раньше в `PATH`.
- Обнаруживайте попытки перехвата PATH: если `which ls` возвращает `/home/user/bin/ls` вместо `/bin/ls`, возможно, был внедрён вредоносный бинарный файл.
“`bash
Audit critical system binaries
for cmd in sudo su passwd ssh scp; do
echo "$cmd -> $(which $cmd)"
done
“`
Это стандартный шаг при усилении защиты сервера, который будет обслуживать SSL-сертификаты или выполнять обработку конфиденциального TLS-трафика, где целостность бинарных файлов не подлежит обсуждению.
При управлении средами Виртуального веб-хостинга с несколькими пользователями важным средством контроля безопасности является проверка того, что каталоги, доступные для записи пользователем, не стоят перед системными каталогами в `PATH` ни одного из пользователей.
Матрица решений: когда какой инструмент использовать
| Сценарий | Рекомендуемый инструмент |
|---|
| — | — |
|---|
| Быстрый интерактивный поиск пути | `which` |
|---|
| Скрипт: проверка наличия команды | `command -v` |
|---|
| Определение, является ли команда псевдонимом или функцией | `type` |
|---|
| Поиск всех экземпляров в PATH | `which -a` или `type -a` |
|---|
| Разрешение символических ссылок до конечного бинарного файла | `readlink -f $(which …)` |
|---|
| Аудит на предмет перехвата PATH | `which` + ручная проверка PATH |
|---|
| Переносимые скрипты для разных оболочек | `command -v` |
|---|
Технические ключевые выводы
- `which` выполняет поиск в `$PATH` слева направо и возвращает первый найденный исполняемый файл — порядок записей `PATH` напрямую определяет, какой бинарный файл будет запущен.
- Флаг `-a` необходим, когда сосуществуют несколько версий инструмента; никогда не предполагайте, что существует только один экземпляр, не проверив это.
- Не используйте `which` в производственных скриптах оболочки — используйте `command -v` для соответствия стандарту POSIX и согласованного поведения в bash, dash и zsh.
- `which` не может видеть встроенные команды оболочки, функции или псевдонимы, определённые в текущем сеансе оболочки, когда работает как внешний бинарный файл.
- Всегда дополняйте результат `which` командой `readlink -f` при наличии символических ссылок, чтобы определить фактически выполняемый бинарный файл.
- В многопользовательских или контейнеризованных средах `PATH` различается между пользователями и между контекстами `sudo` и не-`sudo` — всегда проверяйте в правильном контексте.
- Перехват PATH через каталоги, доступные для записи пользователем и добавленные в начало `$PATH`, является реальным вектором атаки; `which` — быстрый инструмент первичного аудита против него.
Часто задаваемые вопросы
В чём разница между `which` и `whereis`?
`which` выполняет поиск исполняемых файлов только в `$PATH`. `whereis` одновременно выполняет поиск в более широком наборе предопределённых системных каталогов для нахождения бинарного файла, его страницы руководства и исходных файлов. Используйте `whereis`, когда вам нужно найти документацию или исходный код вместе с бинарным файлом.
Почему `which cd` ничего не возвращает?
`cd` является встроенной командой оболочки, а не внешним исполняемым файлом. Поскольку `which` сканирует только `$PATH` в поисках файлов с правом на выполнение, он не может найти встроенные команды. Вместо этого используйте `type cd`, который корректно сообщит `cd is a shell builtin`.
Может ли `which` сообщить мне, какая версия программы установлена?
Нет. `which` возвращает только путь. Чтобы получить версию, передайте результат по конвейеру: `$(which python3) –version` или просто `python3 –version`. Путь от `which` помогает убедиться, что вы запрашиваете нужный бинарный файл.
Почему `which python3` возвращает другой результат при использовании `sudo`?
`sudo` выполняет команды в окружении root, включая `PATH` пользователя root, который обычно более ограничен, чем `PATH` обычного пользователя. Каталоги вроде `~/.local/bin` или пути nvm/pyenv, добавленные в `.bashrc` пользователя, отсутствуют в `PATH` пользователя root. При отладке выполнения с повышенными привилегиями всегда проверяйте с помощью `sudo which python3` отдельно.
Доступна ли `which` на macOS?
Да, macOS включает `which` как часть своего пользовательского пространства, производного от BSD. Однако версия macOS не поддерживает флаг `-a` во всех более старых версиях. На современных macOS с Homebrew у вас может быть установлен GNU `which` наряду с системной версией. Используйте `type -a which` на macOS, чтобы увидеть, какая реализация активна.
