Команда `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` повертає лише шлях. Щоб отримати версію, передайте результат через pipe: `$(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, щоб побачити, яка реалізація активна.
