Kiểm tra xem một quả cầu có bất kỳ trận đấu trong bash


223

Nếu tôi muốn kiểm tra sự tồn tại của một tệp duy nhất, tôi có thể kiểm tra tệp đó bằng cách sử dụng test -e filenamehoặc [ -e filename ].

Giả sử tôi có một quả địa cầu và tôi muốn biết liệu có tệp nào tồn tại có tên phù hợp với quả cầu hay không. Toàn cầu có thể khớp với 0 tệp (trong trường hợp tôi không cần làm gì) hoặc nó có thể khớp với 1 hoặc nhiều tệp (trong trường hợp đó tôi cần phải làm gì đó). Làm thế nào tôi có thể kiểm tra xem một quả cầu có bất kỳ trận đấu? (Tôi không quan tâm có bao nhiêu trận đấu, và sẽ tốt nhất nếu tôi có thể làm điều này với một ifcâu lệnh và không có vòng lặp (đơn giản vì tôi thấy điều đó dễ đọc nhất).

( test -e glob*không thành công nếu toàn cầu khớp với nhiều hơn một tệp.)


3
Tôi nghi ngờ câu trả lời của tôi dưới đây là 'chính xác rõ ràng' theo cách mà tất cả những người khác có thể hack. Đó là một công cụ chế tạo vỏ một dòng tồn tại mãi mãi và dường như là 'công cụ dự định cho công việc đặc biệt này'. Tôi lo ngại rằng người dùng sẽ tham khảo nhầm câu trả lời được chấp nhận ở đây. Bất cứ ai xin vui lòng sửa tôi và tôi sẽ rút bình luận của tôi ở đây, tôi rất vui khi được sai và học hỏi từ nó. Nếu sự khác biệt không xuất hiện quá lớn, tôi sẽ không nêu ra vấn đề này.
Brian Chrisman

1
Các giải pháp yêu thích của tôi cho câu hỏi này là lệnh find hoạt động trong mọi shell (thậm chí cả shell không Bourne) nhưng yêu cầu GNU tìm và lệnh compgen rõ ràng là Bashism. Quá tệ, tôi không thể chấp nhận cả hai câu trả lời.
Ken Bloom

Lưu ý: Câu hỏi này đã được chỉnh sửa kể từ khi được hỏi. Tiêu đề ban đầu là "Kiểm tra xem một quả cầu có bất kỳ trận đấu nào trong bash không". Vỏ cụ thể, 'bash', đã bị loại khỏi câu hỏi sau khi tôi công bố câu trả lời của mình. Việc chỉnh sửa tiêu đề của câu hỏi khiến câu trả lời của tôi có vẻ bị lỗi. Tôi hy vọng ai đó có thể sửa đổi hoặc ít nhất là giải quyết thay đổi này.
Brian Chrisman

Câu trả lời:


178

Giải pháp cụ thể của Bash :

compgen -G "<glob-pattern>"

Thoát khỏi mô hình hoặc nó sẽ được mở rộng trước thành các trận đấu.

Trạng thái thoát là:

  • 1 cho trận đấu,
  • 0 cho 'một hoặc nhiều trận đấu'

stdoutlà một danh sách các tập tin phù hợp với toàn cầu .
Tôi nghĩ rằng đây là lựa chọn tốt nhất về sự đồng nhất và giảm thiểu các tác dụng phụ tiềm ẩn.

CẬP NHẬT : Yêu cầu sử dụng ví dụ.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi

9
Lưu ý rằng đó compgenlà một lệnh tích hợp bash- cụ thể và không phải là một phần của lệnh Unix dựng sẵn tiêu chuẩn POSIX. pubs.opengroup.org/onlinepub/9699919799 pubs.opengroup.org/onlinepub/9699919799/utilities/ trộm Do đó, tránh sử dụng nó trong các tập lệnh trong đó tính di động đối với các shell khác là mối quan tâm.
Diomidis Spinellis

1
Dường như với tôi, một hiệu ứng tương tự không có bash dựng sẽ là sử dụng bất kỳ lệnh nào khác hoạt động trên toàn cầu và không thành công nếu không có tệp nào khớp, chẳng hạn như ls: if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi- có thể hữu ích cho mã golf? Thất bại nếu có một tệp có tên giống như toàn cầu, mà toàn cầu không nên khớp, nhưng nếu đó là trường hợp bạn có thể gặp vấn đề lớn hơn.
Dewi Morgan

4
@DewiMorgan Điều này đơn giản hơn:if ls /tmp/*Files &> /dev/null; then echo exists; fi
Cầu đất sét

Để biết chi tiết về compgen, hãy xem man bashhoặc vớihelp compgen
el-teedee

2
yeah, trích dẫn nó hoặc ký tự đại diện tên tệp sẽ được mở rộng trước. compgen "dir / *. ext"
Brian Chrisman

169

Tùy chọn shell nullglob thực sự là một bashism.

Để tránh sự cần thiết phải lưu và khôi phục trạng thái nullglob tẻ nhạt, tôi chỉ đặt nó bên trong lớp con mở rộng toàn cầu:

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

Để có tính di động tốt hơn và toàn cầu hóa linh hoạt hơn, hãy sử dụng find:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

Các hành động -print -print rõ ràng được sử dụng để tìm thay vì hành động in- ẩn mặc định để tìm sẽ thoát ngay khi tìm thấy tệp đầu tiên phù hợp với tiêu chí tìm kiếm. Trong trường hợp nhiều tệp khớp nhau, tệp này sẽ chạy nhanh hơn nhiều echo glob*hoặc ls glob*cũng tránh được khả năng thừa các dòng lệnh mở rộng (một số shell có giới hạn độ dài 4K).

Nếu tìm thấy cảm giác như quá mức cần thiết và số lượng file có khả năng trận đấu là nhỏ, sử dụng stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi

10
finddường như là chính xác Nó không có trường hợp góc, vì shell không thực hiện mở rộng (và chuyển một quả cầu chưa được mở rộng sang một số lệnh khác), nên nó di động giữa các shell (mặc dù dường như không phải tất cả các tùy chọn bạn sử dụng đều được POSIX chỉ định) và nhanh hơn ls -d glob*(câu trả lời được chấp nhận trước đó) vì nó dừng lại khi đến trận đấu đầu tiên.
Ken Bloom

1
Lưu ý rằng câu trả lời này có thể yêu cầu shopt -u failglobvì các tùy chọn này dường như mâu thuẫn bằng cách nào đó.
Calimo

Các findgiải pháp sẽ phù hợp với một tên tập tin không có ký tự glob là tốt. Trong trường hợp này, đó là những gì tôi muốn. Chỉ cần một cái gì đó để nhận thức mặc dù.
Chúng ta là tất cả Monica

1
Vì ai đó quyết định chỉnh sửa câu trả lời của tôi để làm cho nó nói điều đó, rõ ràng.
flabdablet

1
unix.stackexchange.com/questions/275637/ từ thảo luận về cách thay thế -maxdepthtùy chọn cho tìm POSIX.
Ken Bloom

25
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi

2
Để tránh một sai lầm có thể có, không có trận đấu nào được đặt ra nullglobthay vì kiểm tra xem liệu một kết quả có bằng chính mẫu đó không. Một số mẫu có thể khớp với các tên chính xác bằng chính mẫu đó (ví dụ: a*bnhưng không phải ví dụ a?bhoặc [a]).
Chris Johnsen

Tôi cho rằng điều này thất bại trong khả năng rất khó xảy ra là thực sự có một tệp có tên như toàn cầu. (ví dụ ai đó đã chạy touch '*py'), nhưng điều này chỉ cho tôi một hướng tốt khác.
Ken Bloom

Tôi thích cái này là phiên bản chung nhất.
Ken Bloom

Và cũng ngắn nhất. Nếu bạn chỉ mong đợi một trận đấu, bạn có thể sử dụng "$M"như một cách viết tắt cho "${M[0]}". Mặt khác, bạn đã có bản mở rộng toàn cầu trong một biến mảng, vì vậy bạn sẽ gtg để chuyển nó sang những thứ khác dưới dạng danh sách, thay vì làm cho chúng mở rộng lại toàn cầu.
Peter Cordes

Đẹp. Bạn có thể kiểm tra M nhanh hơn (ít byte hơn và không sinh ra một [quy trình) vớiif [[ $M ]]; then ...
Tobia

22

tôi thích

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

Điều này vừa dễ đọc vừa hiệu quả (trừ khi có một số lượng lớn tệp).
Hạn chế chính là nó tinh tế hơn nhiều so với vẻ ngoài của nó và đôi khi tôi cảm thấy bắt buộc phải thêm một bình luận dài.
Nếu có một trận đấu, "glob*"được mở rộng bằng vỏ và tất cả các trận đấu được chuyển đến exists(), kiểm tra cái đầu tiên và bỏ qua phần còn lại.
Nếu không có kết quả khớp, "glob*"được chuyển đến exists()và được tìm thấy cũng không tồn tại ở đó.

Chỉnh sửa: có thể có một dương tính giả, xem bình luận


13
Nó có thể trả về dương tính giả nếu toàn cầu là một cái gì đó giống như *.[cC](có thể không có choặc Ctệp, nhưng một tệp được gọi *.[cC]) hoặc âm tính giả nếu tệp đầu tiên được mở rộng từ đó là một liên kết tượng trưng đến một tệp không tồn tại hoặc đến một tệp trong một thư mục bạn không có quyền truy cập (bạn muốn thêm một || [ -L "$1" ]).
Stephane Chazelas

Hấp dẫn. Shellcheck báo cáo rằng globalbing chỉ hoạt động với -e, khi có 0 hoặc 1 trận đấu. Nó không hoạt động cho nhiều trận đấu, bởi vì điều đó sẽ trở thành [ -e file1 file2 ]và điều này sẽ thất bại. Đồng thời xem github.com/koalaman/shellcheck/wiki/SC2144 để biết các giải pháp hợp lý và được đề xuất.
Thomas Praxl

10

Nếu bạn đã cài đặt globalfail, bạn có thể sử dụng cái điên này (mà bạn thực sự không nên)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

hoặc là

q=( * ) && echo 0 || echo 1

2
Một sử dụng tuyệt vời của một thất bại noop. Không bao giờ nên được sử dụng ... nhưng thực sự đẹp. :)
Brian Chrisman

Bạn có thể đặt shopt bên trong parens. Bằng cách đó, nó chỉ ảnh hưởng đến bài kiểm tra:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet

8

test -e có một cảnh báo đáng tiếc rằng nó coi các liên kết tượng trưng bị hỏng là không tồn tại. Vì vậy, bạn có thể muốn kiểm tra cho những người quá.

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi

4
Điều đó vẫn không khắc phục được tính tích cực giả trên tên tệp có các ký tự đặc biệt toàn cầu trong đó, như Stephane Chazelas chỉ ra câu trả lời của Dan Bloch. (trừ khi bạn khỉ với nullglob).
Peter Cordes

3
Bạn nên tránh -o-atrong test/ [. Ví dụ, ở đây, nó không thành công nếu $1=với hầu hết các trường. Sử dụng [ -e "$1" ] || [ -L "$1" ]thay thế.
Stephane Chazelas

4

Để đơn giản hóa phần nào câu trả lời của MYYN, dựa trên ý tưởng của anh ấy:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi

4
Đóng, nhưng nếu bạn khớp [a], có một tệp có tên [a], nhưng không có tệp nào được đặt tên a? Tôi vẫn thích nullglobđiều này. Một số người có thể xem điều này là phạm vi, nhưng chúng tôi có thể hoàn toàn chính xác là hợp lý.
Chris Johnsen

@ sondra.kinsey Điều đó sai; toàn cầu [a]chỉ nên khớp a, không phải tên tệp theo nghĩa đen [a].
tripleee

4

Dựa trên câu trả lời của flabdablet , đối với tôi có vẻ như dễ nhất (không nhất thiết phải nhanh nhất) chỉ là sử dụng tìm chính nó, trong khi để mở rộng toàn cầu trên vỏ, như:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

Hoặc ifnhư:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi

Điều này hoạt động chính xác như phiên bản tôi đã trình bày bằng cách sử dụng stat; không chắc chắn làm thế nào tìm thấy là "dễ dàng" hơn stat.
flabdablet

3
Xin lưu ý rằng &> chuyển hướng là một bashism, và sẽ lặng lẽ làm điều sai trái trong các vỏ khác.
flabdablet

Điều này có vẻ tốt hơn findcâu trả lời của flabdablet vì nó chấp nhận các đường dẫn trên toàn cầu và nó ngắn gọn hơn (không yêu cầu -maxdepthvv). Nó cũng có vẻ tốt hơn statcâu trả lời của anh ta vì nó không tiếp tục làm thêm statvào mỗi trận đấu toàn cầu bổ sung. Tôi đánh giá cao nếu bất cứ ai có thể đóng góp các trường hợp góc mà điều này không hoạt động.
drwatson

1
Sau khi xem xét kỹ hơn, tôi sẽ thêm -maxdepth 0vì nó cho phép linh hoạt hơn trong việc thêm điều kiện. ví dụ: giả sử tôi muốn giới hạn kết quả chỉ với các tệp phù hợp. Tôi có thể thử find $glob -type f -quit, nhưng điều đó sẽ trả về đúng nếu toàn cầu KHÔNG khớp với tệp, nhưng khớp với thư mục chứa tệp (thậm chí là đệ quy). Ngược lại find $glob -maxdepth 0 -type f -quitsẽ chỉ trả về true nếu toàn cầu khớp với ít nhất một tệp. Lưu ý rằng maxdepthkhông ngăn chặn toàn cầu có một thành phần thư mục. (FYI 2>là đủ. Không cần &>)
drwatsoncode

2
Điểm quan trọng của việc sử dụng findở nơi đầu tiên là để tránh việc tạo vỏ và sắp xếp một danh sách các trận đấu toàn cầu có khả năng lớn; find -name ... -quitsẽ khớp tối đa một tên tệp. Nếu một tập lệnh dựa vào việc chuyển một danh sách các trận đấu toàn cầu do shell tạo ra find, việc gọi sẽ findkhông đạt được gì ngoài chi phí khởi động quy trình không cần thiết. Đơn giản chỉ cần kiểm tra danh sách kết quả trực tiếp cho sự không trống rỗng sẽ nhanh hơn và rõ ràng hơn.
flabdablet

4

Tôi có một giải pháp khác:

if [ "$(echo glob*)" != 'glob*' ]

Điều này làm việc độc đáo cho tôi. Có một số trường hợp góc tôi bỏ lỡ?


2
Hoạt động trừ khi tập tin thực sự được đặt tên là 'global *'.
Ian Kelling

không hoạt động để truyền trong toàn cầu dưới dạng biến - đưa ra lỗi "quá nhiều đối số" khi có nhiều hơn một kết quả khớp. "$ (echo $ GLOB)" không trả về một chuỗi hoặc ít nhất nó không được hiểu là một đơn do đó có quá nhiều lỗi đối số
DKebler

@DKebler: nó nên được hiểu là một chuỗi, bởi vì nó được gói trong dấu ngoặc kép.
dùng1934428

3

Trong Bash, bạn có thể toàn cầu đến một mảng; nếu toàn cầu không khớp, mảng của bạn sẽ chứa một mục không tương ứng với tệp hiện có:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

Lưu ý: nếu bạn đã nullglobđặt, scriptssẽ là một mảng trống và bạn nên kiểm tra bằng [ "${scripts[*]}" ]hoặc [ "${#scripts[*]}" != 0 ]thay vào đó. Nếu bạn đang viết thư viện phải làm việc có hoặc không có nullglob, bạn sẽ muốn

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

Một lợi thế của phương pháp này là sau đó bạn có danh sách các tệp bạn muốn làm việc, thay vì phải lặp lại hoạt động toàn cầu.


Tại sao, với tập hợp nullglob và mảng có thể trống, bạn vẫn không thể kiểm tra if [ -e "${scripts[0]}" ]...? Bạn cũng cho phép khả năng vỏ tùy chọn nounset bộ?
johnraff

@johnraff, vâng, tôi thường cho nounsetlà đang hoạt động. Ngoài ra, có thể rẻ hơn (một chút) để kiểm tra chuỗi không trống hơn là kiểm tra sự hiện diện của tệp. Mặc dù không chắc, do chúng ta vừa thực hiện một quả địa cầu, có nghĩa là nội dung thư mục sẽ mới trong bộ đệm của hệ điều hành.
Toby Speight

1

Sự ghê tởm này dường như có tác dụng:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

Nó có thể yêu cầu bash, không sh.

Điều này hoạt động vì tùy chọn nullglob làm cho toàn cầu đánh giá thành một chuỗi trống nếu không có kết quả khớp. Do đó, bất kỳ đầu ra không trống nào từ lệnh echo chỉ ra rằng quả cầu khớp với một cái gì đó.


Bạn nên sử dụngif [ "`echo *py`" != "*py"]
yegle 22/214

1
Điều đó sẽ không hoạt động chính xác nếu có một tập tin được gọi *py.
Ryan C. Thompson

Nếu không có tập tin kết thúc py, `echo *py`sẽ được đánh giá *py.
yegle

1
Có, nhưng nó cũng sẽ làm như vậy nếu có một tệp duy nhất được gọi *py, đó là kết quả sai.
Ryan C. Thompson

Chỉnh sửa cho tôi nếu tôi sai, nhưng nếu không có tệp nào khớp *py, tập lệnh của bạn sẽ lặp lại "Glob khớp"?
yegle

1

Tôi không thấy câu trả lời này, vì vậy tôi nghĩ rằng tôi đã đưa nó ra ngoài đó:

set -- glob*
[ -f "$1" ] && echo "found $@"

1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

Giải trình

Khi không có kết quả phù hợp glob*thì $1sẽ chứa 'glob*'. Thử nghiệm -f "$1"sẽ không đúng vì glob*tệp không tồn tại.

Tại sao điều này là tốt hơn so với lựa chọn thay thế

Điều này hoạt động với sh và dẫn xuất: ksh và bash. Nó không tạo ra bất kỳ shell phụ. $(..)`...`các lệnh tạo một lớp vỏ phụ; họ rẽ nhánh một quá trình, và do đó chậm hơn giải pháp này.


1

Như thế này trong (tệp kiểm tra có chứa pattern):

shopt -s nullglob
compgen -W *pattern* &>/dev/null
case $? in
    0) echo "only one file match" ;;
    1) echo "more than one file match" ;;
    2) echo "no file match" ;;
esac

Nó tốt hơn nhiều so với compgen -G: bởi vì chúng ta có thể phân biệt nhiều trường hợp và chính xác hơn.

Có thể làm việc chỉ với một ký tự đại diện *


0
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

Lưu ý rằng điều này có thể rất mất thời gian nếu có nhiều trận đấu hoặc truy cập tệp bị chậm.


1
Điều này sẽ đưa ra câu trả lời sai nếu một mẫu như [a]được sử dụng khi tệp [a]có mặt và tệp akhông có. Nó sẽ nói rằng đã tìm thấy những người mặc dù tập tin duy nhất mà nó phù hợp, anhưng không thực sự có mặt.
Chris Johnsen

Phiên bản này sẽ hoạt động trong POSIX / bin / sh thông thường (không có bashism) và trong trường hợp tôi cần nó, toàn cầu không có dấu ngoặc nào và tôi không cần phải lo lắng về các trường hợp đó là bệnh hoạn khủng khiếp. Nhưng tôi đoán rằng không có cách nào tốt để kiểm tra xem có tệp nào khớp với toàn cầu hay không.
Ken Bloom

0
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi

2
Phiên bản này không thành công khi chính xác một tệp khớp với nhau, nhưng bạn có thể tránh được hàm FOUND = -1 bằng cách sử dụng nullglobtùy chọn shell.
Ken Bloom

@Ken: Hmm, tôi sẽ không gọi nullgloblà bùn. So sánh một kết quả duy nhất với mẫu ban đầu là một loại bùn (và dễ bị kết quả sai), sử dụng nullglobthì không.
Chris Johnsen

@Chris: Tôi nghĩ bạn đọc sai. Tôi đã không gọi nullgloblà bùn.
Ken Bloom

1
@Ken: Thật vậy, tôi đã đọc sai. Hãy chấp nhận lời xin lỗi của tôi cho những lời chỉ trích không hợp lệ của tôi.
Chris Johnsen

-1
(ls glob* &>/dev/null && echo Files found) || echo No file found

5
Cũng sẽ trả về false nếu có các thư mục khớp glob*và ví dụ bạn không có ghi để liệt kê các thư mục đó.
Stephane Chazelas

-1

ls | grep -q "glob.*"

Không phải là giải pháp hiệu quả nhất (nếu có rất nhiều tệp trong thư mục thì nó có thể chậm), nhưng nó đơn giản, dễ đọc và cũng có ưu điểm là regexes mạnh hơn các mẫu bash đơn giản.


-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true

1
Để có câu trả lời tốt hơn, hãy thử thêm một số giải thích cho mã của bạn.
Masoud Rahimi
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.