Почему бы не использовать «который»? Что использовать тогда?

329

Ища путь к исполняемому или проверить , что произойдет , если ввести имя команды в оболочке Unix, существует множество различных утилит ( which, type, command, whence, where, whereis, whatis, hashи т.д.).

Мы часто слышим, что этого whichследует избегать. Почему? Что мы должны использовать вместо этого?

Стефан Шазелас
источник
3
Я думаю, что большинство аргументов против использования whichпредполагают наличие интерактивного контекста оболочки. Этот вопрос помечен / переносимость. Поэтому я интерпретирую вопрос в этом контексте как «что использовать вместо того, whichчтобы найти первый исполняемый файл с заданным именем в $PATH». Большинство ответов и аргументов против whichпсевдонимов, встроенных функций и функций, которые в большинстве реальных переносимых сценариев оболочки представляют только академический интерес. Локально определенные псевдонимы не наследуются при запуске сценария оболочки (если вы его не используете .).
MattBianco
5
@MattBianco, да, cshwhichвсе еще является cshсценарием для большинства коммерческих Unices) действительно читает, ~/.cshrcкогда не интерактивен. Вот почему вы заметите, что csh-скрипты обычно начинаются с #! /bin/csh -f. whichне потому, что он стремится дать вам псевдонимы, потому что он предназначен как инструмент для (интерактивных) пользователей csh. POSIX оболочки пользователи имеют command -v.
Стефан Шазелас
@rudimeier, тогда ответ будет всегда, если ваша оболочка (t)csh(или вы не возражаете, если она не даст вам правильный результат), используйте typeили command -vвместо . Смотрите ответы почему .
Стефан
1
@rudimeier, ( stat $(which ls)неправильно по нескольким причинам (пропущено --, пропущены кавычки), не только использование which). Вы бы использовали stat -- "$(command -v ls)". Предполагается, что lsэто действительно команда, найденная в файловой системе (не встроенная оболочка или функция псевдонима). whichможет дать вам неправильный путь (а не путь, который будет выполнять ваша оболочка, если вы введете ls) или псевдоним, определенный в конфигурации некоторых других оболочек ...
Стефан
1
@rudimeier, опять же, существует ряд условий, при которых многие whichреализации не дают вам даже того, lsчто будет найдено при поиске $PATH(независимо от того, что lsможет вызываться в вашей оболочке). sh -c 'command -v ls'или zsh -c 'rpm -q --whatprovides =ls', скорее всего, даст вам правильный ответ. Дело в том, что whichэто разрушенное наследие csh.
Стефан

Ответы:

367

Вот все, о чем вы никогда не думали, что никогда не захотите узнать об этом:

Резюме

Чтобы получить путь к исполняемому файлу в Bourne-подобном сценарии оболочки (есть несколько предостережений; см. Ниже):

ls=$(command -v ls)

Чтобы узнать, существует ли данная команда:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

По приглашению интерактивной оболочки типа Борна:

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предназначенного для настройки среды, а не оболочки как таковой ).

csh стала намного более популярной, чем оболочка Bourne, поскольку (хотя она имела ужасно худший синтаксис, чем оболочка Bourne), она добавила множество более удобных и приятных функций для интерактивного использования.

В 3BSD1980 году для пользователей был добавлен whichсценарий csh,csh помогающий идентифицировать исполняемый файл, и это едва ли другой сценарий, который вы можете найти, как и whichво многих коммерческих Unices в настоящее время (например, Solaris, HP / UX, AIX или Tru64).

Этот сценарий читает пользователя ~/.cshrc(как и все cshсценарии, если только он не вызывается csh -f) и ищет предоставленные имена команд в списке псевдонимов и в $path(массив, который cshподдерживается на основе $PATH).

Итак, вы whichпришли первым для самой популярной оболочки в то время (и cshвсе еще были популярны до середины 90-х), что является основной причиной, почему она была задокументирована в книгах и до сих пор широко используется.

Обратите внимание, что даже для cshпользователя этот whichсценарий csh не обязательно дает вам правильную информацию. Он получает псевдонимы, определенные в ~/.cshrc, а не те, которые вы, возможно, определили позже в приглашении или, например, с помощью sourceдругого cshфайла, и (хотя это не будет хорошей идеей) PATHмогут быть переопределены в ~/.cshrc.

Выполнение этой whichкоманды из оболочки Bourne будет по-прежнему искать псевдонимы, определенные в вашем ~/.cshrc, но если их нет, потому что вы их не используете csh, это все равно, вероятно, даст вам правильный ответ.

Подобная функциональность не была добавлена ​​в оболочку Bourne до 1984 года в SVR2 с помощью typeвстроенной команды. Тот факт, что он встроен (в отличие от внешнего скрипта), означает, что он может дать вам правильную информацию (в некоторой степени), поскольку он имеет доступ к внутренним компонентам оболочки.

Первоначальная typeкоманда страдала от проблемы, аналогичной whichскрипту, в том, что она не возвращала состояние завершения сбоя, если команда не была найдена. Кроме того, для исполняемых файлов, напротив which, он выводит что-то вроде, ls is /bin/lsа не просто, /bin/lsчто делает его менее простым в использовании в скриптах.

В оболочке Bourne версии 8 Unix (не выпущенной в дикой природе) ее typeвстроенное имя было переименовано whatis. И оболочка Plan9 (некогда преемник Unix) rc(и ее производные, такие как akangaи es) также имеют whatis.

Оболочка Korn (подмножество, на котором основано определение POSIX sh), разработанная в середине 80-х годов, но не получившая широкого распространения до 1988 года, добавила многие cshфункции (редактор строк, псевдонимы ...) поверх оболочки Bourne. , Он добавил свою собственную whenceвстроенную функцию (в дополнение к type), которая приняла несколько опций ( -vдля обеспечения typeподробного вывода -подобного вывода и -pпоиска только исполняемых файлов (не псевдонимов / функций ...)).

Совсем случайно, в связи с проблемами авторского права между AT & T и Беркли, в конце 80-х начале 90-х годов появилось несколько реализаций оболочки свободного программного обеспечения . Вся оболочка Almquist (пепел, которая должна быть заменой оболочки 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команды в различных Unices с разным синтаксисом и поведением.

В Linux (кроме встроенных в tcshand zsh) мы находим несколько реализаций. Например, в последних системах Debian это простой сценарий оболочки POSIX, который ищет команды в $PATH.

busyboxтакже есть whichкоманда.

Существует, GNU whichпожалуй, самый экстравагантный. Он пытается распространить действие whichсценария csh на другие оболочки: вы можете сказать ему, каковы ваши псевдонимы и функции, чтобы он мог дать вам лучший ответ (и я полагаю, что некоторые дистрибутивы 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Утилита (для одного в оболочке или плане 9 Unix V8 Bourne , кроме rc/ es) на самом деле не связана , как это только для документации (базы данных отбирает Whatis, то есть человек , страница синопсис).

whereisОн также был добавлен в 3BSDто же время, как whichесли бы он был написан C, но не cshиспользуется для одновременного поиска исполняемого файла, справочной страницы и исходного кода, но не на основе текущей среды. Итак, еще раз, это отвечает другой необходимости.

Теперь, на стандартном фронте, 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, обратите внимание на caveat / bug, 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=$(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он не является допустимым исполняемым файлом.
  • if fooявляется встроенной функцией или функцией или псевдонимом, command -v fooвозвращает foo. С некоторыми оболочками, такими как ash, pdkshили zsh, он также может возвращать, fooесли $PATHвключает пустую строку и fooв текущем каталоге есть исполняемый файл. Есть некоторые обстоятельства, когда вам может понадобиться принять это во внимание. Имейте в виду, например, что список встроенных функций зависит от реализации оболочки (например, mountиногда встроен для busybox sh) и, например, bashможет получать функции из среды.
  • Если $PATHсодержит компоненты относительного пути (обычно .или пустую строку, которые оба ссылаются на текущий каталог, но могут быть чем угодно), в зависимости от оболочки, command -v cmdможет не выводиться абсолютный путь. Таким образом, путь, который вы получите в то время, когда вы бежите command -v, больше не будет действительным после вас 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 is available
else
  echo given-command is not available
fi

Где можно использовать which

(t)csh

В cshи у tcshвас нет большого выбора. В tcsh, это нормально, как whichвстроено. Во- первых csh, это будет системная whichкоманда, которая в некоторых случаях может не выполнять то, что вы хотите.

найти команды только в некоторых оболочках

Случай , когда это возможно , имеет смысл использовать which, если вы хотите знать , путь команды, игнорируя потенциальные внутренние команды или функции оболочки в bash, csh(не tcsh), dashили Bourneсценарии оболочки, то есть снаряды , которые не имеют whence -p(например , kshили zsh) , command -ev(like yash), whatis -p( rc, akanga) или встроенный which(like tcshили zsh) в системах, где whichдоступен и не является cshсценарием.

Если эти условия соблюдены, то:

echo=$(which echo)

даст вам путь к первому echoвходу $PATH(за исключением угловых случаев), независимо от того, является ли echoтакже встроенная оболочка / псевдоним / функция или нет.

В других оболочках вы бы предпочли:

  • zsh : echo==echoили echo=$commands[echo]илиecho=${${:-echo}:c}
  • кш , зш :echo=$(whence -p echo)
  • Яш :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(остерегайтесь путей с пробелами)
  • рыба :set echo (type -fp echo)

Обратите внимание, что если все, что вы хотите сделать, это запустить эту echoкоманду, вам не нужно указывать ее путь, вы можете просто сделать:

env echo this is not echoed by the builtin echo

Например, с помощью tcsh, чтобы предотвратить использование встроенной функции which:

set Echo = "`env which echo`"

когда вам нужна внешняя команда

Другой случай, когда вы можете захотеть использовать whichэто когда вам действительно нужна внешняя команда. POSIX требует, чтобы все встроенные функции оболочки (например command) также были доступны как внешние команды, но, к сожалению, это не так во commandмногих системах. Например, редко можно найти commandкоманду в операционных системах на основе Linux, в то время как у большинства из них есть whichкоманда (хотя разные с разными параметрами и поведением).

Случаи, когда вам может понадобиться внешняя команда, будут выполняться там, где вы будете выполнять команду без вызова оболочки POSIX.

system("some command line"), 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, вам не придется использовать этот трюк.

Стефан Шазелас
источник
24
@Joe, whichэто cshскрипт для многих коммерческих Unices. Причина историческая, поэтому я дал историю, чтобы люди понимали, откуда она взялась, почему люди привыкли использовать ее и почему на самом деле нет причин, по которым вы должны ее использовать. И да, некоторые люди используют (t) csh. Еще не все используют Linux
Стефан Шазелас
12
Прочитав этот пост, я нашел много контекста для ответа, но не сам ответ. Где в этом посте говорится, почему бы не использовать which, в отличие от того, что вы пытаетесь использовать which, историю which, реализации which, другие команды для выполнения связанных задач или причины для фактического использования which? Почему другие команды лучше ? Чем они отличаются от which? Как они избегают его ловушек? Этот ответ на самом деле тратит больше слов на проблемы с альтернативами, чем проблемы с which.
user62251
1
В отличие от того, что утверждает ответ, command -vне проверяет разрешение на выполнение, по крайней мере, если вы вызываете его с помощью аргумента чистого имени файла без пути. Я протестировал dash 0.5.8 и GNU bash 4.3.48.
Ярно
2
@ StéphaneChazelas Если я создаю новый файл, touch /usr/bin/mytestfileа затем запускаю command -v mytestfile, он даст путь (тогда как which mytestfileнет).
Ярно
2
@ Ярно, да, ты прав. bashостановится на неисполняемом файле, если он не сможет найти исполняемый файл, так что это «ОК» (хотя на практике можно было бы command -v/ typeвернуть ошибку), так как это команда, которую он попытается выполнить при запуске mytestfile, но dashповедение глючит, так как если есть неисполняемый файл cmdперед исполняемым, command -vвозвращает неисполняемый, в то время как выполнение cmdбудет исполнять исполняемый (неправильный также хэшируется). FreeBSD sh(также основанная на ash) имеет ту же ошибку. zsh, yash, ksh, mksh, bash as sh в порядке.
Стефан
47

Причины, по которым кто-то может не захотеть использовать 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только отчеты об исполняемых файлах, а иногда и о псевдонимах (хотя и не всегда в вашей оболочке), а не о функциях.

GNU, на которой man-страница имеет неработающий (как они забыли процитировать $@) пример того, как использовать ее и для отчета о функциях, но так же, как и для псевдонимов, потому что она не реализует синтаксический анализатор оболочки, ее легко обмануть:

$ 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

Это потому, что в большинстве коммерческих Unices 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='

В этой системе существует общесистемный псевдоним, заключающий в себе whichкоманду GNU .

Фиктивный выход , потому что whichчитает вывод bash«с , 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.

Unset PATHне 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» S

Это не лучше с GNU, whichкоторый сообщает:

which: no which in ((null))

(интересно, действительно есть /usr/local/bin/whichв моей системе , которая на самом деле является akangaсценарий , который пришел с akangarcпроизводной оболочки , где по умолчанию PATHявляется /usr/ucb:/usr/bin:/bin:.))

Баш, любая система:

Тот, на кого ссылается Крис в своем ответе :

$ 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.

Стефан Шазелас
источник
mkshфактически использует что-то другое для значения по умолчанию $PATH: сначала _PATH_DEFPATHиспользуется константа времени компиляции операционной системы (чаще всего на BSD), затем confstr(_CS_PATH, …)используется (POSIX), и, если оба не существуют или не работают, /bin:/usr/bin:/sbin:/usr/sbinиспользуется.
Мирабилось
1
В вашем первом примере, даже если lsэто функция, которую она использует lsиз PATH. И whichхорошо, чтобы сказать вам, какой из них используется /usr/bin/ls или /usr/local/bin/ls. Я не вижу «Почему бы не использовать какой» ....
rudimeier
@rudimeier, что which lsдаст мне , /bin/lsнезависимо от того , lsне вызывает функцию /bin/lsили /opt/gnu/bin/lsили dirили вообще ничего. IOW, which(то, что реализации, IMMV) дает что-то неуместное
Стефан
1
@ StéphaneChazelas. Нет нет нет. Я уже знаю, что моя lsфункция. Я знаю , что моя lsфункция вызова lsиз PATH. Теперь whichговорит мне, где файл. Вы видите только один вариант использования: «Что бы моя оболочка сделала с этой командой». Для этого случая использования whichэто неправильно, правильно. Но есть и другие случаи использования, где (GNU) which- это абсолютно правильно.
rudimeier
@rudimeter, зависит от whichреализации. Кто-то скажет вам, что это псевдоним (если у вас настроен псевдоним или если ~/.cshrcв вашем доме есть такой псевдоним), кто-то даст вам путь, но неправильный при некоторых условиях. sh -c 'command -v ls'хотя он и не идеален, он все же с большей вероятностью даст вам правильный ответ на это другое требование (а также является стандартным).
Стефан
21

Одна вещь, которую (из моего быстрого бега) кажется, что Стефан не упомянул, это то, что whichон не имеет представления о хеш-таблице пути вашей оболочки. Это приводит к тому, что он может возвращать результат, который не отражает того, что на самом деле выполняется, что делает его неэффективным при отладке.

Крис Даун
источник
6

В UNIX духе: сделать каждую программу сделать одну вещь хорошо.

Если цель состоит в том, чтобы ответить: какой исполняемый файл существует с этим именем?

Какая исполняемая программа, поставляемая с системами Debian, является хорошим ответом. Предоставленные csh включали псевдонимы, что является источником проблем. То, что некоторые оболочки предоставляют в качестве внутреннего встроенного объекта, имеет другую цель. Либо используйте этот исполняемый файл, либо используйте скрипт, приведенный в конце этого ответа.

Если этот скрипт используется, он отвечает просто, просто и полезно.

Эта цель будет соответствовать первому предложению вашего вопроса:

При поиске пути к исполняемому файлу……

Если у вас есть система, в которой нет исполняемого файла, который называется (в большинстве систем linux есть), вы можете создать его ~/bin/whichранее /bin/в PATH, поэтому личные исполняемые файлы переопределяют системные, такие как в нижней части этого поста:

Этот исполняемый файл будет перечислять (по умолчанию) все исполняемые файлы, найденные в PATH. Если требуется только первое, опция -fдоступна.


На данный момент мы попадаем в другую цель:

что будет выполнять оболочка (после разбора)

Это вытекает из вашего второго предложения:

проверка того, что произойдет, если вы введете имя команды в оболочке Unix

Этот второй предмет пытается найти хороший ответ на вопрос, на который довольно сложно ответить. Оболочки имеют различные взгляды, угловые случаи и (как минимум) различные интерпретации. Добавление к этому:

Есть множество различных утилит (которые, типа, команды, откуда, где, где, что, хэш и т. д.).

И конечно, все попытки соответствуют этой цели.


Избегать чего?

Мы часто слышим то, чего следует избегать.

Интересно: почему это должно быть сказано, если which работает нормально (по крайней мере, в Debian)?

В духе UNIX: заставьте каждую программу делать что-то хорошо.

Внешняя программа whichделает одну вещь: Найти первый исполняемый на пути , который имеет такое же имя , как имя команды . И делает это достаточно хорошо.

Я не знаю ни одной другой программы или утилиты, которые бы ответили на этот вопрос более фундаментально. Как таковой, он полезен и может быть использован при необходимости.

Ближайшая альтернатива, кажется,: command -pv commandName, но это также сообщит о встроенных и псевдонимах. Не тот же ответ.

Конечно, whichон ограничен, он не отвечает на все вопросы, никакой инструмент не может этого сделать (ну, пока нет ...). Но это полезно, когда используется для ответа на вопрос, на который он был разработан (тот, что выше). Многое вроде edбыло ограничено и затем sedпоявилось (или vi/ vim). Или, как awkбыло ограничено, Perl появился и расширился. Тем не менее, ed, sedили / и awkимеют конкретные случаи использования , где vimи perlявляются не лучшими инструментами.

Почему?

Возможно, потому что whichотвечает только на часть вопроса, который может задать пользователь оболочки:

Что выполняется, когда я набираю commandName?


Внешний, который

Который должен быть доступен (во многих системах) как внешний исполняемый файл.
Единственный верный способ вызвать этот внешний инструмент - использовать env, чтобы выйти из оболочки, а затем вызвать which(что работает во всех оболочках):

 $ env which which
 /usr/bin/which

Или используйте полный путь к which(который может различаться в разных системах):

 /usr/bin/which which 

Зачем это hackнужно? Потому что некоторые оболочки (особенно zsh) скрывают which:

 $ zsh -c 'which which'
 which: shell built-in command

Быть внешним инструментом (вроде env) прекрасно объясняет, почему он не будет сообщать внутреннюю информацию оболочки. Как псевдонимы, функции, встроенные функции, специальные встроенные функции, переменные оболочки (не экспортируемые) и т. Д .:

 $ env which ls
 /usr/bin/ls
 $ env which ll       # empty output

Пустой вывод ll(общий псевдоним для ll='ls -l') указывает на то, что llон не связан с исполняемой программой или, по крайней мере, отсутствует исполняемый файл с именем llв переменной PATH. Использование llдолжно вызывать что-то еще, в этом случае, псевдоним:

 $ type ll
 ll is aliased to `ls -l'

type а также command

Команды typeи command -vзапрашиваются POSIX. Ожидается, что они будут работать в большинстве оболочек, и они работают, кроме как в csh, tcsh, fish и rc.

Обе команды могут быть использованы для предоставления другой точки зрения, какая команда будет выполнена.

whence, where, whereis, whatis,hash

Тогда, есть whence, where, whereis, whatis, hash, и некоторые другие. Все разные ответы на похожие вопросы. Все работают по-разному в разных оболочках. Наверное, whenceсамый распространенный после type. Другие представляют собой специальные решения, которые по-разному отвечают на один и тот же вопрос.

Что мы должны использовать вместо этого?

Вероятно , whichпервый , чтобы знать , если существует исполняемый файл по имени Имя_команды , затем typeи commandзатем, если Имя_команды не были найдены еще: whence, where, whereis, whatis, hashв таком порядке.


Сценарий оболочки для предоставления whichисполняемого файла.

#! /bin/sh
set -ef; oldIFS=$IFS; IFS=:

say()( IFS=" "; printf "%s\n" "$*"; )
say "Simplified version of which."
usage(){ say Usage: "$0" [-f] args; }
if [ "$#" -eq 0 ]; then say Missing argument(s); usage; exit 2; fi

firstmatch=0
while getopts f whichopts; do
    case "$whichopts" in
        f) firstmatch=1 ;;
        ?) usage; exit 3 ;;
    esac
done
[ "$OPTIND" -gt 1 ] && shift `expr "$OPTIND" - 1`

allret=0; [ "$#" -eq 0 ] && allret=1
for program in "$@"; do
    ret=1
    for element in $PATH''; do
        case "$program" in
            */*) element="$program"; loop=0;;
            *)   element="${element:-.}/$program"; loop=1;;
        esac
        if [ -f "$element" ] && [ -x "$element" ]; then
            say "$element"
            ret=0
            if [ "$firstmatch" -eq 1 ] || [ "$loop" -eq 0 ]; then break; fi
        fi
    done
    [ "$ret" -eq 1 ] && allret=1
done

IFS="$oldIFS"
exit "$allret"
Исаака
источник
0

Мы часто слышим то, чего следует избегать. Почему? Что мы должны использовать вместо этого?

Я никогда не слышал этого. Пожалуйста, приведите конкретные примеры. Я бы побеспокоился о вашем дистрибутиве linux и установленных пакетах программ, так как это откуда which!

SLES 11,4 x86-64

в версии tcsh 6.18.01:

> which which

which: shell built-in command.

в bash версии 3.2-147:

> which which

/usr/bin/which

> which -v

GNU which v2.19, Copyright (C) 1999 - 2008 Carlo Wood.
GNU which comes with ABSOLUTELY NO WARRANTY;
This program is free software; your freedom to use, change
and distribute this program is protected by the GPL.

whichявляется частью util-linux стандартного пакета, распространяемого Организацией ядра Linux для использования в составе операционной системы Linux. Он также предоставляет эти другие файлы

/bin/dmesg
/bin/findmnt
/bin/logger
/bin/lsblk
/bin/more
/bin/mount
/bin/umount
/sbin/adjtimex
/sbin/agetty
/sbin/blkid
/sbin/blockdev
/sbin/cfdisk
/sbin/chcpu
/sbin/ctrlaltdel
/sbin/elvtune
/sbin/fdisk
/sbin/findfs
/sbin/fsck
/sbin/fsck.cramfs
/sbin/fsck.minix
/sbin/fsfreeze
/sbin/fstrim
/sbin/hwclock
/sbin/losetup
/sbin/mkfs
/sbin/mkfs.bfs
/sbin/mkfs.cramfs
/sbin/mkfs.minix
/sbin/mkswap
/sbin/nologin
/sbin/pivot_root
/sbin/raw
/sbin/sfdisk
/sbin/swaplabel
/sbin/swapoff
/sbin/swapon
/sbin/switch_root
/sbin/wipefs
/usr/bin/cal
/usr/bin/chrp-addnote
/usr/bin/chrt
/usr/bin/col
/usr/bin/colcrt
/usr/bin/colrm
/usr/bin/column
/usr/bin/cytune
/usr/bin/ddate
/usr/bin/fallocate
/usr/bin/flock
/usr/bin/getopt
/usr/bin/hexdump
/usr/bin/i386
/usr/bin/ionice
/usr/bin/ipcmk
/usr/bin/ipcrm
/usr/bin/ipcs
/usr/bin/isosize
/usr/bin/line
/usr/bin/linux32
/usr/bin/linux64
/usr/bin/look
/usr/bin/lscpu
/usr/bin/mcookie
/usr/bin/mesg
/usr/bin/mkzimage_cmdline
/usr/bin/namei
/usr/bin/rename
/usr/bin/renice
/usr/bin/rev
/usr/bin/script
/usr/bin/scriptreplay
/usr/bin/setarch
/usr/bin/setsid
/usr/bin/setterm
/usr/bin/tailf
/usr/bin/taskset
/usr/bin/time
/usr/bin/ul
/usr/bin/uname26
/usr/bin/unshare
/usr/bin/uuidgen
/usr/bin/wall
/usr/bin/whereis
/usr/bin/which
/usr/bin/write
/usr/bin/x86_64
/usr/sbin/addpart
/usr/sbin/delpart
/usr/sbin/fdformat
/usr/sbin/flushb
/usr/sbin/freeramdisk
/usr/sbin/klogconsole
/usr/sbin/ldattach
/usr/sbin/partx
/usr/sbin/rcraw
/usr/sbin/readprofile
/usr/sbin/rtcwake
/usr/sbin/setctsid
/usr/sbin/tunelp

моя util-linuxверсия 2.19. Примечания к выпуску можно легко найти до версии v2.13 от 28 августа 2007 года. Не уверен, в чем смысл или цель этого, конечно, это не было ответа в этой длинной вещи, проголосовавшей 331 раз.

рон
источник
2
Обратите внимание, что в вопросе не упоминается, к какому Unix он относится. Linux - только один из немногих.
Кусалананда
2
Как вы which -vпоказываете, это GNU, который (экстравагантный, упомянутый в другом ответе и никоим образом не специфичный для Linux), а не util-linux, который AFAIK никогда не включал в себя whichутилиту. util-linux 2.19 с 2011 года, GNU с 2.19 с 2008 года.
Стефан Шазелас