Mengapa tidak menggunakan "yang"? Lalu apa yang harus digunakan?

328

Ketika mencari jalan untuk dapat dieksekusi atau memeriksa apa yang akan terjadi jika Anda memasukkan nama perintah di shell Unix, ada sejumlah utilitas yang berbeda ( which, type, command, whence, where, whereis, whatis, hash, dll).

Kita sering mendengar yang whichharus dihindari. Mengapa? Apa yang harus kita gunakan?

Stéphane Chazelas
sumber
3
Saya pikir sebagian besar argumen yang menentang penggunaan whichmengasumsikan konteks shell interaktif. Pertanyaan ini ditandai / mudah dibawa. Jadi saya menafsirkan pertanyaan dalam konteks ini sebagai "apa yang harus digunakan daripada whichmenemukan executable pertama dari nama yang diberikan dalam $PATH". Sebagian besar jawaban dan alasan menentang whichberurusan dengan alias, bawaan dan fungsi, yang dalam kebanyakan skrip shell portabel di dunia nyata hanya untuk kepentingan akademis. Alias ​​yang didefinisikan secara lokal tidak diwariskan saat menjalankan skrip shell (kecuali jika Anda sumbernya dengan .).
MattBianco
5
@MattBianco, ya, csh(dan whichmasih berupa cshskrip pada sebagian besar Unices komersial) tidak dibaca ~/.cshrcsaat tidak interaktif. Itu sebabnya Anda akan melihat skrip csh biasanya dimulai dengan #! /bin/csh -f. whichbukan karena itu bertujuan untuk memberi Anda alias, karena itu dimaksudkan sebagai alat untuk (interaktif) pengguna csh. Kerang POSIX yang dimiliki pengguna command -v.
Stéphane Chazelas
@rudimeier, maka jawabannya akan selalu kecuali jika shell Anda (t)csh(atau Anda tidak keberatan jika itu tidak memberi Anda hasil yang benar), gunakan typeatau command -vsebagai gantinya . Lihat jawaban untuk alasannya .
Stéphane Chazelas
1
@rudimeier, ( stat $(which ls)salah karena beberapa alasan (hilang --, tanda kutip hilang), tidak hanya penggunaan which). Anda akan menggunakan stat -- "$(command -v ls)". Itu mengasumsikan lsmemang perintah yang ditemukan pada sistem file (bukan builtin dari shell Anda, atau fungsi alias). whichmungkin memberi Anda jalur yang salah (bukan jalur yang akan dijalankan shell Anda jika Anda memasukkan ls) atau memberi Anda alias seperti yang ditentukan dalam konfigurasi beberapa shell lain ...
Stéphane Chazelas
1
@rudimeier, sekali lagi, ada sejumlah kondisi di mana banyak whichimplementasi tidak akan memberi Anda bahkan lsyang akan ditemukan oleh pencarian $PATH(terlepas dari apa yang lsmungkin diminta dalam shell Anda). sh -c 'command -v ls', atau zsh -c 'rpm -q --whatprovides =ls'lebih mungkin memberi Anda jawaban yang benar. Intinya di sini adalah whichwarisan yang rusak csh.
Stéphane Chazelas

Jawaban:

366

Inilah semua yang Anda tidak pernah berpikir Anda tidak akan mau tahu tentang hal itu:

Ringkasan

Untuk mendapatkan pathname dari executable dalam skrip shell Bourne-like (ada beberapa peringatan; lihat di bawah):

ls=$(command -v ls)

Untuk mengetahui apakah ada perintah yang diberikan:

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

Pada prompt shell seperti Bourne interaktif:

type ls

The whichperintah adalah warisan yang rusak dari C-Shell dan lebih baik ditinggalkan sendirian di kerang Bourne-seperti.

Gunakan Kasing

Ada perbedaan antara mencari informasi itu sebagai bagian dari skrip atau secara interaktif di prompt shell.

Pada prompt shell, kasus penggunaan yang umum adalah: perintah ini berperilaku aneh, apakah saya menggunakan yang benar? Apa yang sebenarnya terjadi ketika saya mengetik mycmd? Bisakah saya melihat lebih jauh apa itu?

Dalam hal ini, Anda ingin tahu apa yang dilakukan shell Anda ketika Anda menjalankan perintah tanpa benar-benar menjalankan perintah.

Dalam skrip shell, cenderung sangat berbeda. Dalam skrip shell tidak ada alasan mengapa Anda ingin tahu di mana atau apa perintah itu jika semua yang ingin Anda lakukan adalah menjalankannya. Secara umum, apa yang ingin Anda ketahui adalah jalur yang dapat dieksekusi, sehingga Anda dapat memperoleh lebih banyak informasi darinya (seperti jalur ke file lain terkait dengan itu, atau membaca informasi dari konten file yang dapat dieksekusi di jalur itu).

Interaktif, Anda mungkin ingin tahu tentang semua yang my-cmdperintah yang tersedia pada sistem, dalam skrip, jarang jadi.

Sebagian besar alat yang tersedia (seperti yang sering terjadi) telah dirancang untuk digunakan secara interaktif.

Sejarah

Sedikit sejarah dulu.

Kerang Unix awal hingga akhir 70-an tidak memiliki fungsi atau alias. Hanya mencari tradisional executable di $PATH. cshmemperkenalkan alias sekitar tahun 1978 (meskipun cshpertama kali dirilis pada 2BSDbulan Mei 1979), dan juga pemrosesan .cshrcbagi pengguna untuk menyesuaikan shell (setiap shell, seperti yang cshdibaca .cshrcbahkan ketika tidak interaktif seperti dalam skrip).

sementara shell Bourne pertama kali dirilis di Unix V7 pada tahun 1979, dukungan fungsi hanya ditambahkan jauh kemudian (1984 dalam SVR2), dan lagi pula, itu tidak pernah memiliki beberapa rcfile ( .profileyaitu untuk mengkonfigurasi lingkungan Anda, bukan shell per se ).

csh mendapat jauh lebih populer daripada shell Bourne (meskipun memiliki sintaks yang jauh lebih buruk daripada shell Bourne) itu menambahkan banyak fitur yang lebih nyaman dan menyenangkan untuk penggunaan interaktif.

Pada 3BSD(1980), whichskrip csh ditambahkan bagi cshpengguna untuk membantu mengidentifikasi sebuah executable, dan skrip ini hampir tidak berbeda dengan yang dapat Anda temukan whichdi banyak Unix komersial saat ini (seperti Solaris, HP / UX, AIX atau Tru64).

Skrip itu membaca pengguna ~/.cshrc(seperti semua cshskrip lakukan kecuali dipanggil dengan csh -f), dan mencari nama perintah yang disediakan dalam daftar alias dan dalam $path(array yang cshmempertahankan berdasarkan $PATH).

Ini dia, whichmenjadi yang pertama untuk shell paling populer saat itu (dan cshmasih populer sampai pertengahan 90-an), yang merupakan alasan utama mengapa ia didokumentasikan dalam buku dan masih banyak digunakan.

Perhatikan bahwa, bahkan untuk cshpengguna, whichskrip csh tidak selalu memberi Anda informasi yang benar. Itu mendapatkan alias yang didefinisikan ~/.cshrc, bukan yang Anda mungkin telah didefinisikan nanti pada prompt atau misalnya dengan memasukkan file sourcelain csh, dan (meskipun itu bukan ide yang baik), PATHmungkin didefinisikan ulang ~/.cshrc.

Menjalankan whichperintah itu dari shell Bourne, masih akan mencari alias yang didefinisikan di Anda ~/.cshrc, tetapi jika tidak memilikinya karena Anda tidak menggunakannya csh, itu mungkin masih memberi Anda jawaban yang benar.

Fungsionalitas yang serupa tidak ditambahkan ke shell Bourne hingga 1984 di SVR2 dengan typeperintah builtin. Fakta bahwa ia dibangun (sebagai lawan dari skrip eksternal) berarti ia dapat memberi Anda informasi yang benar (sampai batas tertentu) karena memiliki akses ke internal shell.

typePerintah awal mengalami masalah yang sama seperti whichskrip yang tidak mengembalikan status keluar kegagalan jika perintah tidak ditemukan. Juga, untuk executable, sebaliknya which, ini menghasilkan sesuatu seperti ls is /bin/lsbukannya /bin/lsyang membuatnya kurang mudah digunakan dalam skrip.

Unix Version 8's (tidak dirilis di wild) Bourne shell telah typedibangun ulang namanya menjadi whatis. Dan shell Plan9 (penerus Unix yang pernah menjadi) juga rc(dan turunannya seperti akangadan es) whatisjuga.

Shell Korn (subset yang mendasari definisi sh POSIX), dikembangkan pada pertengahan 80-an tetapi tidak banyak tersedia sebelum 1988, menambahkan banyak cshfitur (editor baris, alias ...) di atas shell Bourne . Ia menambahkan whencebuiltin -nya sendiri (sebagai tambahan type) yang mengambil beberapa opsi ( -vuntuk menyediakan typekeluaran verbose-like, dan -phanya mencari executable (bukan alias / fungsi ...)).

Bersamaan dengan kekacauan terkait dengan masalah hak cipta antara AT&T dan Berkeley, beberapa implementasi perangkat lunak shell gratis muncul di akhir tahun 80-an awal tahun 90-an. Semua shell Almquist (abu, untuk menjadi pengganti shell Bourne di BSD), implementasi domain publik ksh (pdksh), bash(disponsori oleh FSF), zshkeluar di antara tahun 1989 dan 1991.

Ash, meskipun dimaksudkan sebagai pengganti shell Bourne tidak memiliki typebuiltin sampai jauh kemudian (di NetBSD 1.3 dan FreeBSD 2.3), meskipun sudah hash -v. OSF / 1 /bin/shmemiliki typebuiltin yang selalu mengembalikan 0 hingga OSF / 1 v3.x. bashtidak menambahkan whencetetapi menambahkan -popsi untuk typemencetak jalur ( type -pakan seperti whence -p) dan -amelaporkan semua perintah yang cocok. tcshdibuat whichbuiltin dan menambahkan whereperintah bertindak seperti bash's type -a. zshmemiliki semuanya.

The fishshell (2005) memiliki typeperintah diimplementasikan sebagai fungsi.

The whichScript csh Sementara itu dihapus dari NetBSD (seperti yang builtin di tcsh dan tidak banyak digunakan dalam kerang lainnya), dan fungsi ditambahkan ke whereis(ketika dipanggil sebagai which, whereisberperilaku seperti whichkecuali bahwa itu hanya mendongak executable di $PATH). Di OpenBSD dan FreeBSD, whichjuga diubah menjadi satu yang ditulis dalam C yang mencari perintah $PATHsaja.

Implementasi

Ada lusinan implementasi whichperintah pada berbagai Unix dengan sintaks dan perilaku yang berbeda.

Di Linux (di samping yang builtin di dalam tcshdan zsh) kami menemukan beberapa implementasi. Pada sistem Debian terbaru misalnya, ini adalah skrip shell POSIX sederhana yang mencari perintah di $PATH.

busyboxjuga memiliki whichperintah.

Ada GNU whichyang mungkin merupakan yang paling mewah. Ia mencoba untuk memperluas apa yang dilakukan whichskrip csh ke shell lain: Anda dapat memberi tahu apa itu alias dan fungsi Anda sehingga dapat memberikan jawaban yang lebih baik (dan saya percaya beberapa distribusi Linux menetapkan beberapa alias global di sekitarnya untuk bashmelakukannya) .

zshmemiliki beberapa operator untuk memperluas ke jalur yang dapat dieksekusi: operator = ekspansi nama file dan :cpengubah ekspansi riwayat (di sini diterapkan pada ekspansi parameter ):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zsh, dalam zsh/parametersmodul juga membuat tabel hash perintah sebagai commandsarray asosiatif:

$ print -r -- $commands[ls]
/bin/ls

The whatisutilitas (kecuali untuk satu di Unix V8 Bourne shell atau Plan 9 rc/ es) tidak benar-benar terkait seperti itu untuk dokumentasi saja (greps database whatis, yaitu halaman man sinopsis).

whereisjuga ditambahkan pada 3BSDsaat yang sama seolah- whicholah itu ditulis C, tidak cshdan digunakan untuk mencari pada saat yang sama, halaman buku panduan dan sumber yang dapat dieksekusi tetapi tidak didasarkan pada lingkungan saat ini. Jadi sekali lagi, itu menjawab kebutuhan yang berbeda.

Sekarang, di bagian depan standar, POSIX menentukan perintah command -vdan -V(yang dulunya opsional sampai POSIX.2008). UNIX menentukan typeperintah (tidak ada opsi). Itu semua ( where, which, whencetidak ditentukan dalam standar apapun)

Hingga beberapa versi, typedan command -vmerupakan opsional dalam spesifikasi Pangkalan Linux Standar yang menjelaskan mengapa misalnya beberapa versi lama posh(meskipun berdasarkan pdkshkeduanya) tidak memiliki keduanya. command -vjuga ditambahkan ke beberapa implementasi shell Bourne (seperti pada Solaris).

Status Hari Ini

Status saat ini adalah bahwa typedan command -vada di mana-mana di semua cangkang Bourne-like (meskipun, seperti dicatat oleh @jarno, perhatikan peringatan / bug bashketika tidak dalam mode POSIX atau beberapa keturunan cangkang Almquist di bawah ini dalam komentar). tcshadalah satu-satunya shell di mana Anda ingin menggunakan which(karena tidak typeada dan whichdibangun).

Di dalam shell selain tcshdan zsh, whichdapat memberi tahu Anda jalur executable yang diberikan selama tidak ada alias atau fungsi dengan nama yang sama di salah satu dari kami ~/.cshrc, ~/.bashrcatau file startup shell apa pun dan Anda tidak menentukan $PATHdi ~/.cshrc. Jika Anda memiliki alias atau fungsi yang ditentukan untuknya, itu mungkin atau mungkin tidak memberi tahu Anda tentang hal itu, atau memberi tahu Anda hal yang salah.

Jika Anda ingin tahu tentang semua perintah dengan nama yang diberikan, tidak ada yang portabel. Anda akan menggunakan wheredi tcshatau zsh, type -adalam bashatau zsh, whence -adi ksh93 dan kerang lainnya, Anda dapat menggunakan typedalam kombinasi dengan which -ayang mungkin bekerja.

Rekomendasi

Mendapatkan pathname ke executable

Sekarang, untuk mendapatkan pathname dari executable dalam skrip, ada beberapa peringatan:

ls=$(command -v ls)

akan menjadi cara standar untuk melakukannya.

Ada beberapa masalah:

  • Tidak mungkin mengetahui jalur yang dapat dieksekusi tanpa mengeksekusinya. Semua type, which, command -v... semua heuristik digunakan untuk mengetahui jalan. Mereka loop melalui $PATHkomponen dan menemukan file non-direktori pertama yang Anda jalankan izinnya. Namun, tergantung pada shell, ketika menjalankan perintah, banyak dari mereka (Bourne, AT&T ksh, zsh, ash ...) hanya akan menjalankannya dalam urutan $PATHsampai execvepanggilan sistem tidak kembali dengan kesalahan . Misalnya jika $PATHberisi /foo:/bardan Anda ingin mengeksekusi ls, mereka akan terlebih dahulu mencoba mengeksekusi /foo/lsatau jika itu gagal /bar/ls. Sekarang eksekusi/foo/lsmungkin gagal karena Anda tidak memiliki izin eksekusi tetapi juga karena banyak alasan lain, seperti itu bukan eksekusi yang valid. command -v lsakan melaporkan /foo/lsjika Anda memiliki izin eksekusi /foo/ls, tetapi menjalankan lsmungkin benar-benar berjalan /bar/lsjika /foo/lsbukan merupakan eksekusi yang valid.
  • jika foomerupakan builtin atau fungsi atau alias, command -v fookembali foo. Dengan beberapa shell seperti ash, pdkshatau zsh, ia juga dapat kembali foojika $PATHmenyertakan string kosong dan ada foofile yang dapat dieksekusi di direktori saat ini. Ada beberapa kondisi di mana Anda mungkin perlu memperhitungkannya. Ingatlah misalnya bahwa daftar bawaan bervariasi dengan implementasi shell (misalnya, mountkadang-kadang dibangun untuk busybox sh), dan misalnya bashbisa mendapatkan fungsi dari lingkungan.
  • jika $PATHberisi komponen path relatif (biasanya .atau string kosong yang keduanya merujuk ke direktori saat ini tetapi bisa berupa apa saja), tergantung pada shell, command -v cmdmungkin tidak menampilkan path absolut. Jadi jalur yang Anda dapatkan pada saat Anda menjalankan command -vtidak akan lagi berlaku setelah Anda di cdtempat lain.
  • Anekdot: dengan shell ksh93, jika /opt/ast/bin(meskipun jalan yang tepat dapat bervariasi pada sistem yang berbeda saya percaya) di dalam kamu $PATH, ksh93 akan membuat tersedia beberapa builtin tambahan ( chmod, cmp, cat...), tapi command -v chmodakan kembali /opt/ast/bin/chmodbahkan jika jalan itu doesn' tidak ada.

Menentukan apakah ada perintah

Untuk mengetahui apakah perintah yang diberikan ada secara standar, Anda dapat melakukan:

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

Di mana orang mungkin ingin menggunakan which

(t)csh

Di cshdan tcsh, Anda tidak punya banyak pilihan. Dalam tcsh, itu bagus seperti whichbuiltin. Dalam csh, itu akan menjadi whichperintah sistem , yang mungkin tidak melakukan apa yang Anda inginkan dalam beberapa kasus.

temukan perintah hanya di beberapa shell

Suatu kasus di mana masuk akal untuk digunakan whichadalah jika Anda ingin mengetahui lintasan sebuah perintah, mengabaikan potensi builtin shell atau fungsi dalam bash, csh(tidak tcsh) dash, atau Bourneskrip shell, yaitu shell yang tidak memiliki whence -p(suka kshatau zsh) , command -ev(seperti yash), whatis -p( rc, akanga) atau builtin which(suka tcshatau zsh) pada sistem di mana whichtersedia dan bukan cshskrip.

Jika persyaratan tersebut dipenuhi, maka:

echo=$(which echo)

akan memberikan jalan yang pertama echodi $PATH(kecuali dalam kasus sudut), terlepas dari apakah echojuga terjadi menjadi shell builtin / alias / fungsi atau tidak.

Di shell lain, Anda lebih suka:

  • zsh : echo==echoatau echo=$commands[echo]atauecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • yash :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(waspadalah terhadap jalur dengan spasi)
  • ikan :set echo (type -fp echo)

Perhatikan bahwa jika semua yang Anda ingin lakukan adalah menjalankan bahwa echoperintah, Anda tidak harus mendapatkan jalan, Anda hanya dapat melakukan:

env echo this is not echoed by the builtin echo

Misalnya, dengan tcsh, untuk mencegah builtin whichdigunakan:

set Echo = "`env which echo`"

ketika Anda membutuhkan perintah eksternal

Kasus lain di mana Anda mungkin ingin menggunakan whichadalah ketika Anda benar - benar membutuhkan perintah eksternal. POSIX mensyaratkan bahwa semua shell builtin (seperti command) juga tersedia sebagai perintah eksternal, tetapi sayangnya itu tidak berlaku untuk commandbanyak sistem. Sebagai contoh, jarang menemukan commandperintah pada sistem operasi berbasis Linux sementara kebanyakan dari mereka memiliki whichperintah (meskipun berbeda dengan opsi dan perilaku yang berbeda).

Kasus di mana Anda mungkin menginginkan perintah eksternal berada di mana pun Anda akan menjalankan perintah tanpa menggunakan shell POSIX.

Fungsi system("some command line"), popen()... C atau berbagai bahasa memang memanggil shell untuk mem-parsing baris perintah itu, jadi system("command -v my-cmd")kerjakanlah di dalamnya. Pengecualian untuk hal itu adalah perlyang mengoptimalkan shell jika tidak melihat karakter khusus shell (selain ruang). Itu juga berlaku untuk operator backtick-nya:

$ 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

Penambahan bahwa :;kekuatan di atas perluntuk memanggil shell di sana. Dengan menggunakan which, Anda tidak perlu menggunakan trik itu.

Stéphane Chazelas
sumber
24
@ Jo, whichadalah cshskrip di banyak Unite komersial. Alasannya historis, itu sebabnya saya memberikan sejarah, jadi orang mengerti dari mana asalnya, mengapa orang terbiasa menggunakannya dan mengapa sebenarnya tidak ada alasan Anda harus menggunakannya. Dan ya, beberapa orang menggunakan (t) csh. Belum semua orang menggunakan Linux
Stéphane Chazelas
12
Setelah membaca posting ini, saya telah menemukan banyak konteks untuk jawabannya, tetapi bukan jawaban itu sendiri. Di mana dalam posting ini sebenarnya dikatakan mengapa tidak digunakan which, sebagai lawan dari hal-hal yang mungkin Anda coba whichlakukan, sejarah which, implementasi which, perintah lain untuk melakukan tugas terkait, atau alasan untuk benar-benar menggunakan which? Mengapa perintah lain lebih baik ? Apa yang mereka lakukan secara berbeda which? Bagaimana mereka menghindari jebakannya? Jawaban ini sebenarnya menghabiskan lebih banyak kata pada masalah dengan alternatif daripada masalah dengan which.
user62251
1
Berbeda dengan apa yang diklaim oleh jawabannya, command -vtidak memeriksa izin eksekusi, setidaknya jika Anda menyebutnya dengan argumen nama file murni tanpa jalur. Saya menguji dengan dash 0.5.8 dan GNU bash 4.3.48.
jarno
2
@ StéphaneChazelas Jika saya membuat file baru touch /usr/bin/mytestfiledan kemudian dijalankan command -v mytestfile, itu akan memberikan path (padahal which mytestfiletidak).
jarno
2
@ Jarno, oh ya, Anda benar. bashakan menyelesaikan file yang tidak dapat dieksekusi jika tidak dapat menemukan file yang dapat dieksekusi, jadi itu "OK" (meskipun dalam praktiknya seseorang lebih suka command -v/ typemengembalikan kesalahan) karena itulah perintah yang akan coba dijalankan ketika Anda menjalankan mytestfile, tetapi dashperilaku buggy, seolah-olah ada yang tidak dapat dieksekusi di cmddepan yang dapat dieksekusi, command -vmengembalikan yang tidak dapat dieksekusi sementara mengeksekusi cmdakan mengeksekusi yang dapat dieksekusi (yang salah juga hash). FreeBSD sh(juga berdasarkan ash) memiliki bug yang sama. zsh, yash, ksh, mksh, bash karena dia OK.
Stéphane Chazelas
47

Alasan mengapa seseorang mungkin tidak ingin menggunakan whichsudah dijelaskan, tetapi berikut adalah beberapa contoh pada beberapa sistem di mana whichsebenarnya gagal.

Pada cangkang mirip Bourne, kami membandingkan keluaran whichdengan keluaran type( typemenjadi cangkang yang dibangun, itu dimaksudkan sebagai kebenaran dasar, karena cangkang memberi tahu kami bagaimana ia akan memanggil sebuah perintah).

Banyak kasus yang sudut kasus, tetapi ingatlah bahwa which/ typesering digunakan dalam kasus-kasus sudut (untuk menemukan jawaban untuk perilaku tak terduga seperti: ? Mengapa di bumi adalah bahwa perintah berperilaku seperti itu, mana yang saya menelepon ).

Sebagian besar sistem, sebagian besar shell mirip Bourne: fungsi

Kasus yang paling jelas adalah untuk fungsi:

$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls

Alasannya adalah bahwa whichhanya melaporkan tentang executable, dan kadang-kadang tentang alias (meskipun tidak selalu yang dari shell Anda ), bukan fungsi.

GNU yang man page memiliki contoh yang rusak (karena mereka lupa kutip $@) tentang bagaimana menggunakannya untuk melaporkan fungsi juga, tetapi seperti halnya untuk alias, karena ia tidak mengimplementasikan parser sintaksis shell, itu mudah dibodohi:

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

Sebagian besar sistem, sebagian besar cangkang mirip Bourne: builtin

Kasus lain yang jelas adalah builtin atau kata kunci, karena whichmenjadi perintah eksternal tidak memiliki cara untuk mengetahui builtin apa yang dimiliki shell Anda (dan beberapa shell suka zsh, bashatau kshdapat memuat builtin secara dinamis):

$ 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

(itu tidak berlaku untuk di zshmana whichbuiltin)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 dan banyak lainnya:

$ 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

Itu karena pada sebagian besar Unices komersial, which(seperti dalam implementasi asli pada 3BSD) adalah cshskrip yang berbunyi ~/.cshrc. Alias ​​yang akan dilaporkan adalah yang didefinisikan di sana terlepas dari alias yang saat ini telah Anda tentukan dan terlepas dari shell yang sebenarnya Anda gunakan.

Di HP / UX atau Tru64:

% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls

(versi Solaris dan AIX telah memperbaiki masalah itu dengan menyimpan $pathsebelum membaca ~/.cshrcdan memulihkannya sebelum mencari perintah)

$ 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

Atau:

$ 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

(tentu saja, menjadi cshskrip yang tidak dapat Anda harapkan berfungsi dengan argumen yang berisi spasi ...)

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='

Di sistem itu, ada alias yang didefinisikan di seluruh sistem yang membungkus whichperintah GNU .

Output palsu karena whichmembaca output dari bash's aliastetapi tidak tahu bagaimana untuk mengurai dengan benar dan menggunakan heuristik (satu alias per baris, terlihat untuk pertama ditemukan perintah setelah |, ;, &...)

Hal terburuk pada CentOS adalah zshmemiliki whichperintah built - in yang sangat baik tetapi CentOS berhasil memecahkannya dengan menggantinya dengan alias tidak berfungsi untuk GNU which.

Debian 7.0, ksh93:

(meskipun berlaku untuk kebanyakan sistem dengan banyak cangkang)

$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which

Di Debian, /bin/whichadalah /bin/shskrip. Dalam kasus saya, shmenjadi dashtetapi sama saja bash.

Unset PATHbukan untuk menonaktifkan PATHpencarian, tetapi berarti menggunakan PATH default sistem yang sayangnya pada Debian, tidak ada yang setuju ( dashdan bashmemiliki /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zshmemiliki /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93memiliki /bin:/usr/bin, mkshmemiliki /usr/bin:/bin( $(getconf PATH)), execvp()(seperti di env) telah :/bin:/usr/bin(ya, mencari di direktori saat ini terlebih dahulu! )).

Itulah sebabnya whichmendapatkan salah di atas karena itu menggunakan dash'standar s PATHyang berbeda dari ksh93' s

Tidak lebih baik dengan GNU whichyang melaporkan:

which: no which in ((null))

(Menariknya, memang ada /usr/local/bin/whichpada sistem saya yang sebenarnya merupakan akangaskrip yang datang dengan akanga( rcturunan shell di mana defaultnya PATHadalah /usr/ucb:/usr/bin:/bin:.))

bash, sistem apa pun:

Yang dimaksud Chris dalam jawabannya :

$ 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

Juga setelah menelepon hashsecara manual:

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

Sekarang kasus di mana whichdan kadang typegagal:

$ 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

Sekarang, dengan beberapa cangkang:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

Dengan yang lain:

$ foo
a/foo

Baik whichatau typebisa tahu sebelumnya bahwa b/footidak dapat dijalankan. Beberapa kerang seperti bash, kshatau yash, saat menjalankan foomemang akan mencoba untuk menjalankan b/foodan melaporkan kesalahan, sementara yang lain (seperti zsh, ash, csh, Bourne, tcsh) akan menjalankan a/foopada kegagalan execve()sistem panggilan b/foo.

Stéphane Chazelas
sumber
mkshsebenarnya menggunakan sesuatu yang berbeda untuk default $PATH: pertama, konstanta waktu kompilasi sistem operasi _PATH_DEFPATHdigunakan (paling umum pada BSD), kemudian, confstr(_CS_PATH, …)digunakan (POSIX), dan jika keduanya tidak ada atau gagal, /bin:/usr/bin:/sbin:/usr/sbindigunakan.
mirabilos
1
Dalam contoh pertama Anda, meskipun lsmerupakan fungsi yang digunakannya lsdari PATH. Dan whichboleh mengatakan kepada Anda yang mana yang digunakan /usr/bin/ls atau /usr/local/bin/ls. Saya tidak melihat "Mengapa tidak menggunakan yang" ....
rudimeier
@rudimeier, itu which lsakan memberi saya /bin/lsterlepas dari apakah lsfungsi panggilan /bin/lsatau /opt/gnu/bin/lsatau diratau tidak sama sekali. TKI, which(yang implementasi, IMMV) memberikan sesuatu yang tidak relevan
Stéphane Chazelas
1
@ StéphaneChazelas. Tidak tidak Tidak. Saya sudah tahu bahwa lsfungsi saya adalah. Saya tahu bahwa saya lsfungsi memanggil lsdari PATH. Sekarang whichberi tahu saya di mana file itu. Anda hanya melihat satu kasus penggunaan tunggal: "Apa yang akan dilakukan shell saya dengan perintah ini." Untuk kasus penggunaan whichini salah, benar. Tetapi ada kasus penggunaan lain di mana (GNU) whichadalah hal yang tepat.
rudimeier
@rudimeter, tergantung pada whichimplementasinya. Beberapa akan memberi tahu Anda itu adalah alias (jika Anda memiliki alias yang dikonfigurasi, atau jika ada ~/.cshrcdi rumah Anda yang memiliki alias seperti itu), beberapa akan memberi Anda jalur tetapi yang salah dalam beberapa kondisi. sh -c 'command -v ls', meskipun tidak sempurna masih lebih mungkin untuk memberikan jawaban yang tepat untuk persyaratan yang berbeda (dan juga standar).
Stéphane Chazelas
21

Satu hal yang (dari skim cepat saya) tampaknya Stephane tidak menyebutkan adalah bahwa whichtidak tahu tentang tabel hash path shell Anda. Ini memiliki efek yang mungkin mengembalikan hasil yang tidak mewakili apa yang sebenarnya dijalankan, yang membuatnya tidak efektif dalam debugging.

Chris Down
sumber
6

Dalam semangat UNIX: Buat setiap program melakukan satu hal dengan baik.

Jika tujuannya adalah untuk menjawab: Yang dieksekusi ada dengan ini nama?

Program yang dapat dieksekusi yang disediakan dengan sistem Debian adalah jawaban yang bagus. Yang disediakan dengan csh termasuk alias, yang merupakan sumber masalah. Yang disediakan oleh beberapa shell sebagai internal builtin memiliki tujuan yang berbeda. Baik gunakan yang dapat dieksekusi atau gunakan skrip yang disediakan di akhir jawaban ini.

Jika skrip ini digunakan, apa jawabannya bersih, sederhana dan bermanfaat.

Sasaran ini akan cocok dengan kalimat pertama dari pertanyaan Anda:

Saat mencari jalur ke executable ... ...

Jika Anda memiliki sistem yang tidak memiliki executable yang disebut (sebagian besar sistem linux memilikinya), Anda dapat membuatnya ~/bin/whichsebelumnya /bin/di PATH sehingga sistem yang dapat dieksekusi pribadi menimpa yang seperti di bagian bawah posting ini:

Eksekusi itu akan mencantumkan (secara default) semua executable yang ditemukan di PATH. Jika hanya yang pertama diperlukan, opsi -ftersedia.


Pada titik ini kita jatuh ke tujuan yang berbeda:

apa yang akan dieksekusi shell (setelah penguraian)

Itu berasal dari kalimat kedua Anda:

memeriksa apa yang akan terjadi jika Anda memasukkan nama perintah di shell Unix

Subjek kedua ini berusaha menemukan jawaban yang baik untuk pertanyaan yang agak sulit dijawab. Kerang memiliki pandangan yang berbeda, kasus sudut dan (minimal) interpretasi yang berbeda. Menambah itu:

ada sejumlah utilitas yang berbeda (yang, ketik, perintah, dari mana, di mana, di mana, whatis, hash, dll).

Dan tentu saja, semua upaya cocok dengan tujuan itu.


Hindari yang mana?

Kita sering mendengar apa yang harus dihindari.

Saya bertanya-tanya: Mengapa harus dikatakan jika whichberfungsi dengan baik (setidaknya dalam bahasa debian)?

Dalam semangat UNIX: Buat setiap program melakukan satu hal dengan baik.

The eksternal Program whichmelakukan satu hal: Cari dieksekusi pertama di PATH yang memiliki nama yang sama dengan nama perintah . Dan melakukannya dengan cukup baik.

Saya tidak tahu ada program atau utilitas lain yang menjawab pertanyaan ini dengan cara yang lebih mendasar. Dengan demikian, ini berguna dan dapat digunakan saat dibutuhkan.

Alternatif terdekat tampaknya adalah command -pv commandName:, tetapi itu juga akan melaporkan tentang builtin dan alias. Bukan jawaban yang sama.

Tentu saja whichterbatas, tidak menjawab semua pertanyaan, tidak ada alat yang bisa melakukan itu (yah, belum ...). Tetapi berguna ketika digunakan untuk menjawab pertanyaan yang dirancang untuk dijawab (yang tepat di atas). Banyak suka edterbatas dan kemudian sedmuncul (atau vi/ vim). Atau suka awkterbatas dan membuat Perl muncul dan meluas. Namun, ed, sedatau / dan awkmemiliki kasus penggunaan tertentu di mana vimatau perlyang tidak alat terbaik.

Mengapa?

Mungkin karena whichmenjawab hanya sebagian dari pertanyaan yang mungkin ditanyakan pengguna shell:

Apa yang sedang dieksekusi ketika saya mengetik commandName?


Eksternal yang mana

Yang harus tersedia (dalam banyak sistem) sebagai executable eksternal.
Satu-satunya cara pasti untuk memanggil alat eksternal itu adalah menggunakan env untuk keluar dari shell dan kemudian memanggil which(yang bekerja di semua shell):

 $ env which which
 /usr/bin/which

Atau gunakan path lengkap ke which(yang dapat bervariasi pada sistem yang berbeda):

 /usr/bin/which which 

Mengapa itu hackdibutuhkan? Karena beberapa kerang (khususnya zsh) bersembunyi which:

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

Menjadi alat eksternal (seperti env) dengan sempurna menjelaskan mengapa itu tidak akan melaporkan informasi internal shell. Seperti alias, fungsi, bawaan, variabel bawaan, variabel shell (tidak diekspor), dll:

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

Output kosong ll(alias umum untuk ll='ls -l') menunjukkan bahwa lltidak terkait dengan program yang dapat dieksekusi, atau setidaknya, bahwa tidak ada file yang dapat dieksekusi yang disebutkan lldalam PATH. Penggunaan llharus memanggil sesuatu yang lain, dalam hal ini, alias:

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

type dan command

Perintah typedan command -vdiminta oleh POSIX. Mereka seharusnya diharapkan bekerja di sebagian besar cangkang, dan mereka melakukannya, kecuali dalam csh, tcsh, fish dan rc.

Kedua perintah dapat digunakan untuk memberikan sudut pandang lain yang perintahnya akan dieksekusi.

whence, where, whereis, whatis,hash

Lalu, ada whence, where, whereis, whatis, hash, dan beberapa orang lain. Semua jawaban berbeda untuk pertanyaan serupa. Semua bekerja dengan cara yang berbeda dalam cangkang yang berbeda. Mungkin, whenceadalah yang paling umum setelahnya type. Yang lain adalah solusi khusus yang menjawab pertanyaan yang sama dengan cara yang berbeda.

Apa yang harus kita gunakan?

Mungkin whichpertama yang tahu jika ada yang dapat dieksekusi dengan nama CommandName , kemudian typedan commandkemudian, jika CommandName belum ditemukan belum: whence, where, whereis, whatis, hashdalam urutan itu.


Script Shell untuk menyediakan yang whichdapat dieksekusi.

#! /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"
Ishak
sumber
0

Kita sering mendengar apa yang harus dihindari. Mengapa? Apa yang harus kita gunakan?

Saya belum pernah mendengarnya. Harap berikan contoh spesifik. Saya akan khawatir tentang distribusi linux Anda dan paket perangkat lunak yang diinstal, dari mana whichasalnya!

SLES 11,4 x86-64

dalam versi tcsh 6.18.01:

> which which

which: shell built-in command.

dalam versi 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.

whichadalah bagian dari util-linux paket standar yang didistribusikan oleh Organisasi Kernel Linux untuk digunakan sebagai bagian dari sistem operasi Linux. Ini juga menyediakan file-file lain ini

/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

saya util-linuxadalah versi 2.19. Catatan rilis dapat dengan mudah ditemukan kembali ke tanggal v2.13 (28-Agustus-2007). Tidak yakin apa maksud atau tujuan dari hal ini, itu pasti tidak dijawab dalam hal panjang yang dibatalkan 331 kali.

ron
sumber
2
Perhatikan bagaimana pertanyaan itu tidak menyebutkan apa yang dimaksud Unix. Linux hanyalah satu dari sedikit.
Kusalananda
2
Seperti yang Anda which -vtunjukkan, itulah GNU yang (yang boros disebutkan dalam jawaban lain dan sama sekali tidak spesifik untuk Linux), bukan util-linux yang AFAIK tidak pernah menyertakan whichutilitas. util-linux 2.19 berasal dari 2011, GNU yang 2.19 berasal dari 2008.
Stéphane Chazelas