- Вопрос или проблема
- Резюме
- Сценарии использования
- История
- Реализации
- Статус на сегодня
- Рекомендации
- Получение пути к исполняемому файлу
- Определение, существует ли команда
- Где можно использовать which
- (t)csh
- Найти команды только в некоторых оболочках
- Когда вам действительно нужна внешняя команда
- Большинство систем, большинство оболочек, подобных Bourne: функции
- Большинство систем, большинство оболочек, подобных Bourne: встроенные команды
- Solaris 10, AIX 7.1, HP/UX 11i, Tru64 5.1 и многие другие:
- CentOS 6.4, bash
- Debian 7.0, ksh93:
- bash, любая система:
- Теперь случай, когда which и иногда type дают сбой:
- Почему вы хотите результат which с самого начала?
- Он не универсален
- Формат его вывода не уточнен
- Ответ может устареть
- Ответ или решение
- Почему не следует использовать which и что вместо этого использовать
- Введение
- Проблемы с which
- Рекомендуемые альтернативы
- Заключение
Вопрос или проблема
Когда вы ищете путь к исполняемому файлу или проверяете, что произойдет, если вы введете имя команды в оболочке Unix, существует множество различных утилит (which
, type
, command
, whence
, where
, whereis
, whatis
, hash
и т. д.).
Мы часто слышим, что which
следует избегать. Почему? Что мы можем использовать вместо этого?
Вот вся информация, о которой вы никогда не думали, что не захотите знать:
Резюме
Чтобы получить путь к исполняемому файлу в сценарии оболочки, подобной Bourne (есть несколько оговорок; см. ниже):
ls_path=$(command -v ls)
Чтобы узнать, существует ли указанная команда:
if command -v given-command > /dev/null; then
echo given-command доступна
else
echo given-command не доступна
fi
В командной строке интерактивной оболочки, подобной Bourne:
type ls
Команда which
является сломанным наследием от C-Shell и лучше оставить в покое в оболочках, подобных Bourne.
Сценарии использования
Существует различие между поиском этой информации как частью сценария или интерактивно в командной строке.
В командной строке, типичный сценарий использования: эта команда ведет себя странно, использую ли я правильную? Что на самом деле произошло, когда я набрал mycmd
? Могу ли я узнать больше о том, что это?
В этом случае вы хотите знать, что ваша оболочка делает, когда вы вызываете команду, не вызывая саму команду.
В сценариях оболочки, это, как правило, совершенно другое. В сценарии оболочки нет причины, по которой вы хотите знать, где или что представляет собой команда, если все, что вам нужно сделать, это запустить ее. В общем, то, что вы хотите знать, это путь к исполняемому файлу, чтобы получить больше информации о нем (например, путь к другому файлу относительно этого, или прочитать информацию из содержимого исполняемого файла по этому пути).
Интерактивно вы можете захотеть узнать о всех командах my-cmd
, доступных в системе, в сценариях это редко требуется.
Большинство доступных инструментов (как это часто бывает) были разработаны для интерактивного использования.
История
Сначала немного истории.
Ранние Unix оболочки до конца 70-х не имели функций или псевдонимов. Только традиционный поиск исполняемых файлов в $PATH
. csh
ввела псевдонимы около 1978 года (хотя csh
была впервые выпущена в 2BSD
, в мае 1979 года) и также обработку .cshrc
для пользователей, чтобы настроить оболочку (каждая оболочка, такая как csh
, читает .cshrc
, даже когда она не интерактивная, например, в сценариях).
Хотя оболочка Bourne была впервые выпущена в Unix V7 ранее в 1979 году, поддержка функций была добавлена гораздо позже (1984 году в SVR2), и у нее никогда не было какого-либо rc
файла (файл .profile
предназначен для настройки вашей среды, а не для оболочки пер se).
csh
стала гораздо более популярной, чем оболочка Bourne, так как (хотя у нее был ужасно худший синтаксис, чем у оболочки Bourne) она добавила много более удобных и приятных функций для интерактивного использования.
В 3BSD
(1980) был добавлен which
csh скрипт для пользователей csh
, чтобы помочь идентифицировать исполняемые файлы, и это на самом деле не сильно отличается от скрипта, который вы можете найти как which
на многих коммерческих Unix системах сегодня (таких как Solaris, HP/UX, AIX или Tru64).
Этот скрипт читает ~/.cshrc
пользователя (как делают все скрипты csh
, если они не вызываются с csh -f
) и ищет указанные имена команд в списке псевдонимов и в $path
(массив, который csh
поддерживает на основе $PATH
).
Вот как дела обстоят: which
пришел первым для самой популярной оболочки в то время (и csh
оставалась популярной до середины 90-х), что является основной причиной, почему он был задокументирован в книгах и по-прежнему широко используется.
Обратите внимание, что даже для пользователя csh
этот which
csh скрипт не обязательно предоставит вам правильную информацию. Он получает псевдонимы, определенные в ~/.cshrc
, а не те, которые вы могли определить позже в командной строке или, например, вызвав другой файл csh
с помощью source
, и (хотя это было бы не очень хорошей идеей) PATH
может быть переопределен в ~/.cshrc
.
Выполнение этой команды which
из оболочки Bourne все еще будет искать псевдонимы, определенные в вашем ~/.cshrc
, но если у вас его нет, потому что вы не используете csh
, это все равно, вероятно, даст вам правильный ответ.
Подобная функциональность не была добавлена в оболочку Bourne до 1984 года в SVR2 с помощью встроенной команды type
. Тот факт, что она встроенная (в отличие от внешнего скрипта), означает, что она может дать вам правильную информацию (в какой-то степени), поскольку у нее есть доступ к внутреннему устройству оболочки.
type пострадала от проблемы, аналогичной скрипту which
в том, что она не возвращала код ошибки при отсутствии найденной команды. Кроме того, для исполняемых файлов, в отличие от which
, она выводила что-то вроде ls is /bin/ls
вместо просто /bin/ls
, что делало ее менее удобной для использования в сценариях.
Оболочка Unix Version 8 (не выпущенная в общественное пользование) переименовала встроенную команду type
в whatis
и расширила ее, чтобы также сообщать о параметрах и выводить определения функций. Она также исправила проблему type
, когда она не возвращала ошибку при отсутствии имени.
rc
, оболочка Plan9 (который должен был стать преемником Unix) (и его производные, такие как akanga
и es
) тоже имеют whatis
.
Оболочка Korn (подмножество которой основано на определении POSIX sh
), разработанная в середине 80-х, но не широко доступная до 1988 года, добавила много функций csh
(редактор строк, псевдонимы…) поверх оболочки Bourne. Она добавила свой собственный встроенный whence
(в дополнение к type
), который принимал несколько опций (-v
, чтобы предоставить подробный вывод, похожий на type
, и -p
, чтобы искать только исполняемые файлы (а не псевдонимы/функции…)).
Совпадение с волнующими вопросам касательно авторских прав между AT&T и Berkeley, несколько бесплатных программных реализаций оболочек появились в конце 80-х и начале 90-х годов. Все оболочки Almquist (ash
, заменяющая оболочку Bourne в BSD), реализация ksh
в общественном достоянии (pdksh
), bash
(при поддержке FSF), zsh
появились между 1989 и 1991 годами.
Ash, хотя и предназначалась для замены оболочки Bourne, не имела встроенной type
до гораздо более позднего срока (в NetBSD 1.3 и FreeBSD 2.3), хотя у нее имелась hash -v
. OSF/1 /bin/sh
имела встроенную type
, которая всегда возвращала 0 вплоть до OSF/1 v3.x. bash
не добавила whence
, но добавила опцию -p
к type
, чтобы напечатать путь (type -p
будет как whence -p
) и -a
для отчета о всех совпадающих командах. tcsh
сделала which
встроенной и добавила команду where
, работающую как bash
‘s type -a
. zsh
имеет их все.
Оболочка fish
(2005) имеет команду type
, реализованную как функцию.
Тем временем скрипт which
csh был удален из NetBSD (так как он был встроенным в tcsh и не очень полезен в других оболочках), а функциональность была добавлена в whereis
(когда вызывается как which
, whereis
ведет себя как which
, за исключением того, что он ищет только исполняемые файлы в $PATH
). В OpenBSD и FreeBSD which
также была изменена на ту, что написана на C, которая ищет команды только в $PATH
.
Реализации
Существует десятки реализаций команды which
на различных Unix системах с разным синтаксисом и поведением.
На Linux (помимо встроенных в tcsh
и zsh
) мы находим несколько реализаций. Например, на современных системах Debian это простой POSIX сценарий оболочки, который ищет команды в $PATH
.
busybox
также имеет команду which
.
Существует GNU
which
, который, вероятно, является самым эксцентричным. Он пытается расширить то, что делал скрипт which
, на другие оболочки: вы можете сообщить ему о ваших псевдонимах и функциях, чтобы он мог дать вам лучший ответ (и, я полагаю, некоторые дистрибутивы Linux устанавливают некоторые глобальные псевдонимы для bash
для этого).
zsh
имеет пару операторов для расширения до пути к исполняемым файлам: оператор =
расширения имени файла и модификатор :c
для расширения истории (применяемый здесь к расширению параметров):
$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls
zsh
, в модуле zsh/parameters
, также создает таблицу хешей команд как ассоциативный массив commands
:
$ print -r -- $commands[ls]
/bin/ls
Утилита whatis
(за исключением той, что в оболочке Unix V8 Bourne или в Plan 9 rc
/es
) не действительно связана, так как она предназначена только для документации (вызывает поиск в базе данных whatis, это сжатие страницы man).
whereis
также была добавлена в 3BSD
одновременно с which
, хотя она была написана на C
, а не на csh
, и используется для поиска одновременно исполняемого файла, страницы man и исходника, но не на основе текущей среды. Так что снова, это отвечает на другую потребность.
Теперь, на стандартном фронте, POSIX определяет команды command -v
и -V
(которые ранее были необязательными до POSIX.2008). UNIX определяет команду type
(без опций). Вот и все (where
, which
, whence
не определены в любом стандарте).
До некоторых версий type
и command -v
были необязательными в спецификации Linux Standard Base, что объясняет, почему, например, в некоторых старых версиях posh
(хотя основанных на pdksh
, который имел оба) не было ни одного из них. command -v
также была добавлена в некоторые реализации оболочки Bourne (например, на Solaris).
Статус на сегодня
Текущий статус таков, что type
и command -v
повсеместно присутствуют во всех оболочках, подобных Bourne (хотя, как заметил @jarno, обратите внимание на оговорку/ошибку в bash
при отсутствии в режиме POSIX или некоторых потомках оболочки Almquist, указанных ниже в комментариях). tcsh
— единственная оболочка, в которой вы хотите использовать which
(так как там нет type
, а which
является встроенной).
В оболочках, кроме tcsh
и zsh
, which
может сообщить вам путь к данному исполняемому файлу, если только в любой из ваших ~/.cshrc
, ~/.bashrc
или любом файле инициализации оболочки нет псевдонима или функции с таким же именем, и вы не определяете $PATH
в вашем ~/.cshrc
. Если у вас есть определенный псевдоним или функция для этого, она может сообщить вам о нем или не сообщить, или сообщить неправильное значение.
Если вы хотите знать о всех командах с заданным именем, ничего переносимого. Вы бы использовали where
в tcsh
или zsh
, type -a
в bash
или zsh
, whence -a
в ksh93, и в других оболочках вы можете использовать type
в сочетании с which -a
, что может сработать.
Рекомендации
Получение пути к исполняемому файлу
Теперь, чтобы получить путь к исполняемому файлу в сценарии, есть несколько оговорок:
ls_path=$(command -v ls)
будет стандартным способом сделать это.
Однако есть несколько проблем:
- Невозможно узнать путь к исполняемому файлу, не выполняя его. Все
type
,which
,command -v
… используют эвристики для определения пути. Они проходят по компонентам$PATH
и находят первый не директории файл, для которого у вас есть разрешение на выполнение. Тем не менее, в зависимости от оболочки, когда дело доходит до выполнения команды, многие из них (Bourne, AT&T ksh, zsh, ash…) просто выполнят их в порядке$PATH
, пока системный вызовexecve
не вернет ошибку. Например, если$PATH
содержит/foo:/bar
и вы хотите выполнитьls
, они сначала попробуют выполнить/foo/ls
, или если это неудачно,/bar/ls
. Теперь выполнение/foo/ls
может не удаться, потому что у вас нет разрешения на выполнение, но также и по многим другим причинам, например, это не является действительным исполняемым файлом.command -v ls
сообщит/foo/ls
, если у вас есть разрешение на выполнение для/foo/ls
, но выполнениеls
может фактически запустить/bar/ls
, если/foo/ls
не является действительным исполняемым файлом. - если
foo
является встроенной функцией или псевдонимом,command -v foo
возвращаетfoo
. С некоторыми оболочками, такими какash
,pdksh
илиzsh
, это также может вернутьfoo
, если$PATH
включает пустую строку и в текущем директории имеется исполняемый файлfoo
. Есть некоторые обстоятельства, когда вам нужно учитывать это. Имейте в виду, что список встроенных команд варьируется в зависимости от реализации оболочки (например,mount
иногда является встроенной для busyboxsh
), и, например,bash
может получать функции из окружения. - если
$PATH
содержит относительные компоненты пути (обычно.
или пустую строку, которые обе ссылаются на текущий каталог, но могут быть чем угодно), в зависимости от оболочки,command -v cmd
может не выводить абсолютный путь. Таким образом, путь, который вы получаете в момент выполненияcommand -v
, больше не будет действительным, после того как выcd
в другое место. - Анеcdотическая: с оболочкой ksh93, если
/opt/ast/bin
(хотя этот точный путь может различаться на разных системах, я полагаю) находится в вашем$PATH
, ksh93 сделает доступными несколько дополнительных встроенных команд (chmod
,cmp
,cat
…), ноcommand -v chmod
вернет/opt/ast/bin/chmod
, даже если этот путь не существует.
Определение, существует ли команда
Чтобы узнать, существует ли указанная команда стандартным образом, вы можете сделать:
if command -v given-command > /dev/null 2>&1; then
echo given-command доступна
else
echo given-command не доступна
fi
Где можно использовать which
(t)csh
В csh
и tcsh
у вас не так много выбора. В tcsh
это нормально, поскольку which
встроенная. В csh
это будет команда системы which
, которая может не сделать то, что вы хотите в нескольких случаях.
Найти команды только в некоторых оболочках
Случай, когда может иметь смысл использовать which
, это если вы хотите знать путь команды, игнорируя потенциальные встроенные команды оболочки или функции в bash
, csh
(не tcsh
), dash
или Bourne
сценарии оболочки, то есть оболочки, которые не имеют whence -p
(такие как ksh
или zsh
), command -ev
(такие как yash
), whatis -p
(rc
, akanga
) или встроенный which
(такие как tcsh
или zsh
) на системах, где which
доступен и не является скриптом csh
.
Если эти условия выполнены, то:
echo_path=$(which echo)
даст вам путь к первому echo
в $PATH
(кроме крайних случаев), независимо от того, является ли echo
также встроенной/псевдонимом/функцией или нет.
В других оболочках вы бы предпочли:
- zsh:
echo_path==echo
илиecho_path=$commands[echo]
илиecho_path=${${:-echo}:c}
- ksh, zsh:
echo_path=$(whence -p echo)
- yash:
echo_path=$(command -ev echo)
- rc, akanga:
echo_path = `{whatis -p echo}
(остерегайтесь путей, содержащих пробелы) - fish:
set echo_path (type -fp echo)
Имейте в виду, что если все, что вы хотите сделать, это выполнить эту команду echo
, вам не нужно получать ее путь, вы можете просто сделать:
env echo это не выводится встроенной командой echo
Например, с tcsh
, чтобы предотвратить использование встроенной which
:
set echo_path = "`env which echo`"
Когда вам действительно нужна внешняя команда
Другой случай, когда вы можете захотеть использовать which
, это когда вам действительно нужна внешняя команда. POSIX требует, чтобы все встроенные команды оболочки (такие как command
) также были доступны как внешние команды, но, к сожалению, это не так для command
на многих системах. Например, редко можно найти команду command
в операционных системах на базе Linux, в то время как большинство из них имеют команду which
(хотя и разные версии с разными опциями и поведением).
Случаи, когда вам нужна внешняя команда, могут возникнуть, когда вы собираетесь выполнить команду без вызова оболочки POSIX.
Функции system("некоторые командные строки")
, popen()
… различных языков C вызывают оболочку для разбора этой командной строки, так что system("command -v my-cmd")
будет работать в них. Исключением будет perl
, который оптимизирует выполнение оболочки, если не видит ни одного специального символа оболочки (кроме пробела). Это также относится к его оператору обратной кавычки:
$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0
$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs
Добавление :;
выше заставляет perl
вызвать оболочку. Используя which
, вам не придется использовать этот трюк.
Причины, по которым может не стоить использовать which
, уже были объяснены, но вот несколько примеров на нескольких системах, где which
на самом деле дает сбой.
В оболочках, подобных Bourne, мы сравниваем вывод which
с выводом type
(type
является встроенной командой оболочки, она должна быть истиной, так как это оболочка, рассказывающая нам, как она вызовет команду).
Многие случаи являются крайними случаями, но имейте в виду, что which
/type
часто используются в крайних случаях (чтобы найти ответ на неожиданное поведение, например: почему эта команда ведет себя так, какую же я вызываю?).
Большинство систем, большинство оболочек, подобных Bourne: функции
Самый очевидный случай — это функции:
$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls
Причина заключается в том, что which
сообщает только об исполняемых файлах, а иногда и о псевдонимах (хотя не всегда о тех, что в вашей оболочке), а не о функциях.
Страница man GNU which имеет сломанный (так как они забыли заключить $@
в кавычки) пример о том, как использовать его для отчета о функциях, но, как и в случае с псевдонимами, поскольку он не реализует парсер синтаксиса оболочки, его легко обмануть:
$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}
Большинство систем, большинство оболочек, подобных Bourne: встроенные команды
Другой очевидный случай — это встроенные команды или ключевые слова, так как which
, будучи внешней командой, не имеет никакой возможности знать, какие встроенные команды имеются в вашей оболочке (и некоторые оболочки, такие как zsh
, bash
или ksh
, могут загружать встроенные команды динамически):
$ type echo . time
echo is a shell builtin
. is a shell builtin
time is a shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time
(это не касается zsh
, где which
является встроенной)
Solaris 10, AIX 7.1, HP/UX 11i, Tru64 5.1 и многие другие:
$ csh
% which ls
ls: aliased to ls -F
% unalias ls
% which ls
ls: aliased to ls -F
% ksh
$ which ls
ls: aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls
Это происходит потому, что во многих коммерческих Unix системах which
(как в оригинальной реализации в 3BSD) является csh
скриптом, который читает ~/.cshrc
.
Псевдонимы, которые он будет сообщать, — это те, что определены там, независимо от текущих определенных псевдонимов и независимо от модели оболочки, которую вы используете.
В HP/UX или Tru64:
% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls
(версии Solaris и AIX исправили эту проблему, сохраняя $path
перед чтением ~/.cshrc
и восстанавливая его перед поиском команд)
$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin
Или:
$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin
(конечно, будучи скриптом csh
, вы не можете ожидать, что он будет работать с аргументами, содержащими пробелы…)
CentOS 6.4, bash
$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
/usr/bin/test
$ alias $'foo=\nalias bar="
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar="
На этой системе есть псевдоним, определенный на уровне системы, который оборачивает команду GNU which
.
Ложный вывод вызван тем, что which
читает вывод bash
‘s alias
, но не знает, как правильно его разобрать, и использует эвристику (один псевдоним на строку, ищет первую найденную команду после |
, ;
, &
…)
Худшее в CentOS заключается в том, что zsh
имеет совершенно нормальную встроенную команду which
, но CentOS смогла сломать ее, заменив на неработающий псевдоним для GNU which
.
Debian 7.0, ksh93:
(хотя это относится к большинству систем с множеством оболочек)
$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which
На Debian /bin/which
является скриптом /bin/sh
. В моем случае sh
— это dash
, но это то же самое, когда это bash
.
Неустановленный PATH
не предназначен для отключения поиска PATH
, а означает использование умолчательного пути системы, который, к сожалению, на Debian никто не согласен (dash
и bash
имеют /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
, zsh
имеет /bin:/usr/bin:/usr/ucb:/usr/local/bin
, ksh93
имеет /bin:/usr/bin
, mksh
имеет /usr/bin:/bin
($(getconf PATH)
), execvp()
(как в env
) имеет :/bin:/usr/bin
(да, сначала смотрит в текущем каталоге!)).
Вот почему which
дает неверный результат выше, так как он использует dash
‘s умолчательный PATH
, который отличается от ksh93
.
С GNU which
дела обстоят не лучше, он сообщает:
which: no which in ((null))
(что любопытно, в моей системе действительно есть /usr/local/bin/which
, который на самом деле является akanga
скриптом, поставляемым с akanga
(производной оболочкой rc
, где умолчательный PATH
— /usr/ucb:/usr/bin:/bin:.
))
bash, любая система:
Тот самый пример, который упоминает Крис в своем ответе:
$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls
Также после вызова hash
вручную:
$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)
Теперь случай, когда which
и иногда type
дают сбой:
$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo
Теперь с некоторыми оболочками:
$ foo
bash: ./b/foo: /: bad interpreter: Permission denied
С другими:
$ foo
a/foo
Ни which
, ни type
не могут заранее знать, что b/foo
не может быть выполнен. Некоторые оболочки, такие как bash
, ksh
или yash
, при вызове foo
действительно попытаются выполнить b/foo
и сообщат об ошибке, в то время как другие (как zsh
, ash
, csh
, Bourne
, tcsh
) выполнит a/foo
при неудаче системного вызова execve()
для b/foo
.
Одним из моментов, который, похоже, не упомянул Стефан, является то, что which
не имеет представления о хэш-таблице путей вашей оболочки. Это приводит к тому, что он может вернуть результат, который не соответствует тому, что на самом деле выполняется, что делает его неэффективным при отладке.
Я часто использую which
, чтобы быстро перейти к исполняемому файлу так: vim $(which my_executable)
. Недавно я нашел этот пост и был довольно удивлен — я делал это неправильно все это время? Но потом я проверил все ответы и не нашел причин полностью отказать от which
: (вывод остается без изменений)
$ type -a pacman
pacman is aliased to `pacman'
pacman is /usr/bin/pacman
$ command -v pacman
alias pacman='pacman'
$ which pacman
/usr/bin/pacman
$ bash --version
GNU bash, version 5.1.8(1)-release (x86_64-pc-linux-gnu)
Команда which является сломанным наследием от C-Shell и лучше ее оставить в покое в оболочках, подобным Bourne.
Я думаю, что следует использовать любой инструмент, если он удовлетворяет всем вашим требованиям. Я использую which
, чтобы находить путь к исполняемому файлу в $PATH
, и, конечно, это не подходящий инструмент, чтобы изучить, что на самом деле скрывается за вашей командой, и тогда type
и command
лучше подойдут.
(Так как этот вопрос помечен как “переносимость”, различные интерпретации вопроса исключены, включая “интерактивное использование” и “меня заботит только система, которую я использую”. Таким образом, этот ответ учитывает только Почему нельзя делать это в сценарии оболочки?)
Стефан представил всесторонний анализ вариантов, с указанием, почему каждый из них хорош или плох. Нет одной причины, почему which
— всегда неверный ответ. Скорее, множество мелких факторов, способствующих этому. Хотя вы можете быть терпимы к одному или нескольким из них, в совокупности они могут изменить ваше мнение.
Одна причина, которая еще не была упомянута, чтобы не использовать which
, это то, что это поднимает вопрос:
Почему вы хотите результат which
с самого начала?
Под этим я имею в виду, что иногда цель не должна заключаться в том, чтобы найти замену для which
, а скорее в том, чтобы реорганизовать код таким образом, чтобы не было необходимости в этом изначально.
Распространенный шаблон:
CMD=$( which cmd )
# некоторое время спустя...
$CMD --some --args
Не учитывая тот факт, что $CMD
почти всегда не заключен в кавычки, я бы утверждал, что весь этот шаблон сломан.
Одно дело жестко закодировать CMD=/my/path/to/cmd
, когда цель заключается в том, чтобы избежать зависимости от $PATH
.
Но если вы собираетесь искать в $PATH
в любом случае, то это почти всегда не имеет смысла. Уберите which
и $CMD
полностью, и просто запишите:
cmd --some --args
Если вы думаете, что вам нужен путь, потому что вы хотите внедрить его в функцию или псевдоним, тогда вот где command
может помочь:
function cmd {
command cmd --extra-arg "$@"
}
Конечно, есть исключения, когда вам нужна программа, основанная на другом PATH
. В таком случае рассмотрите возможность использования:
function cmd {
PATH=$OTHERPATH command cmd "$@"
}
Суть, в общем, заключается в том, чтобы избегать ментального укорочения “как мне получить путь к команде”, и вместо этого рассматривать каждый случай индивидуально: действительно ли мне нужен путь и почему?
В некоторых случаях which
может быть приемлем, но, по крайней мере, иногда есть лучшие решения. Если вы пишете учебник для других людей, пожалуйста, постарайтесь вдумчиво рассмотреть другие варианты.
Даже если вы думаете, что CMD=$( which cmd ) ; ... $CMD args
оправдан, есть еще причины избегать which
Он не универсален
Список команд, требуемых POSIX, включает command
и type
, но не which
.
Формат его вывода не уточнен
which
обязан лишь указать путь к указанной команде, а не предоставлять его дословно и без комментариев. Хотя большинство версий делают это.
Вполне законно, если which foo
выводит что-то вроде:
foo might be /opt/foobar/bin/foo (unverified)
; илиfoo is in /bin
; или~foo/bin/foo
(где~foo
обозначает домашний каталог foo); или/proc/1234/root/bin/foo
(где процесс 1234 с тех пор завершил работу)
Ответ может устареть
(это в основном исключает все формы CMD=$( что-то ) ; ... $CMD ...
и не является специфическим для which
.)
По разным причинам расположение исполняемого файла для данного имени команды может измениться.
Иногда вы хотите старое расположение, а иногда нет.
Иногда путь к команде является относительным (начинается с .
, а не /
). Это редкость, потому что обычно это рассматривается как риск безопасности, но это все еще может применяться в особых случаях, в таком случае простое выполнение cd
может аннулировать путь к программе.
Честно говоря, which
это то, что это.
Он работал для меня на протяжении многих лет, ОС и платформ.
Моя рекомендация: используйте это.
Я использую это все время в командной строке.
Действительно, я никогда не буду использовать это в сценарии. Суть в том, что
я не должен это использовать в сценариях.
Я бы не использовал which
, чтобы обнаружить конкретную версию чего-либо.
Есть небольшое исключение.
В современное время, не Sys V, разработчики, действительно, помещают системные команды,
“где-то”.
Здесь which
, возможно, должен определить базовую команду.
Ответ или решение
Почему не следует использовать which
и что вместо этого использовать
Введение
При работе с исполняемыми файлами в Unix-подобных системах существует множество утилит, таких как which
, type
, command
и другие, каждая из которых имеет свои особенности и предназначение. Однако, which
часто рекомендуется избегать, особенно в сценариях написания скриптов. В этом ответе будет рассмотрено, почему это так, а также чем его можно заменить.
Проблемы с which
-
Неверная информация о командах:
Утилитаwhich
, будучи скриптом, исполняемым в шелле, не учитывает внутренние функции и псевдонимы. Например, если команда была определена как функция или псевдоним,which
может вернуть путь к исполняемому файлу, игнорируя информацию о том, что была вызвана функция.$ type ls ls is a function $ which ls /bin/ls
В этом примере видно, что
which
не показывает, чтоls
является функцией. -
Ошибочная работа с путями:
Посколькуwhich
основывается на переменной окружения$PATH
, он может вернуть неверный путь, если в этом пути присутствуют относительные директории или если текущая директория была изменена. -
Проблемы с совместимостью:
which
не является частью стандарта POSIX, и его наличие не гарантируется во всех Unix-подобных системах. В некоторых случаях разные версииwhich
могут вести себя по-разному. -
Непредсказуемый вывод:
Формат выводаwhich
не является строгим: он может содержать дополнительные сообщения, что делает его использование в сценариях ненадежным. -
Неэффективность для скриптов:
Вводя в скрипт команды, которые используютwhich
для получения пути к исполняемым файлам, мы создаем ненужные зависимости, которые ухудшают читаемость и поддержку кода.
Рекомендуемые альтернативы
-
command -v
:
Для написания скриптов рекомендуется использоватьcommand -v
, так как он возвращает более точную информацию о команде, включая встроенные функции и псевдонимы. Пример:cmd_path=$(command -v ls)
-
type
:
При интерактивном использовании, чтобы проверить, является ли команда встроенной, или псевдонимом, можно использоватьtype
:type ls
-
Установка явного пути в скриптах:
Вместо использованияwhich
, лучше явно указывать путь к исполняемому файлу, чтобы избежать путаницы и ошибок при его выполнении.
Заключение
Учитывая все вышеперечисленные проблемы, использование which
в скриптах может привести к ошибкам и неясностям. Предпочтительные альтернативы, такие как command -v
и type
, обеспечивают большую точность и предсказуемость. Важно помнить, что лучше всего управлять командами так, чтобы минимизировать неопределенности и повысить читаемость кода.