Kiểm tra xem có tệp nào khớp với mẫu để thực thi tập lệnh không


29

Tôi đang cố gắng viết một iftuyên bố để kiểm tra xem có tệp nào khớp với một mẫu nhất định không. Nếu có một tệp văn bản trong một thư mục, nó sẽ chạy một tập lệnh đã cho.

Mã của tôi hiện tại:

if [ -f /*.txt ]; then ./script fi

Xin vui lòng cho một số ý tưởng; Tôi chỉ muốn chạy tập lệnh nếu có .txttrong thư mục.


3
Bạn có chắc chắn "thư mục" được cho là /? Ngoài ra, bạn đang thiếu một dấu chấm phẩy trước đó fi.
tước

Giải pháp mạnh mẽ nhất mà tôi gặp phải là sử dụng findnhư được giải thích ở đây trên stackoverflow .
Joshua Goldberg

Câu trả lời:


39
[ -f /*.txt ]

sẽ chỉ trả về đúng nếu có một (và chỉ một) tệp không bị ẩn trong /đó có tên kết thúc .txtvà nếu tệp đó là tệp thông thường hoặc liên kết tượng trưng đến tệp thông thường.

Đó là bởi vì các ký tự đại diện được mở rộng bằng vỏ trước khi được truyền cho lệnh (ở đây [).

Vì vậy, nếu có một /a.txt/b.txt, [sẽ được thông qua 5 đối số: [, -f, /a.txt, /b.txt]. [sau đó sẽ phàn nàn rằng -fđược đưa ra quá nhiều đối số.

Nếu bạn muốn kiểm tra xem *.txtmẫu có mở rộng thành ít nhất một tệp không bị ẩn (thường xuyên hay không):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobbashcụ thể, nhưng vỏ thích ksh93, zsh, yash, tcshcó báo cáo tương đương.

Lưu ý rằng nó tìm thấy các tệp đó bằng cách đọc nội dung của thư mục, nó không thử và truy cập các tệp đó, điều này làm cho nó hiệu quả hơn các giải pháp gọi các lệnh như lshoặc stattrên danh sách các tệp được tính toán bởi trình bao.

shTương đương tiêu chuẩn sẽ là:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Vấn đề là với các vỏ Bourne hoặc POSIX, nếu một mẫu không khớp, nó sẽ tự mở rộng. Vì vậy, nếu *.txtmở rộng ra *.txt, bạn không biết liệu đó là vì không có .txttệp nào trong thư mục hay vì có một tệp được gọi *.txt. Sử dụng [*].txt *.txtcho phép phân biệt giữa hai.


[ -f /*.txt ]là khá nhanh so với compgen.
Daniel Böhmer

@ DanielBöhmer [ -f /*.txt ]sẽ sai, nhưng trong thử nghiệm của tôi trên một thư mục chứa 3425các tệp, 94trong đó là các tệp txt không bị ẩn, compgen -G "*.txt" > /dev/null 2>&1dường như nhanh như set -- *.txt; [ "$#" -gt 0 ](20,5 giây cho cả hai khi lặp lại 10000 lần trong trường hợp của tôi).
Stéphane Chazelas

11

Bạn luôn có thể sử dụng find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Giải trình:

  • find . : tìm kiếm thư mục hiện tại
  • -maxdepth 1: không tìm kiếm thư mục con
  • -type f : chỉ tìm kiếm các tập tin thông thường
  • name "*.txt" : tìm kiếm các tập tin kết thúc bằng .txt
  • 2>/dev/null : chuyển hướng thông báo lỗi đến /dev/null
  • | grep -q . : grep cho bất kỳ ký tự nào, sẽ trả về false nếu không tìm thấy ký tự nào.
  • && ./script: ./scriptChỉ thực thi nếu lệnh trước thành công ( &&)

2
findchỉ trả về false nếu gặp sự cố khi tìm tệp chứ không trả về nếu không tìm thấy tệp nào. Bạn muốn đặt đầu ra grep -q .để kiểm tra xem nó có tìm thấy thứ gì không.
Stéphane Chazelas

@StephaneChazelas bạn hoàn toàn đúng. Thật kỳ lạ, tôi đã thử nó và nó dường như hoạt động. Phải làm điều gì đó kỳ lạ bởi vì nó không còn nữa. Khi nào sẽ tìm thấy "gặp khó khăn khi tìm tập tin"?
terdon

@terdon, giống như khi một số thư mục không thể truy cập được, hoặc lỗi I / O hoặc bất kỳ lỗi nào được trả về bởi bất kỳ cuộc gọi hệ thống nào. Trong trường hợp đó, hãy thử sau chmod a-x ..
Stéphane Chazelas

8

Một giải pháp khả thi cũng là Bash dựng sẵn compgen. Lệnh đó trả về tất cả các kết quả khớp có thể có cho mẫu hình cầu và có mã thoát cho biết liệu có tệp nào khớp không.

compgen -G "/*.text" > /dev/null && ./script

Tôi tìm thấy câu hỏi này trong khi tìm kiếm các giải pháp nhanh hơn mặc dù.


1
Tìm tốt Nếu bạn đang ở một miền địa phương nhiều byte, bạn có thể cải thiện nó một chút với LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas

7

Đây là một lớp lót để làm điều đó:

$ ls
file1.pl  file2.pl

tập tin tồn tại

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

tập tin không tồn tại

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Cách tiếp cận này sử dụng các toán tử ||&&toán tử trong bash. Đây là các toán tử "hoặc" và "và".

Vì vậy, nếu lệnh stat trả về $?bằng 0 thì lệnh đầu tiên echođược gọi, nếu nó trả về 1, thì lệnh thứ hai echođược gọi.

kết quả trả về từ stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Câu hỏi này được bao quát rộng rãi trên stackoverflow:


1
Tại sao sử dụng phi tiêu chuẩn statkhi ls -dcó thể làm tương tự?
Stéphane Chazelas

Tôi nghĩ ls -ddanh sách một thư mục? Có vẻ như không hoạt động khi tôi vừa thử liệt kê một thư mục chứa các tệp trong đó ls -d *.pl.
slm

Bạn có thể thay thế báo cáo kết quả bên trái của &&bằng ls *.txtvà nó sẽ làm việc tốt. Hãy chắc chắn rằng bạn gửi thiết bị xuất chuẩn và thiết bị xuất chuẩn /dev/nulltheo đề xuất của @slm.
unxnut

1
Nếu bạn sử dụng ls *.txtvà không có file hiện tại trong thư mục này sẽ trả về một $? = 2, mà sẽ vẫn làm việc với nếu sau đó, nhưng điều này là một trong những lý do tôi đã lựa chọn stathơn ls. Tôi muốn 0 cho thành công và 1 cho thất bại.
slm

ls -dlà để liệt kê các thư mục thay vì nội dung của họ. Vì vậy, ls -dchỉ cần làm lstattrên các tập tin, giống như GNU statlàm. Những lệnh trạng thái thoát khác không trả về khi thất bại là hệ thống cụ thể, sẽ không có ý nghĩa gì khi đưa ra các giả định về chúng.
Stéphane Chazelas

4

Như Chazelas chỉ ra, tập lệnh của bạn sẽ thất bại nếu việc mở rộng ký tự đại diện khớp với nhiều hơn một tệp.

Tuy nhiên, có một mẹo tôi sử dụng ( thậm chí tôi không thích lắm ) để đi lại:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Làm thế nào nó hoạt động?

Mở rộng ký tự đại diện sẽ khớp với một loạt tên tệp, chúng tôi nhận được cái đầu tiên nếu có một số, nếu không thì không có giá trị.


IMO đây là câu trả lời ít tệ nhất ở đây. Tất cả đều có vẻ khá kinh khủng, như thể một tính năng cơ bản bị thiếu trong ngôn ngữ.
cắm

@plugwash đó là cố ý ... * nix shell script có một số điều khiển luồng cơ bản và một vài tỷ lệ cược và kết thúc khác, nhưng vào cuối ngày, công việc của nó là kết dính các lệnh khác. Nếu bash hút ... đó là vì các lệnh bạn sử dụng từ nó hút
cb88

2
Đó là logic sai (và bạn đang thiếu dấu ngoặc kép). Điều đó kiểm tra nếu tệp phù hợp đầu tiên là một tệp thông thường. Nó cũng có thể là một tệp không thường xuyên nhưng có thể có một số .txttệp khác thuộc loại thường xuyên . Hãy thử ví dụ sau mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas

1

Đơn giản như:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l đếm các dòng trong ký tự đại diện mở rộng.


1
Downvote: Điều này thu thập một số lượng đáng kinh ngạc các antipotype mới bắt đầu trong một số lượng nhỏ mã. Bạn không nên phân tích lsđầu ra và hầu như không bao giờ kiểm tra $?trực tiếp vì ifđã làm điều đó. Ngoài ra, sử dụng wcđể xem nếu một cái gì đó xảy ra cũng bị đánh giá sai tương tự.
tripleee

0

Tôi thích giải pháp mảng trước đó, nhưng điều đó có thể trở nên lãng phí với số lượng lớn tệp - shell sẽ sử dụng rất nhiều bộ nhớ để xây dựng mảng và chỉ có phần tử đầu tiên được thử nghiệm.

Đây là một cấu trúc thay thế mà tôi đã thử nghiệm ngày hôm qua:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

Giá trị thoát khỏi grep dường như đang xác định đường dẫn thông qua cấu trúc điều khiển. Điều này cũng kiểm tra với các biểu thức thông thường hơn là các mẫu vỏ. Một số hệ thống của tôi có lệnh "pcregrep" cho phép khớp regex phức tạp hơn nhiều.

(Tôi đã chỉnh sửa câu trả lời này để loại bỏ "ls" trong lệnh thay thế sau khi đọc những lời chỉ trích ở trên để phân tích cú pháp.)


-2

nếu bạn muốn sử dụng mệnh đề if, hãy đánh giá số đếm:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
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.