Чому б не використати "який"? Що тоді використовувати?


328

Шукаючи шлях до виконуваного або перевірити , що станеться , якщо ввести ім'я команди в оболонці 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) або надати псевдонім, як визначено в конфігурації деяких інших оболонок ...
Stéphane Chazelas

1
@rudimeier, знову ж таки, існує ряд умов, за яких багато whichреалізацій не дають вам навіть того, lsщо було б знайдено при пошуку $PATH(незалежно від того, що lsможе викликати вашу оболонку). sh -c 'command -v ls', або zsh -c 'rpm -q --whatprovides =ls'більше шансів дати вам правильну відповідь. Справа тут у тому, що whichце зламана спадщина csh.
Стефан Шазелас

Відповіді:


366

Ось все, про що ви ніколи не думали, що ніколи не захочете знати про це:

Підсумок

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

У 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робить його менш простим у використанні в сценаріях.

Оболонка Борна для Unix версії 8 (не випущена в дикій природі) повинна була typeперейменована в whatis. І оболонка Plan9 (колишній наступник Unix) rc(і її похідні, як akangaі es) також є whatis.

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

Одночасно з потрясінням щодо проблем з авторським правом між AT&T та Berkeley, в кінці 80-х на початку 90-х з'явилося кілька реалізацій безкоштовних програмних оболонок. Вся оболонка Almquist (зола, яка повинна бути заміною оболонки Борна в BSD), реалізація ksh (pdksh) bash(спонсорована FSF) у публічному доступі , zshз'явилася між 1989 і 1991 роками.

Еш, хоч і мала бути заміною оболонки Борна, не мала 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команду реалізована у вигляді функції.

whichCSH сценарій тим часом був видалений з NetBSD (як це було в Tcsh вбудованої і не багато користі в інших оболонках), а функціональність додається до whereis(при виклику , як which, whereisповодиться як whichкрім того, що він тільки дивиться виконувані $PATH). У OpenBSD і FreeBSD whichтакож було змінено на написане на C, яке шукає $PATHлише команди .

Впровадження

Існують десятки реалізацій whichкоманди в різних Unices з різним синтаксисом і поведінкою.

На Linux (поруч із вбудованими в tcshта в 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є всюдисущим у всіх оболонках Борна (хоча, як зазначає @jarno, зверніть увагу на застереження / помилку, bashколи вони не перебувають у режимі POSIX або деякі нащадки оболонки Алквіста нижче в коментарях). 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не є дійсним виконуваним файлом.
  • якщо fooвбудований або функція чи псевдонім, command -v fooповертається foo. З такими оболонками, як ash, pdkshабо zsh, він також може повернутися, fooякщо він $PATHмістить порожній рядок і fooв поточному каталозі є виконавчий файл. Є деякі обставини, коли вам може знадобитися це врахувати. Майте на увазі, наприклад, що список вбудованих файлів змінюється залежно від реалізації оболонки (наприклад, mountіноді вбудований для зайнятої скриньки 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навіть якщо цей шлях Байдуже » t існують.

Визначення, чи існує команда

Щоб дізнатися, чи дана команда існує стандартно, ви можете зробити:

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(як yash), whatis -p( rc, akanga) або вбудований which(як tcshабо zsh) в системах, де whichдоступний і не є cshсценарієм.

Якщо ці умови виконані, то:

echo=$(which echo)

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

В інших оболонках вам краще:

  • zsh : echo==echoабо echo=$commands[echo]абоecho=${${:-echo}:c}
  • ksh , zsh :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оптимізацією оболонки, якщо вона не бачить спеціального символу оболонки (крім пробілу). Це стосується також оператора backtick:

$ 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
Stéphane Chazelas

12
Прочитавши цю публікацію, я знайшов багато контексту для відповіді, але не самої відповіді. Де в цій публікації написано насправді, чому б не використовувати which, на відміну від речей, які ви, можливо, намагаєтеся whichробити, історію which, виконання which, інші команди, щоб виконувати пов'язані завдання чи причини фактично використовувати which? Чому інші команди краще ? Що вони роблять інакше which? Як вони уникають її підводних каменів? Ця відповідь фактично витрачає більше слів на проблеми з альтернативами, ніж проблеми з which.
користувач62251

1
Всупереч тому, що відповідь стверджує, command -vне перевіряє на виконання дозволу, принаймні, якщо ви викликаєте це чисто аргументом імені файлу без шляху. Я перевірив тире 0,5.8 та GNU bash 4.3.48.
jarno

2
@ StéphaneChazelas Якщо я створити новий файл до touch /usr/bin/mytestfileта після цього запустіть command -v mytestfile, він дасть шлях (тоді як which mytestfileні).
jarno

2
@jarno, о так, ти маєш рацію. bashбуде влаштовуватися на невиконаний файл, якщо він не може знайти виконавчий файл, тому це "ОК" (хоча на практиці скоріше command -v/ typeповернути помилку), оскільки це команда, яку він намагатиметься виконати під час запуску mytestfile, але dashповедінка є помилковою, як ніби cmdперед виконавчим файлом стоїть невиконаний файл , він command -vповертає невиконавший, тоді як виконання cmdбуде виконувати виконуваний файл (неправильний також хеширується). FreeBSD sh(також заснований на ash) має таку ж помилку. zsh, yash, ksh, mksh, bash як sh добре.
Стефан Шазелас

47

Причини, через які можна не хотіти використовувати which, вже були пояснені, але ось декілька прикладів у кількох системах, де whichнасправді відбувається збій.

На оболонках, схожих на Борна, ми порівнюємо результат виведення whichз виведенням type( typeбудучи оболонкою вбудованою, це означає, що це основна істина, оскільки саме оболонка говорить нам, як вона викликала б команду).

Багато випадків є кутовими справами, але майте на увазі, що which/ typeчасто використовуються у кутових випадках (щоб знайти відповідь на несподівану поведінку на зразок: чому на землі така команда поводиться так, до якої я дзвоню? ).

Більшість систем, більшість оболонок Борна: функціонують

Найбільш очевидний випадок для функцій:

$ 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;
}

Більшість систем, більшість оболонок Борна: вбудовані

Інший очевидний випадок або ключові слова вбудованих функцій , як і 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, баш

$ 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«з , 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пошуку, але означає використання системи 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типовий параметр, PATHякий відрізняється від ksh93's

Не краще з GNU, whichякий звітує:

which: no which in ((null))

(Цікаво, дійсно є /usr/local/bin/whichв моїй системі , яка насправді є akangaсценарій , який прийшов з akangarcпохідною оболонки , де за замовчуванням 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.


mkshнасправді використовується дещо інше за замовчуванням $PATH: спочатку _PATH_DEFPATHвикористовується константа часу компіляції операційної системи (найчастіше на BSD), потім confstr(_CS_PATH, …)використовується (POSIX), і якщо обидва не існують або не працюють, /bin:/usr/bin:/sbin:/usr/sbinвикористовується.
mirabilos

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) надає щось неактуальне
Stéphane Chazelas

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, риби та 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походить!

СЛЕСИ 11,4 x86-64

у tcsh версії 6.18.01:

> which which

which: shell built-in command.

у версії 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 - лише одна з небагатьох.
Kusalananda

2
Як which -vпоказує, це GNU, який (екстравагантний згаданий в іншій відповіді і жодним чином не специфічний для Linux), а не util-linux, який AFAIK ніколи не включав whichутиліту. util-linux 2.19 - з 2011 року, GNU - 2.19, з 2008 року.
Stéphane Chazelas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.