Câu hỏi Làm thế nào để kiểm tra xem một chương trình có tồn tại từ một tập lệnh Bash không?


Làm cách nào để tôi xác thực rằng một chương trình tồn tại, theo cách sẽ trả lại lỗi và thoát hoặc tiếp tục với tập lệnh?

Nó có vẻ như nó phải dễ dàng, nhưng nó đã được stumping tôi.


1582
2018-02-26 21:52


gốc




Các câu trả lời:


Câu trả lời

POSIX tương thích:

command -v <the_command>

Dành cho bash môi trường cụ thể:

hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords

Giải trình

Tránh which. Không chỉ là một quá trình bên ngoài mà bạn đang khởi chạy để làm rất ít (có nghĩa là nội trang dựng sẵn như hash, type hoặc là command là cách rẻ hơn), bạn cũng có thể dựa vào nội trang để thực sự làm những gì bạn muốn, trong khi hiệu ứng của các lệnh bên ngoài có thể dễ dàng thay đổi từ hệ thống đến hệ thống.

Tại sao lại quan tâm?

  • Nhiều hệ điều hành có which cái đó thậm chí không đặt trạng thái thoát, có nghĩa là if which foo thậm chí sẽ không làm việc ở đó và sẽ luôn luôn báo cáo rằng foo tồn tại, ngay cả khi nó không (lưu ý rằng một số vỏ POSIX xuất hiện để làm điều này cho hash quá).
  • Nhiều hệ điều hành which làm các công cụ tùy chỉnh và độc ác như thay đổi đầu ra hoặc thậm chí là móc vào trình quản lý gói.

Vì vậy, không sử dụng which. Thay vào đó, hãy sử dụng một trong các cách sau:

$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }

(Ghi chú phụ nhỏ: một số sẽ đề xuất 2>&- là giống nhau 2>/dev/null nhưng ngắn hơn - điều này là không đúng sự thật. 2>&- đóng FD 2 gây ra lỗi trong chương trình khi nó cố gắng ghi vào stderr, điều này rất khác với việc viết thành công cho nó và loại bỏ đầu ra (và nguy hiểm!))

Nếu băm băm của bạn là /bin/sh thì bạn nên quan tâm đến những gì POSIX nói. type và hashCác mã thoát không được xác định rõ ràng bởi POSIX và hash được nhìn thấy để thoát thành công khi lệnh không tồn tại (chưa thấy điều này với type chưa). commandTrạng thái thoát của POSIX được xác định rõ ràng bởi POSIX, do đó có thể là an toàn nhất để sử dụng.

Nếu tập lệnh của bạn sử dụng bash mặc dù, các quy tắc POSIX không thực sự quan trọng nữa và cả hai type và hash trở nên hoàn toàn an toàn để sử dụng. type bây giờ có một -P để chỉ tìm kiếm PATH và hash có tác dụng phụ là vị trí của lệnh sẽ được băm (để tra cứu nhanh hơn lần sau khi bạn sử dụng nó), thường là một điều tốt vì bạn có thể kiểm tra sự tồn tại của nó để thực sự sử dụng nó.

Như một ví dụ đơn giản, đây là một hàm chạy gdate nếu nó tồn tại, nếu không date:

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "$@"
    else
        date "$@"
    fi
}

2286
2018-03-24 12:45



@Geert: Phần &> / dev / null ẩn thông báo 'loại' phát ra khi 'foo' không tồn tại. Các> & 2 trên echo làm cho chắc chắn để gửi thông báo lỗi đến lỗi tiêu chuẩn thay vì đầu ra tiêu chuẩn; bởi vì đó là quy ước. Cả hai đều xuất hiện trên thiết bị đầu cuối của bạn, nhưng lỗi chuẩn chắc chắn là đầu ra ưu tiên cho các thông báo lỗi và cảnh báo không mong muốn. - lhunath
cờ -P không hoạt động trong 'sh', vd stackoverflow.com/questions/2608688/… - momeara
Đối với những người không quen với chuyển hướng i / o 'nâng cao' trong bash: 1) 2>&-  ("mô tả tập tin đầu ra gần 2", là tiêu chuẩn) có cùng kết quả như 2> /dev/null; 2) >&2 là lối tắt cho 1>&2, mà bạn có thể nhận ra là "chuyển hướng stdout thành stderr". Xem Trang hướng dẫn kịch bản Bash nâng cao i / o trang chuyển hướng để biết thêm thông tin. - mikewaters
Giải pháp này không hoạt động với FreeBSD / sh. băm trên FreeBSD luôn trả về với mã 0 - jyavenard
@mikewaters 2>&- Là không phải giống như 2>/dev/null. Trước đây đóng trình mô tả tệp, trong khi sau đó chỉ chuyển hướng mô tả đó sang /dev/null. Bạn có thể không thấy lỗi vì chương trình cố gắng thông báo cho bạn về stderr rằng stderr bị đóng. - nyuszika7h


Sau đây là một cách di động để kiểm tra xem một lệnh tồn tại trong $PATH   có thể thực thi được:

[ -x "$(command -v foo)" ]

Thí dụ:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi

Kiểm tra thực thi là cần thiết vì bash trả về một tệp không thực thi được nếu không tìm thấy tệp thực thi nào với tên đó $PATH.

Cũng lưu ý rằng nếu một tệp không thực thi có cùng tên với tệp thực thi tồn tại trước đó trong $PATH, dấu gạch ngang trả về giá trị cũ, mặc dù dấu gạch ngang sẽ được thực thi. Đây là lỗi và vi phạm tiêu chuẩn POSIX. [Báo cáo lỗi] [Tiêu chuẩn]

Ngoài ra, điều này sẽ thất bại nếu lệnh bạn đang tìm kiếm đã được định nghĩa là một bí danh.


239
2017-11-05 14:33



Tôi thích câu trả lời này bởi vì bài kiểm tra rất dễ sử dụng trong một kịch bản. Nó không yêu cầu tạo một hàm. - Stephen Ostermiller
Sẽ command -v tạo ra một con đường ngay cả đối với một tập tin không thực thi được? Đó là, -x thực sự cần thiết? - einpoklum
@einpoklum -x kiểm tra rằng tệp có thể thực thi được, đó là câu hỏi là gì. - Ken Sharp
@KenSharp: Nhưng điều đó có vẻ là thừa, vì command bản thân nó sẽ thử nghiệm cho nó đang được thực thi - phải không? - einpoklum
@einpoklum Có, nó là cần thiết. Trong thực tế, ngay cả giải pháp này có thể phá vỡ trong một trường hợp cạnh. Cảm ơn vì đã mang đến sự chú ý của tôi. dấu gạch ngang, bash và zsh tất cả bỏ qua các tệp không thực thi được trong $PATH khi thực hiện một lệnh. Tuy nhiên, hành vi của command -v rất không nhất quán. Trong dấu gạch ngang, nó trả về tệp khớp đầu tiên trong $PATH, bất kể nó có khả thi hay không. Trong bash, nó trả về kết quả khớp đầu tiên trong $PATH, nhưng nếu không có, nó có thể trả về một tập tin không thực thi được. Và trong zsh, nó sẽ không bao giờ trả về một tệp không thực thi được. - nyuszika7h


Tôi đồng ý với lhunath để ngăn cản việc sử dụng whichvà giải pháp của anh ấy hoàn toàn hợp lệ cho người dùng BASH. Tuy nhiên, để di động hơn, command -v sẽ được sử dụng thay thế:

$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed.  Aborting." >&2; exit 1; }

Chỉ huy command tuân thủ POSIX, xem tại đây để biết thông số kỹ thuật của nó: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Chú thích: type là POSIX tuân thủ, nhưng type -P không phải là.


190
2018-01-24 18:16



Giống như trên - exit 1; giết một xterm, nếu được gọi từ đó. - user unknown
Điều này sẽ không hoạt động trên sh chuẩn: bạn &> không phải là hướng dẫn chuyển hướng hợp lệ. - jyavenard
@jyavenard: Câu hỏi được gắn thẻ bash, do đó ký hiệu chuyển hướng bash cụ thể hơn súc tích &>/dev/null. Tuy nhiên, tôi đồng ý với bạn, những gì thực sự quan trọng là tính di động, tôi đã chỉnh sửa câu trả lời của tôi cho phù hợp, bây giờ sử dụng chuyển hướng sh tiêu chuẩn >/dev/null 2>&1. - GregV
để thậm chí cải thiện thêm câu trả lời này, tôi sẽ làm hai việc: 1: sử dụng "&>" để đơn giản hóa nó, như câu trả lời của Josh. 2: phá vỡ {} thành một dòng phụ, đặt một tab trước tiếng vang, để dễ đọc - knocte
Tôi chỉ đặt một lớp lót này vào một hàm bash nếu ai đó muốn nó ... github.com/equant/my_bash_tools/blob/master/tarp.bash - equant


Tôi có một hàm được định nghĩa trong .bashrc của tôi làm cho việc này trở nên dễ dàng hơn.

command_exists () {
    type "$1" &> /dev/null ;
}

Đây là một ví dụ về cách nó được sử dụng (từ của tôi .bash_profile.)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi

81
2017-10-14 09:24



Cái gì &> làm gì? - Saad Malik
Các &>  chuyển hướng cả stdout và stderr cùng với nhau. - Josh Strater
Chỉ làm việc với type "$1" > /dev/null 2>&1 - Marcello de Sales
&> có thể không có sẵn trong phiên bản Bash của bạn. Mã của Marcello sẽ hoạt động tốt; Nó làm điều tương tự. - Josh Strater


Nó phụ thuộc vào việc bạn muốn biết liệu nó tồn tại trong một trong các thư mục trong $PATH biến hoặc cho dù bạn biết vị trí tuyệt đối của nó. Nếu bạn muốn biết nếu nó ở trong $PATH biến, sử dụng

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi

nếu không sử dụng

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi

Chuyển hướng đến /dev/null/ trong ví dụ đầu tiên ngăn chặn đầu ra của which chương trình.


66
2018-02-26 22:01



Bạn thực sự không nên sử dụng "cái nào" vì những lý do được nêu trong bình luận của tôi. - lhunath


Mở rộng các câu trả lời của @ lhunath và @ GregV, đây là mã cho những người muốn dễ dàng đặt kiểm tra đó bên trong một if tuyên bố:

exists()
{
  command -v "$1" >/dev/null 2>&1
}

Dưới đây là cách sử dụng:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi

27
2017-12-07 21:17



Hầu như vụng về như nó có thể được. Đọc về trạng thái thoát trong man bash và học cách sử dụng nó. Nó sẽ làm cho mã của bạn đơn giản hơn và thanh lịch hơn. Các dấu ngoặc đơn không phải là một phần của if cú pháp, chúng chỉ là viết tắt của lệnh test. if kiểm tra xem liệu lệnh có thành công hay không (có trạng thái thoát là 0). - Palec
@Palec: Bạn nói đúng, nó là khá vụng về. Tôi làm sạch nó một chút và tôi hy vọng nó có vẻ thích hợp hơn bây giờ. - Romário
Sẵn sàng để học hỏi và cải thiện phải được khen thưởng. +1 Điều này thật sạch sẽ và đơn giản. Điều duy nhất tôi có thể thêm là command thành công ngay cả đối với bí danh, có thể có phần phản trực giác. Việc kiểm tra sự tồn tại trong một trình bao tương tác sẽ cung cấp các kết quả khác nhau khi bạn di chuyển nó sang một tập lệnh. - Palec
Tôi vừa kiểm tra và sử dụng shopt -u expand_aliases bỏ qua / ẩn bí danh (như alias ls='ls -F' đã đề cập trong câu trả lời khác) và shopt -s expand_aliases giải quyết chúng qua command -v. Vì vậy, có lẽ nó nên được thiết lập trước khi kiểm tra và bỏ đặt sau, mặc dù nó có thể ảnh hưởng đến giá trị trả về hàm nếu bạn không nắm bắt và trả về đầu ra của lệnh gọi một cách rõ ràng. - dragon788


Thử sử dụng:

test -x filename

hoặc là

[ -x filename ]

Từ manpage bash dưới Biểu thức có điều kiện:

 -x file
          True if file exists and is executable.

19
2018-02-26 21:57



Điều đó có nghĩa là bạn cần phải biết đường dẫn đầy đủ đến ứng dụng. - lhunath
OP đã không chỉ định nếu anh ta muốn kiểm tra một trường hợp cụ thể hoặc cho bất kỳ thể hiện thực thi nào ... Tôi đã trả lời nó theo cách tôi đọc nó. - dmckee


Để sử dụng hash, như @lhunath đề xuất, trong một tập lệnh bash:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi

Tập lệnh này chạy hash và sau đó kiểm tra xem mã thoát của lệnh gần đây nhất, giá trị được lưu trữ trong $?, bằng 1. Nếu hash không tìm thấy foo, mã thoát sẽ là 1. Nếu foo có mặt, mã thoát sẽ là 0.

&> /dev/null chuyển hướng lỗi chuẩn và đầu ra tiêu chuẩn từ hash để nó không xuất hiện trên màn hình và echo >&2 ghi thông điệp vào lỗi tiêu chuẩn.


16
2018-06-24 17:01



Tại sao không chỉ if hash foo &> /dev/null; then ... ? - Beni Cherniavsky-Paskin