Tại sao không sử dụng dịch vụ mà Dùng gì rồi?


328

Khi tìm kiếm đường dẫn đến một thực thi hoặc kiểm tra những gì sẽ xảy ra nếu bạn nhập một tên lệnh trong một vỏ Unix, có rất nhiều tiện ích khác nhau ( which, type, command, whence, where, whereis, whatis, hash, vv).

Chúng ta thường nghe rằng whichnên tránh. Tại sao? Thay vào đó chúng ta nên sử dụng cái gì?


3
Tôi nghĩ rằng hầu hết các đối số chống lại việc sử dụng whichđều giả định bối cảnh vỏ tương tác. Câu hỏi này được gắn thẻ / tính di động. Vì vậy, tôi giải thích câu hỏi trong ngữ cảnh này là "sử dụng cái gì thay vì whichtìm tệp thực thi đầu tiên của một tên đã cho trong $PATH". Hầu hết các câu trả lời và lý do chống lại whichviệc đối phó với các bí danh, nội dung và hàm, trong hầu hết các tập lệnh shell di động trong thế giới thực chỉ là mối quan tâm học thuật. Các bí danh được xác định cục bộ không được kế thừa khi chạy tập lệnh shell (trừ khi bạn lấy nó với .).
MattBianco

5
@MattBianco, vâng, csh(và whichvẫn là cshtập lệnh trên hầu hết các Unice thương mại) không đọc ~/.cshrckhi không tương tác. Đó là lý do tại sao bạn sẽ nhận thấy các tập lệnh csh thường bắt đầu bằng #! /bin/csh -f. whichkhông phải vì nó nhằm mục đích cung cấp cho bạn các bí danh, bởi vì nó có nghĩa là một công cụ cho người dùng (tương tác) csh. Người dùng có vỏ POSIX command -v.
Stéphane Chazelas

@rudimeier, thì câu trả lời sẽ luôn luôn trừ khi vỏ của bạn là (t)csh(hoặc bạn không phiền nếu nó không cho bạn kết quả chính xác), sử dụng typehoặc command -vthay vào đó . Xem câu trả lời tại sao .
Stéphane Chazelas

1
@rudimeier, ( stat $(which ls)sai vì nhiều lý do (thiếu --, thiếu dấu ngoặc kép), không chỉ sử dụng which). Bạn sẽ sử dụng stat -- "$(command -v ls)". Giả định đó lsthực sự là một lệnh được tìm thấy trên hệ thống tệp (không phải là nội dung của shell của bạn hoặc chức năng của bí danh). whichcó thể cung cấp cho bạn đường dẫn sai (không phải đường dẫn mà shell của bạn sẽ thực thi nếu bạn nhập ls) hoặc cung cấp cho bạn một bí danh như được định nghĩa trong cấu hình của một số shell khác ...
Stéphane Chazelas

1
@rudimeier, một lần nữa, có một số điều kiện theo đó nhiều whichtriển khai sẽ không cung cấp cho bạn ngay cả những lsđiều sẽ được tìm thấy bằng cách tra cứu $PATH(bất kể điều gì lscó thể xảy ra trong vỏ của bạn). sh -c 'command -v ls', hoặc zsh -c 'rpm -q --whatprovides =ls'có nhiều khả năng cung cấp cho bạn câu trả lời chính xác. Điểm ở đây whichlà một di sản bị phá vỡ từ csh.
Stéphane Chazelas

Câu trả lời:


366

Đây là tất cả những gì bạn không bao giờ nghĩ rằng bạn sẽ không bao giờ muốn biết về nó:

Tóm lược

Để có được tên đường dẫn của một tệp thực thi trong tập lệnh shell giống Bourne (có một vài cảnh báo; xem bên dưới):

ls=$(command -v ls)

Để tìm hiểu nếu một lệnh nhất định tồn tại:

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

Tại dấu nhắc của một vỏ tương tác Bourne:

type ls

Các whichlệnh là một di sản bị hỏng từ C-Shell và còn lại tốt hơn một mình trong vỏ Bourne-như thế nào.

Trường hợp sử dụng

Có một sự khác biệt giữa tìm kiếm thông tin đó như là một phần của tập lệnh hoặc tương tác tại dấu nhắc shell.

Tại dấu nhắc shell, trường hợp sử dụng điển hình là: lệnh này hoạt động kỳ lạ, tôi có đang sử dụng đúng không? Chính xác thì chuyện gì đã xảy ra khi tôi gõ mycmd? Tôi có thể nhìn xa hơn xem nó là gì không?

Trong trường hợp đó, bạn muốn biết shell của bạn làm gì khi bạn gọi lệnh mà không thực sự gọi lệnh.

Trong các kịch bản shell, nó có xu hướng khá khác nhau. Trong tập lệnh shell không có lý do tại sao bạn muốn biết lệnh ở đâu hoặc lệnh là gì nếu tất cả những gì bạn muốn làm là chạy nó. Nói chung, những gì bạn muốn biết là đường dẫn của tệp thực thi, vì vậy bạn có thể lấy thêm thông tin từ nó (như đường dẫn đến tệp khác liên quan đến tệp đó hoặc đọc thông tin từ nội dung của tệp thực thi tại đường dẫn đó).

Tương tác, bạn có thể muốn biết về tất cả các my-cmdlệnh có sẵn trên hệ thống, trong các tập lệnh, hiếm khi như vậy.

Hầu hết các công cụ có sẵn (như thường lệ) đã được thiết kế để được sử dụng tương tác.

Lịch sử

Một chút lịch sử đầu tiên.

Các shell Unix đầu cho đến cuối những năm 70 không có chức năng hoặc bí danh. Chỉ có truyền thống tìm kiếm thực thi trong $PATH. cshbí danh giới thiệu khoảng năm 1978 (mặc dù cshlần đầu tiên được phát hành trong 2BSD, tháng 5 năm 1979), và cũng có thể chế biến một .cshrccho người dùng tùy biến vỏ (mỗi vỏ, như cshđọc .cshrcngay cả khi không tương tác như trong kịch bản).

Trong khi shell Bourne được phát hành lần đầu tiên trong Unix V7 vào đầu năm 1979, hỗ trợ chức năng chỉ được thêm vào sau đó (1984 trong SVR2), và dù sao, nó không bao giờ có một số rctệp (đó .profilelà cấu hình môi trường của bạn, không phải là shell per se ).

csh trở nên phổ biến hơn nhiều so với shell Bourne vì (mặc dù nó có cú pháp cực kỳ tệ hơn so với shell Bourne), nó đã thêm rất nhiều tính năng tiện lợi và tốt hơn để sử dụng tương tác.

Vào 3BSDnăm 1980, một whichtập lệnh csh đã được thêm vào cho cshngười dùng để giúp xác định tập tin thực thi và đó là tập lệnh khó có thể tìm thấy như whichtrên nhiều Unice thương mại hiện nay (như Solaris, HP / UX, AIX hoặc Tru64).

Tập lệnh đó đọc người dùng ~/.cshrc(giống như tất cả các cshtập lệnh thực hiện trừ khi được gọi với csh -f) và tìm (các) tên lệnh được cung cấp trong danh sách các bí danh và trong $path(mảng cshduy trì dựa trên $PATH).

Ở đây bạn đi, whichlần đầu tiên cho vỏ phổ biến nhất vào thời điểm đó (và cshvẫn còn phổ biến cho đến giữa những năm 90), đó là lý do chính tại sao nó được ghi lại trong sách và vẫn được sử dụng rộng rãi.

Lưu ý rằng, ngay cả đối với cshngười dùng, whichtập lệnh csh đó không nhất thiết phải cung cấp cho bạn thông tin chính xác. Nó lấy các bí danh được xác định trong ~/.cshrc, không phải các bí danh mà bạn có thể đã xác định sau đó tại dấu nhắc hoặc ví dụ bằng cách nhập sourcemột cshtệp khác , và (mặc dù đó không phải là một ý tưởng tốt), PATHcó thể được xác định lại ~/.cshrc.

Chạy whichlệnh đó từ trình bao Bourne, vẫn sẽ tra cứu các bí danh được xác định trong bạn ~/.cshrc, nhưng nếu không có vì bạn không sử dụng csh, điều đó vẫn có thể giúp bạn có câu trả lời đúng.

Một chức năng tương tự không được thêm vào trình bao Bourne cho đến năm 1984 trong SVR2 bằng typelệnh dựng sẵn. Việc nó được dựng sẵn (trái ngược với tập lệnh bên ngoài) có nghĩa là nó có thể cung cấp cho bạn thông tin đúng (ở một mức độ nào đó) vì nó có quyền truy cập vào phần bên trong của trình bao.

Lệnh ban đầu typegặp phải một vấn đề tương tự như whichtập lệnh ở chỗ nó không trả về trạng thái thoát thất bại nếu không tìm thấy lệnh. Ngoài ra, đối với các tệp thực thi, ngược lại which, nó tạo ra một cái gì đó giống như ls is /bin/lsthay vì chỉ /bin/lslàm cho nó ít dễ sử dụng hơn trong các tập lệnh.

Phiên bản Unix Bour 8 (không được phát hành trong vỏ) Bourne đã được typeđổi tên thành whatis. Và Plan9 (trình tự kế thừa của Unix) rc(và các dẫn xuất của nó như akangaes) whatiscũng vậy.

Shell Korn (một tập hợp con mà định nghĩa sh POSIX dựa trên), được phát triển vào giữa những năm 80 nhưng không được phổ biến rộng rãi trước năm 1988, đã thêm nhiều cshtính năng (trình chỉnh sửa dòng, bí danh ...) trên đầu vỏ Bourne . Nó đã thêm phần whencedựng sẵn của riêng mình (ngoài ra type), có một số tùy chọn ( -vđể cung cấp typeđầu ra dài dòng giống như và -pchỉ tìm kiếm các tệp thực thi (không phải bí danh / hàm ...)).

Trùng hợp với sự hỗn loạn liên quan đến các vấn đề bản quyền giữa AT & T và Berkeley, một vài triển khai vỏ phần mềm miễn phí đã xuất hiện vào cuối những năm 80 đầu thập niên 90. Tất cả các vỏ Almquist (tro, để thay thế vỏ Bourne trong BSD), việc triển khai phạm vi công cộng của ksh (pdksh), bash(được tài trợ bởi FSF), zshxuất hiện vào giữa năm 1989 và 1991.

Ash, mặc dù có nghĩa là một sự thay thế cho vỏ Bourne đã không được tích hợp typesẵn cho đến sau này (trong NetBSD 1.3 và FreeBSD 2.3), mặc dù nó đã có hash -v. OSF / 1 /bin/shtypenội dung tích hợp luôn trả về 0 cho đến OSF / 1 v3.x. bashkhông thêm whencenhưng thêm -ptùy chọn để typein đường dẫn ( type -psẽ như thế whence -p) và -abáo cáo tất cả các lệnh khớp. tcshlàm whichBUILTIN và thêm một wherelệnh hành động như bash's type -a. zshcó tất cả

Các fishvỏ (2005) có một typelệnh thực hiện như một hàm.

Các whichkịch bản csh khi đó đã bị xóa khỏi NetBSD (như nó đã được dựng sẵn trong tcsh và không sử dụng nhiều trong vỏ khác), và các chức năng thêm vào whereis(khi gọi như which, whereishoạt động như whichngoại trừ việc nó chỉ nhìn lên thực thi trong $PATH). Trong OpenBSD và FreeBSD, whichcũng được đổi thành một chữ viết bằng C chỉ tìm kiếm các lệnh $PATH.

Triển khai

Có hàng tá việc thực hiện một whichlệnh trên các Unice khác nhau với cú pháp và hành vi khác nhau.

Trên Linux (bên cạnh các bản dựng sẵn trong tcshzsh), chúng tôi tìm thấy một số triển khai. Ví dụ, trên các hệ thống Debian gần đây, đó là tập lệnh shell POSIX đơn giản tìm kiếm các lệnh trong $PATH.

busyboxcũng có một whichlệnh.

Có một GNU whichcái có lẽ là xa hoa nhất. Nó cố gắng mở rộng whichtập lệnh csh đã làm với các shell khác: bạn có thể nói cho nó biết bí danh và chức năng của bạn là gì để nó có thể cho bạn câu trả lời tốt hơn (và tôi tin rằng một số bản phân phối Linux đặt một số bí danh toàn cầu bashđể làm điều đó) .

zshcó một vài toán tử để mở rộng theo đường dẫn của các tệp thực thi: toán tử = mở rộng tên tệp và bộ :csửa đổi mở rộng lịch sử (ở đây áp dụng cho mở rộng tham số ):

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

zsh, trong zsh/parametersmô-đun cũng làm cho bảng băm lệnh dưới dạng commandsmảng kết hợp:

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

Các whatistiện ích (trừ trường hợp một trong Unix V8 Bourne shell hoặc Plan 9 rc/ es) là không thực sự liên quan vì nó là chỉ tài liệu (greps cơ sở dữ liệu whatis, đó là người đàn ông trang tóm tắt).

whereiscũng được thêm vào 3BSDcùng lúc với whichviệc nó được viết C, không cshvà được sử dụng để tra cứu cùng lúc, trang man và nguồn thực thi nhưng không dựa trên môi trường hiện tại. Vì vậy, một lần nữa, câu trả lời một nhu cầu khác nhau.

Bây giờ, ở mặt trước tiêu chuẩn, POSIX chỉ định các lệnh command -v-V(được sử dụng là tùy chọn cho đến POSIX.2008). UNIX chỉ định typelệnh (không có tùy chọn). Đó là tất cả ( where, which, whencekhông được chỉ định trong tiêu chuẩn có)

Lên đến một số phiên bản typecommand -vlà tùy chọn trong đặc tả Cơ sở Tiêu chuẩn Linux, điều này giải thích tại sao một số phiên bản cũ của posh(mặc dù dựa trên pdkshcả hai) đều không có. command -vcũng đã được thêm vào một số triển khai shell Bourne (như trên Solaris).

Tình trạng hôm nay

Tình trạng hiện nay là typecommand -vcó mặt ở tất cả các vỏ giống như Bourne (mặc dù, như đã lưu ý bởi @jarno, lưu ý cảnh báo / lỗi bashkhi không ở chế độ POSIX hoặc một số hậu duệ của vỏ Almquist bên dưới trong các bình luận). tcshlà vỏ duy nhất mà bạn muốn sử dụng which(vì không typecó ở đó và whichđược dựng sẵn).

Trong vỏ ngoài tcshzsh, whichcó thể cho bạn biết con đường thực thi được miễn là không có bí danh hoặc chức năng bằng cách đó cùng tên trong bất kỳ của chúng tôi ~/.cshrc, ~/.bashrchoặc bất kỳ tập tin khởi động vỏ và bạn không xác định $PATHtrong của bạn ~/.cshrc. Nếu bạn có một bí danh hoặc chức năng được xác định cho nó, nó có thể hoặc không thể cho bạn biết về nó, hoặc cho bạn biết điều sai.

Nếu bạn muốn biết về tất cả các lệnh theo một tên cụ thể, không có gì có thể mang theo được. Bạn sẽ sử dụng wheretrong tcshhoặc zsh, type -atrong bashhoặc zsh, whence -atrong ksh93 và trong các shell khác, bạn có thể sử dụng typekết hợp với which -anhững thứ có thể hoạt động.

khuyến nghị

Lấy tên đường dẫn để thực thi

Bây giờ, để có được tên đường dẫn của một tệp thực thi trong tập lệnh, có một vài lưu ý:

ls=$(command -v ls)

sẽ là cách tiêu chuẩn để làm điều đó.

Có một vài vấn đề:

  • Không thể biết đường dẫn của tệp thực thi mà không thực thi nó. Tất cả các type, which, command -v... tất cả chẩn đoán sử dụng để tìm ra con đường. Họ lặp qua các $PATHthành phần và tìm tệp không phải thư mục đầu tiên mà bạn có quyền thực thi. Tuy nhiên, tùy thuộc vào shell, khi thực hiện lệnh, nhiều người trong số họ (Bourne, AT & T ksh, zsh, ash ...) sẽ thực hiện chúng theo thứ tự $PATHcho đến khi execvecuộc gọi hệ thống không trả về lỗi . Chẳng hạn, nếu $PATHchứa /foo:/barvà bạn muốn thực thi ls, trước tiên họ sẽ cố gắng thực thi /foo/lshoặc nếu điều đó không thành công /bar/ls. Bây giờ thực hiện/foo/lscó thể thất bại vì bạn không có quyền thực thi nhưng cũng vì nhiều lý do khác, như đó không phải là một thực thi hợp lệ. command -v lssẽ báo cáo /foo/lsnếu bạn có quyền thực thi /foo/ls, nhưng chạy lsthực sự có thể chạy /bar/lsnếu /foo/lskhông phải là thực thi hợp lệ.
  • nếu foolà nội dung hoặc hàm hoặc bí danh, command -v footrả về foo. Với một số shell như ash, pdkshhoặc zsh, nó cũng có thể trả về foonếu $PATHbao gồm chuỗi rỗng và có tệp thực thi footrong thư mục hiện tại. Có một số trường hợp mà bạn có thể cần phải tính đến điều đó. Ví dụ, hãy nhớ rằng danh sách các nội trang thay đổi theo cách triển khai shell (ví dụ, mountđôi khi được tích hợp sẵn cho busybox sh) và ví dụ bashcó thể nhận các hàm từ môi trường.
  • nếu $PATHchứa các thành phần đường dẫn tương đối (thông thường .hoặc chuỗi rỗng, cả hai đều tham chiếu đến thư mục hiện tại nhưng có thể là bất cứ thứ gì), tùy thuộc vào trình bao, command -v cmdcó thể không xuất ra một đường dẫn tuyệt đối. Vì vậy, đường dẫn bạn có được tại thời điểm bạn chạy command -vsẽ không còn hiệu lực sau khi bạn cdở một nơi khác.
  • Giai thoại: với vỏ ksh93, nếu /opt/ast/bin(mặc dù con đường chính xác có thể khác nhau trên các hệ thống khác nhau Tôi tin) là ở bạn $PATH, ksh93 sẽ cung cấp một vài builtins thêm ( chmod, cmp, cat...), nhưng command -v chmodsẽ trở lại /opt/ast/bin/chmodngay cả khi con đường đó doesn' t tồn tại

Xác định xem một lệnh có tồn tại không

Để tìm hiểu xem một lệnh đã cho có tồn tại chuẩn hay không, bạn có thể làm:

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

Nơi mà người ta có thể muốn sử dụng which

(t)csh

Trong cshtcsh, bạn không có nhiều sự lựa chọn. Trong tcshđó, đó là tốt như whichđược xây dựng. Trong cshđó, đó sẽ là whichlệnh hệ thống , có thể không làm những gì bạn muốn trong một vài trường hợp.

tìm lệnh chỉ trong một số shell

Một trường hợp nó có thể làm cho tinh thần để sử dụng whichlà nếu bạn muốn biết con đường của một lệnh, bỏ qua builtins vỏ tiềm năng hoặc chức năng trong bash, csh(không tcsh), dashhoặc Bournevỏ kịch bản, đó là vỏ mà không có whence -p(như kshhay zsh) , command -ev(như yash), whatis -p( rc, akanga) hoặc nội dung which(như tcshhoặc zsh) trên các hệ thống whichcó sẵn và không phải là cshtập lệnh.

Nếu những điều kiện đó được đáp ứng, thì:

echo=$(which echo)

sẽ cung cấp cho bạn con đường đầu tiên echotrong $PATH(trừ những trường hợp góc), bất kể echocũng sẽ xảy ra là một vỏ BUILTIN / bí danh / chức năng hay không.

Trong các shell khác, bạn thích:

  • zsh : echo==echohoặc echo=$commands[echo]hoặcecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • yash :echo=$(command -ev echo)
  • RC , akanga : echo=`whatis -p echo`(coi chừng đường dẫn có dấu cách)
  • :set echo (type -fp echo)

Lưu ý rằng nếu tất cả những gì bạn muốn làm là chạyecho lệnh đó , bạn không cần phải có đường dẫn của nó, bạn chỉ có thể làm:

env echo this is not echoed by the builtin echo

Ví dụ, với tcsh, để ngăn chặn nội dung whichđược sử dụng:

set Echo = "`env which echo`"

khi bạn cần một lệnh bên ngoài

Một trường hợp khác mà bạn có thể muốn sử dụng whichlà khi bạn thực sự cần một lệnh bên ngoài. POSIX yêu cầu tất cả các nội dung shell (như command) cũng có sẵn dưới dạng các lệnh bên ngoài, nhưng thật không may, đó không phải là trường hợp commandtrên nhiều hệ thống. Chẳng hạn, rất hiếm khi tìm thấy một commandlệnh trên các hệ điều hành dựa trên Linux trong khi hầu hết chúng đều có whichlệnh (mặc dù các lệnh khác nhau với các tùy chọn và hành vi khác nhau).

Các trường hợp bạn có thể muốn một lệnh bên ngoài sẽ là bất cứ nơi nào bạn thực thi lệnh mà không cần gọi trình bao POSIX.

Các chức năng system("some command line"), popen()... của C hoặc các ngôn ngữ khác nhau gọi một trình bao để phân tích dòng lệnh đó, do đó, hãy system("command -v my-cmd")thực hiện chúng. Một ngoại lệ sẽ là perltối ưu hóa lớp vỏ nếu nó không nhìn thấy bất kỳ ký tự đặc biệt nào của vỏ (trừ không gian). Điều đó cũng áp dụng cho toán tử backtick của nó:

$ 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

Việc bổ sung các :;lực lượng trên perlđể gọi một cái vỏ ở đó. Bằng cách sử dụng which, bạn sẽ không phải sử dụng thủ thuật đó.


24
@Joe, whichlà một cshkịch bản trên nhiều Unices thương mại. Lý do là lịch sử, đó là lý do tại sao tôi đưa ra lịch sử, vì vậy mọi người hiểu nó đến từ đâu, tại sao mọi người quen với việc sử dụng nó và tại sao thực sự không có lý do gì bạn nên sử dụng nó. Và vâng, một số người sử dụng (t) csh. Không phải ai cũng sử dụng Linux
Stéphane Chazelas

12
Sau khi đọc bài đăng này, tôi đã tìm thấy rất nhiều bối cảnh cho câu trả lời, nhưng bản thân nó không phải là câu trả lời. Trường hợp trong bài viết này thực sự nói tại sao không sử dụng which, trái ngược với những thứ bạn có thể đang cố gắng sử dụng whichđể làm, lịch sử which, thực hiện which, các lệnh khác để thực hiện các tác vụ liên quan hoặc lý do thực sự sử dụng which? Tại sao các lệnh khác tốt hơn ? Họ làm gì khác nhau which? Làm thế nào để họ tránh những cạm bẫy của nó? Câu trả lời này thực sự dành nhiều từ cho các vấn đề với các lựa chọn thay thế hơn là các vấn đề với which.
dùng62251

1
Trái với những gì câu trả lời yêu cầu, command -vkhông kiểm tra quyền thực thi, ít nhất là nếu bạn gọi nó bằng đối số tên tệp thuần túy không có đường dẫn. Tôi đã thử nghiệm bằng dash 0,5.8 và bash GNU 4.3.48.
jarno

2
@ StéphaneChazelas Nếu tôi tạo tệp mới touch /usr/bin/mytestfilevà sau đó chạy command -v mytestfile, nó sẽ đưa ra đường dẫn (trong khi which mytestfilekhông).
jarno

2
@jarno, oh vâng, bạn nói đúng. bashsẽ giải quyết trên một tệp không thể thực thi nếu nó không thể tìm thấy tệp thực thi, vì vậy nó "OK" (mặc dù trong thực tế, người ta thà command -v/ typetrả lại lỗi) vì đó là lệnh mà nó sẽ cố thực thi khi bạn chạy mytestfile, nhưng dashhành vi là lỗi, như thể không có khả năng thực thi cmdtrước một thực thi, command -vtrả về cái không thể thực thi trong khi thực thi cmdsẽ thực thi cái thực thi (cái sai cũng bị băm). FreeBSD sh(cũng dựa trên ash) có lỗi tương tự. zsh, yash, ksh, mksh, bash như sh đều ổn.
Stéphane Chazelas

47

Những lý do tại sao một người có thể không muốn sử dụng whichđã được giải thích, nhưng đây là một vài ví dụ trên một vài hệ thống whichthực sự thất bại.

Trên các shell giống như Bourne, chúng ta so sánh đầu ra của whichđầu ra type( typelà một vỏ được xây dựng, nó có nghĩa là sự thật nền tảng, vì nó là vỏ cho chúng ta biết nó sẽ gọi lệnh như thế nào).

Nhiều trường hợp là trường hợp góc , nhưng hãy nhớ rằng which/ typethường được sử dụng trong các trường hợp góc (để tìm câu trả lời cho một hành vi không mong muốn như: tại sao trên trái đất, lệnh đó lại hành xử như vậy, tôi đang gọi ai? ).

Hầu hết các hệ thống, hầu hết các shell giống Bourne: các hàm

Trường hợp rõ ràng nhất là cho các chức năng:

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

Lý do là whichchỉ báo cáo về các tệp thực thi và đôi khi về các bí danh (mặc dù không phải luôn luôn là các vỏ của bạn ), không phải là các hàm.

Trang GNU mà man đã bị hỏng (vì họ quên trích dẫn $@) về cách sử dụng nó để báo cáo các chức năng, nhưng cũng giống như đối với các bí danh, vì nó không triển khai trình phân tích cú pháp shell, nó dễ bị lừa:

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

Hầu hết các hệ thống, hầu hết các shell giống như Bourne: nội dung

Một trường hợp rõ ràng khác là nội dung hoặc từ khóa, vì whichlà một lệnh bên ngoài không có cách nào để biết phần tử nào mà shell của bạn có (và một số shell như zsh, bashhoặc kshcó thể tải động tích hợp):

$ 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

(không áp dụng cho zshnơi whichđược dựng sẵn)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 và nhiều loại khác:

$ 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

Đó là bởi vì trên hầu hết các Thông báo thương mại, which(như trong triển khai ban đầu trên 3BSD) là một cshtập lệnh đọc ~/.cshrc. Các bí danh mà nó sẽ báo cáo là những bí danh được xác định ở đó bất kể các bí danh bạn hiện đã xác định và bất kể vỏ bạn đang thực sự sử dụng.

Trong HP / UX hoặc Tru64:

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

(phiên bản Solaris và AIX đã khắc phục sự cố đó bằng cách lưu $pathtrước khi đọc ~/.cshrcvà khôi phục nó trước khi tra cứu (các) lệnh)

$ 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

Hoặc là:

$ 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

(tất nhiên, là một cshtập lệnh mà bạn không thể mong đợi nó hoạt động với các đối số có chứa khoảng trắng ...)

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

Trên hệ thống đó, có một bí danh toàn hệ thống được xác định bao bọc whichlệnh GNU .

Sản lượng giả mạo là bởi vì whichlần đọc đầu ra của bash's aliasnhưng không biết làm thế nào để phân tích nó đúng cách và sử dụng công nghệ tự động (một bí danh trên mỗi dòng, tìm kiếm đầu tiên tìm thấy lệnh sau |, ;, &...)

Điều tồi tệ nhất trên CentOS là zshcó một whichlệnh dựng sẵn hoàn toàn tốt nhưng CentOS đã xoay sở để phá vỡ nó bằng cách thay thế nó bằng một bí danh không hoạt động thành GNU which.

Debian 7.0, ksh93:

(mặc dù áp dụng cho hầu hết các hệ thống có nhiều shell)

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

Trên Debian, /bin/whichlà một /bin/shtập lệnh. Trong trường hợp của tôi, shdashnhưng đó là tương tự khi nó bash.

Một unset PATHkhông phải là để vô hiệu hóa PATHtìm kiếm, nhưng có nghĩa là sử dụng của hệ thống PATH mặc định mà tiếc là trên Debian, không ai đồng ý trên ( dashbash/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()(như trong env) có :/bin:/usr/bin(vâng, trông trong thư mục hiện đầu tiên! )).

Đó là lý do whichđược nó sai ở trên vì nó đang sử dụng dash's mặc định PATHđó là khác nhau từ ksh93' s

GNU whichkhông báo cáo tốt hơn :

which: no which in ((null))

(thật thú vị, thực sự có một /usr/local/bin/whichhệ thống của tôi thực sự là một akangatập lệnh đi kèm akanga(một rcdẫn xuất shell trong đó mặc định PATH/usr/ucb:/usr/bin:/bin:.))

bash, bất kỳ hệ thống:

Người mà Chris đang đề cập đến trong câu trả lời của mình :

$ 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

Ngoài ra sau khi gọi hashthủ công:

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

Bây giờ một trường hợp whichvà đôi khi typethất bại:

$ 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

Bây giờ, với một số vỏ:

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

Với những người khác:

$ foo
a/foo

Không whichphải cũng không typethể biết trước rằng b/fookhông thể được thực thi. Một số vỏ như bash, kshhoặc yash, khi gọi foothực sự sẽ cố gắng chạy b/foovà báo cáo lỗi, trong khi những người khác (như zsh, ash, csh, Bourne, tcsh) sẽ chạy a/fookhi sự thất bại của các execve()cuộc gọi hệ thống trên b/foo.


mkshthực sự sử dụng một cái gì đó khác cho mặc định $PATH: đầu tiên, hằng số thời gian biên dịch của hệ điều hành _PATH_DEFPATHđược sử dụng (phổ biến nhất trên BSD), sau đó, confstr(_CS_PATH, …)được sử dụng (POSIX) và nếu cả hai không tồn tại hoặc thất bại, /bin:/usr/bin:/sbin:/usr/sbinđều được sử dụng.
mirabilos

1
Trong ví dụ đầu tiên của bạn, ngay cả khi lslà một hàm mà nó đang sử dụng lstừ PATH. Và whichnó là tốt để cho bạn biết cái nào được sử dụng /usr/bin/ls hoặc /usr/local/bin/ls. Tôi không thấy "Tại sao không sử dụng cái nào" ....
rudimeier

@rudimeier, đó which lssẽ cho tôi /bin/lsbất kể lschức năng cuộc gọi /bin/lshoặc /opt/gnu/bin/lshoặc dirhoặc không có gì cả. IOW, which(mà triển khai, IMMV) đang đưa ra một cái gì đó không liên quan
Stéphane Chazelas

1
@ StéphaneChazelas. Không không không. Tôi đã biết rằng tôi lslà một chức năng. Tôi biết rằng lschức năng của tôi được gọi lstừ PATH. Bây giờ whichcho tôi biết nơi tập tin. Bạn chỉ thấy một trường hợp sử dụng duy nhất: "Shell của tôi sẽ làm gì với lệnh này." Đối với trường hợp sử dụng này whichlà sai, chính xác. Nhưng có những trường hợp sử dụng khác trong đó (GNU) whichchính xác là điều đúng.
rudimeier

@rudimét, phụ thuộc vào việc whichthực hiện. Một số người sẽ cho bạn biết đó là bí danh (nếu bạn có cấu hình bí danh hoặc nếu có một bí danh ~/.cshrctrong nhà bạn có bí danh như vậy), một số sẽ cung cấp cho bạn một đường dẫn nhưng sai trong một số điều kiện. sh -c 'command -v ls', mặc dù không hoàn hảo vẫn có nhiều khả năng cung cấp cho bạn câu trả lời đúng cho yêu cầu khác nhau đó (và cũng là tiêu chuẩn).
Stéphane Chazelas

21

Một điều mà (từ việc đọc lướt nhanh của tôi) có vẻ như Stephane không đề cập đến là whichkhông biết gì về bảng băm đường dẫn của shell của bạn. Điều này có tác dụng là nó có thể trả về một kết quả không đại diện cho những gì thực sự được chạy, điều này làm cho nó không hiệu quả trong việc gỡ lỗi.


6

Theo tinh thần UNIX: Làm cho mỗi chương trình làm một việc tốt.

Nếu mục tiêu là để trả lời: Thực thi nào tồn tại với tên này ?

Chương trình thực thi được cung cấp với các hệ thống Debian là một câu trả lời tốt. Các cung cấp với csh bao gồm các bí danh, đó là một nguồn của vấn đề. Cái mà một số shell cung cấp như một nội trang có một mục tiêu khác. Sử dụng tệp thực thi đó hoặc sử dụng tập lệnh được cung cấp ở cuối câu trả lời này.

Nếu tập lệnh này được sử dụng, những gì nó trả lời là sạch sẽ, đơn giản và hữu ích.

Mục tiêu này sẽ phù hợp với câu đầu tiên của câu hỏi của bạn:

Khi tìm đường dẫn đến một Tập trận thực thi

Nếu bạn có một hệ thống không có tệp thực thi được gọi là (hầu hết các hệ thống linux có một hệ thống), bạn có thể tạo một hệ thống ~/bin/whichtrước đó /bin/trong PATH để các hệ thống ghi đè thực thi cá nhân như hệ thống ở cuối bài này:

Thực thi đó sẽ liệt kê (theo mặc định) tất cả các thực thi được tìm thấy trong PATH. Nếu chỉ đầu tiên là bắt buộc, tùy chọn -fcó sẵn.


Tại thời điểm này, chúng tôi rơi vào một mục tiêu khác:

những gì shell sẽ thực thi (sau khi phân tích cú pháp)

Điều đó xuất phát từ câu thứ hai của bạn:

kiểm tra xem điều gì sẽ xảy ra nếu bạn nhập tên lệnh trong shell Unix

Chủ đề thứ hai này cố gắng tìm một câu trả lời hay cho một câu hỏi khá khó trả lời. Shell có các quan điểm khác nhau, các trường hợp góc và (tối thiểu) các cách hiểu khác nhau. Thêm vào đó:

có rất nhiều tiện ích khác nhau (trong đó, loại, lệnh, từ đâu, ở đâu, trong đó, whatis, hash, v.v.).

Và chắc chắn, tất cả các nỗ lực phù hợp với mục tiêu đó.


Tránh cái nào?

Chúng ta thường nghe rằng nên tránh.

Tôi tự hỏi: Tại sao điều đó nên được nói nếu whichhoạt động tốt (ít nhất là trong debian)?

Theo tinh thần UNIX: Làm cho mỗi chương trình làm một việc tốt.

Các bên ngoài chương trình whichđang thực hiện một điều: Tìm thực thi đầu tiên trên PATH có tên giống như tên lệnh . Và đang làm điều đó hợp lý tốt.

Tôi không biết bất kỳ chương trình hoặc tiện ích nào khác trả lời câu hỏi này theo cách cơ bản hơn. Như vậy, nó rất hữu ích và có thể được sử dụng khi cần thiết.

Sự thay thế gần nhất có vẻ là : command -pv commandName, nhưng điều đó cũng sẽ báo cáo về nội dung và bí danh. Không cùng một câu trả lời.

Tất nhiên, whichlà có giới hạn, nó không trả lời tất cả các câu hỏi, không có công cụ nào có thể làm điều đó (tốt, chưa ...). Nhưng nó rất hữu ích khi được sử dụng để trả lời câu hỏi mà nó được thiết kế để trả lời (câu hỏi ở trên). Giống như edbị giới hạn và sau đó sedxuất hiện (hoặc vi/ vim). Hoặc như awkbị hạn chế và làm cho Perl xuất hiện và mở rộng. Tuy nhiên, ed, sedhoặc / và awkcó trường hợp sử dụng cụ thể mà vimhoặc perlkhông công cụ tốt nhất.

Tại sao?

Có lẽ bởi vì whichcâu trả lời chỉ là một phần của câu hỏi mà người dùng shell có thể hỏi:

Điều gì đang được thực thi khi tôi gõ một CommandName?


Bên ngoài mà

Mà nên có sẵn (trong nhiều hệ thống) như là một thực thi bên ngoài.
Cách chắc chắn duy nhất để gọi công cụ bên ngoài đó là sử dụng env để thoát khỏi shell và sau đó gọi which(hoạt động trong tất cả các shell):

 $ env which which
 /usr/bin/which

Hoặc sử dụng đường dẫn đầy đủ đến which(có thể khác nhau trên các hệ thống khác nhau):

 /usr/bin/which which 

Tại sao hackcần thiết? Bởi vì một số shell (đặc biệt zsh) ẩn which:

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

Là một công cụ bên ngoài (như env) giải thích hoàn hảo lý do tại sao nó sẽ không báo cáo thông tin nội bộ shell. Giống như bí danh, hàm, hàm dựng, tích hợp đặc biệt, biến shell (không xuất), v.v .:

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

Đầu ra trống của ll(một bí danh chung cho ll='ls -l') chỉ ra rằng llkhông liên quan đến chương trình thực thi, hoặc ít nhất, không có tệp thực thi có tên lltrong PATH. Việc sử dụng llnên gọi một cái gì đó khác, trong trường hợp này, một bí danh:

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

typecommand

Các lệnh typecommand -vđược yêu cầu bởi POSIX. Họ nên được dự kiến ​​sẽ làm việc trong hầu hết các vỏ, và họ làm, ngoại trừ trong csh, tcsh, cá và RC.

Cả hai lệnh có thể được sử dụng để cung cấp một quan điểm khác về lệnh nào sẽ được thực thi.

whence, where, whereis, whatis,hash

Sau đó, có whence, where, whereis, whatis, hash, và một số người khác. Tất cả các câu trả lời khác nhau cho các câu hỏi tương tự. Tất cả làm việc theo những cách khác nhau trong vỏ khác nhau. Có lẽ, whencelà phổ biến nhất sau type. Những người khác là giải pháp đặc biệt trả lời cùng một câu hỏi theo những cách khác nhau.

Thay vào đó chúng ta nên sử dụng cái gì?

Có lẽ whichđầu tiên biết nếu có tồn tại một thực thi bởi tên của CommandName , sau đó typecommandrồi, nếu CommandName đã chưa được tìm thấy: whence, where, whereis, whatis, hashtheo thứ tự đó.


Shell script để cung cấp một whichthực thi.

#! /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

Chúng ta thường nghe rằng nên tránh. Tại sao? Thay vào đó chúng ta nên sử dụng cái gì?

Tôi chưa bao giờ nghe đến điều đó. Vui lòng cung cấp các ví dụ cụ thể. Tôi sẽ lo lắng về việc phân phối linux và các gói phần mềm đã cài đặt, vì đó là từ đâu whichđến!

SLES 11,4 x86-64

trong phiên bản tcsh 6.18.01:

> which which

which: shell built-in command.

trong phiên bản 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.

whichlà một phần của tiện ích linux, một gói tiêu chuẩn được phân phối bởi Tổ chức hạt nhân Linux để sử dụng như một phần của hệ điều hành Linux. Nó cũng cung cấp các tệp khác

/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-linuxphiên bản của tôi là 2.19. Ghi chú phát hành có thể dễ dàng được tìm thấy trở lại v2.13 ngày (28 tháng 8 năm 2007). Không chắc mục đích hay mục tiêu của việc này là gì, nó chắc chắn không được trả lời trong điều dài dòng đó được nâng lên tới 331 lần.


2
Lưu ý cách câu hỏi không đề cập đến những gì Unix đề cập đến. Linux chỉ là một trong số ít.
Kusalananda

2
Như bạn which -v chương trình , đó là GNU (câu trả lời ngông cuồng được đề cập trong câu trả lời khác và không có cách nào cụ thể đối với Linux), không phải là linux mà AFAIK không bao gồm whichtiện ích. produc-linux 2.19 là từ năm 2011, GNU, 2.19 là từ năm 2008
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.