Có một lệnh bash đếm các tập tin?


182

Có lệnh bash nào đếm số lượng tệp khớp với một mẫu không?

Ví dụ: tôi muốn lấy số lượng của tất cả các tệp trong một thư mục khớp với mẫu này: log*

Câu trả lời:


243

Lớp lót đơn giản này sẽ hoạt động trong mọi lớp vỏ, không chỉ bash:

ls -1q log* | wc -l

ls -1q sẽ cung cấp cho bạn một dòng trên mỗi tệp, ngay cả khi chúng chứa khoảng trắng hoặc ký tự đặc biệt như dòng mới.

Đầu ra được dẫn đến wc -l, tính số lượng dòng.


10
Tôi sẽ không sử dụng -l, vì điều đó đòi hỏi stat(2)trên mỗi tệp và cho mục đích đếm không thêm gì.
camh

12
Tôi sẽ không sử dụng ls, vì nó tạo ra một quá trình con. log*được mở rộng bởi shell, không ls, vì vậy một đơn giản echosẽ làm.
cdarke

2
Ngoại trừ tiếng vang sẽ không hoạt động nếu bạn có tên tệp có dấu cách hoặc ký tự đặc biệt.
Daniel

4
@WalterTross Điều đó đúng (không phải hiệu quả là yêu cầu của câu hỏi ban đầu). Tôi cũng chỉ thấy rằng -q chăm sóc các tệp với dòng mới, ngay cả khi đầu ra không phải là thiết bị đầu cuối. Và những lá cờ này được hỗ trợ bởi tất cả các nền tảng và vỏ mà tôi đã thử nghiệm. Cập nhật câu trả lời, cảm ơn bạn và camh cho đầu vào!
Daniel

3
Nếu có một thư mục được gọi logstrong thư mục được đề cập, thì nội dung của thư mục nhật ký đó cũng sẽ được tính. Điều này có lẽ không cố ý.
mogsie

54

Bạn có thể thực hiện việc này một cách an toàn (nghĩa là sẽ không bị lỗi bởi các tệp có dấu cách hoặc \ntrong tên của chúng) với bash:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

Bạn cần kích hoạt nullglobđể không nhận được chữ *.logtrong $logfiles mảng nếu không có tệp nào khớp. (Xem Cách "hoàn tác" một 'set -x'? Để biết ví dụ về cách đặt lại an toàn.)


2
Có lẽ chỉ ra một cách rõ ràng rằng đây là câu trả lời duy nhất của Bash , đặc biệt là đối với những khách truy cập mới chưa hoàn toàn tăng tốc về Sự khác biệt giữa sh và bash
tripleee

Ngoài ra, trận chung kết shopt -u nullglobnên được bỏ qua nếu nullglobkhông được đặt thì bạn đã bắt đầu.
tripleee

Lưu ý: Thay thế *.logbằng chỉ *sẽ đếm các thư mục. Nếu các tệp bạn muốn liệt kê có quy ước đặt tên truyền thống name.extension, hãy sử dụng *.*.
AlainD

52

Rất nhiều câu trả lời ở đây, nhưng một số không tính đến

  • tên tệp có dấu cách, dòng mới hoặc ký tự điều khiển trong đó
  • tên tệp bắt đầu bằng dấu gạch nối (hãy tưởng tượng một tệp được gọi -l)
  • các tệp bị ẩn, bắt đầu bằng dấu chấm (nếu toàn cầu *.logthay vìlog*
  • thư mục phù hợp với toàn cầu (ví dụ: thư mục được gọi logsphù hợp log*)
  • thư mục trống (tức là kết quả là 0)
  • thư mục cực lớn (liệt kê tất cả có thể làm cạn kiệt bộ nhớ)

Đây là một giải pháp xử lý tất cả chúng:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Giải trình:

  • -UNguyên nhân lskhông sắp xếp các mục, có nghĩa là nó không cần tải toàn bộ danh sách thư mục trong bộ nhớ
  • -bin các lối thoát kiểu C cho các ký tự không rõ ràng, chủ yếu gây ra các dòng mới được in dưới dạng \n.
  • -ain ra tất cả các tệp, thậm chí các tệp bị ẩn (không thực sự cần thiết khi toàn cầu log*ngụ ý không có tệp ẩn)
  • -din ra các thư mục mà không cố gắng liệt kê các nội dung của thư mục, đó là những gì lsthường làm
  • -1 đảm bảo rằng nó nằm trên một cột (ls thực hiện điều này tự động khi ghi vào một đường ống, vì vậy nó không thực sự cần thiết)
  • 2>/dev/nullchuyển hướng stderr để nếu có 0 tệp nhật ký, bỏ qua thông báo lỗi. (Lưu ý rằng shopt -s nullglobsẽ gây ra lsviệc liệt kê toàn bộ thư mục làm việc thay thế.)
  • wc -ltiêu thụ danh sách thư mục khi nó được tạo, do đó, đầu ra lskhông bao giờ có trong bộ nhớ tại bất kỳ thời điểm nào.
  • --Tên tệp được phân tách khỏi lệnh bằng cách sử dụng --để không được hiểu là đối số ls(trong trường hợp log*bị xóa)

Shell sẽ mở rộng log*ra danh sách đầy đủ các tệp, có thể làm cạn kiệt bộ nhớ nếu có nhiều tệp, do đó, chạy nó qua grep sẽ tốt hơn:

ls -Uba1 | grep ^log | wc -l

Cái cuối cùng này xử lý các thư mục cực lớn của các tệp mà không sử dụng nhiều bộ nhớ (mặc dù nó sử dụng một lớp con). Điều -dnày không còn cần thiết nữa, vì nó chỉ liệt kê nội dung của thư mục hiện tại.


48

Đối với một tìm kiếm đệ quy:

find . -type f -name '*.log' -printf x | wc -c

wc -csẽ đếm số lượng ký tự trong đầu ra của find, trong khi -printf xyêu findcầu in một ký tự xcho mỗi kết quả.

Đối với một tìm kiếm không đệ quy, hãy làm điều này:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c

6
Ngay cả khi bạn không có tệp có khoảng trắng, một số người dùng khác trong tập lệnh của bạn có thể gặp phải tệp có tên độc hại, khiến tập lệnh bị lỗi. Ngoài ra, những người khác gặp phải điều này trên StackOverflow có thể có các tệp có dòng mới và cần biết những cạm bẫy.
mogsie

FYI nếu bạn đơn giản bỏ đi -name '*.log'thì nó sẽ đếm tất cả các tệp, đó là những gì tôi cần cho trường hợp sử dụng của mình. Ngoài ra cờ -maxdepth cực kỳ hữu ích, cảm ơn!
starmandeluxe

2
Điều này vẫn tạo ra kết quả không chính xác nếu có tên tệp có dòng mới trong đó. Cách giải quyết dễ dàng với find; chỉ cần in một cái gì đó khác với tên tập tin nguyên văn.
tripleee

8

Câu trả lời được chấp nhận cho câu hỏi này là sai, nhưng tôi có đại diện thấp nên không thể thêm nhận xét cho câu hỏi đó.

Câu trả lời đúng cho câu hỏi này được đưa ra bởi Mat:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

Vấn đề với câu trả lời được chấp nhận là wc -l đếm số lượng ký tự dòng mới và đếm chúng ngay cả khi chúng in ra thiết bị đầu cuối là '?' trong đầu ra của 'ls -l'. Điều này có nghĩa là câu trả lời được chấp nhận FAILS khi tên tệp chứa ký tự dòng mới. Tôi đã thử lệnh được đề xuất:

ls -l log* | wc -l

và nó báo cáo sai giá trị là 2 ngay cả khi chỉ có 1 tệp khớp với mẫu có tên xảy ra để chứa ký tự dòng mới. Ví dụ:

touch log$'\n'def
ls log* -l | wc -l

6

Nếu bạn có nhiều tệp và bạn không muốn sử dụng shopt -s nullglobgiải pháp mảng thanh lịch và bash, bạn có thể sử dụng find và miễn là bạn không in tên tệp (có thể chứa dòng mới).

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Điều này sẽ tìm thấy tất cả các tệp khớp với nhật ký * và không bắt đầu bằng .* - "không phải tên. *" Là phần mềm, nhưng điều quan trọng cần lưu ý là mặc định cho "ls" là không hiển thị tệp dấu chấm, nhưng mặc định để tìm là bao gồm chúng.

Đây là một câu trả lời đúng và xử lý bất kỳ loại tên tệp nào bạn có thể ném vào nó, bởi vì tên tệp không bao giờ được chuyển xung quanh giữa các lệnh.

Nhưng, shopt nullglobcâu trả lời là câu trả lời tốt nhất!


Bạn có lẽ nên cập nhật câu trả lời ban đầu của bạn thay vì trả lời lại.
qodeninja

Tôi nghĩ rằng sử dụng findvs sử dụng lslà hai cách khác nhau để giải quyết vấn đề. findkhông phải lúc nào cũng có mặt trên máy, nhưng lsthông thường là,
mogsie

2
Nhưng sau đó, một hộp mỡ lợn có findlẽ không có tất cả các tùy chọn ưa thích cho lscả hai.
tripleee

1
Cũng lưu ý cách nó mở rộng ra toàn bộ cây thư mục nếu bạn lấy ra-maxdepth 1
tripleee

1
Lưu ý giải pháp này sẽ đếm các tập tin bên trong các thư mục ẩn trong số của nó. findlàm điều này theo mặc định. Điều này có thể tạo ra sự nhầm lẫn nếu người ta không nhận ra có một thư mục con bị ẩn và có thể giúp sử dụng nó lstrong một số trường hợp không được báo cáo theo mặc định.
MrPotatoHead 12/2/19

6

Đây là một lót của tôi cho việc này.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

Nó đã cho tôi một số googling để hiểu, nhưng điều này là tốt đẹp! Vì vậy, set -- không làm gì cả ngoại trừ việc chúng tôi sẵn sàng $#, lưu trữ số lượng đối số dòng lệnh được truyền cho chương trình shell
xverges

@xverges Có, "shopt -s nullglob" không tính các tệp ẩn (.files). set - là để lưu trữ / cài đặt số lượng tham số vị trí (num tệp, trong trường hợp này). và # $ để hiển thị số lượng tham số vị trí (số tập tin).
zee

3

Bạn có thể sử dụng tùy chọn -R để tìm các tệp cùng với các tệp bên trong các thư mục đệ quy

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

bạn có thể sử dụng các mẫu trên grep


3

Một bình luận quan trọng

(không đủ danh tiếng để bình luận)

Đây là BUGGY :

ls -1q some_pattern | wc -l

Nếu shopt -s nullglobtình cờ được đặt, nó sẽ in số lượng TẤT CẢ các tệp thông thường, không chỉ các tệp có mẫu (được thử nghiệm trên CentOS-8 và Cygwin). Ai biết những con bọ vô nghĩa khác lscó gì?

Đây là ĐÚNG và nhanh hơn nhiều:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Nó làm công việc mong đợi.


Và thời gian chạy khác nhau.
Thứ nhất: 0.006trên CentOS và 0.083trên Cygwin (trong trường hợp nó được sử dụng cẩn thận).
Thứ 2: 0.000trên CentOS và 0.003trên Cygwin.


2

Bạn có thể định nghĩa một lệnh như vậy một cách dễ dàng, bằng cách sử dụng hàm shell. Phương pháp này không yêu cầu bất kỳ chương trình bên ngoài nào và không sinh ra bất kỳ quy trình con nào. Nó không thử lsphân tích cú pháp nguy hiểm và xử lý các ký tự đặc biệt của người dùng (các khoảng trắng, dòng mới, dấu gạch chéo, v.v.) tốt. Nó chỉ dựa vào cơ chế mở rộng tên tệp được cung cấp bởi shell. Nó tương thích với ít nhất sh, bash và zsh.

Dòng bên dưới định nghĩa một hàm được gọi là countin số lượng đối số mà nó được gọi.

count() { echo $#; }

Đơn giản chỉ cần gọi nó với mẫu mong muốn:

count log*

Để kết quả chính xác khi mẫu hình cầu không khớp, tùy chọn shell nullglob(hoặc failglob- đó là hành vi mặc định trên zsh) phải được đặt khi mở rộng thời gian xảy ra. Nó có thể được đặt như thế này:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

Tùy thuộc vào những gì bạn muốn đếm, bạn cũng có thể quan tâm đến tùy chọn shell dotglob.

Thật không may, với bash ít nhất, không dễ để đặt các tùy chọn này cục bộ. Nếu bạn không muốn đặt chúng trên toàn cầu, giải pháp đơn giản nhất là sử dụng chức năng theo cách phức tạp hơn này:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Nếu bạn muốn khôi phục cú pháp nhẹ count log*hoặc nếu bạn thực sự muốn tránh sinh ra một nhánh con, bạn có thể hack một cái gì đó dọc theo dòng:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

Là một phần thưởng, chức năng này được sử dụng phổ biến hơn. Ví dụ:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

Bằng cách biến chức năng thành tệp tập lệnh (hoặc chương trình C tương đương), có thể gọi được từ PATH, nó cũng có thể được tạo bằng các chương trình như findxargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search

2

Tôi đã đưa ra câu trả lời này rất nhiều suy nghĩ, đặc biệt là đưa ra những thứ không phân tích . Lúc đầu, tôi đã thử

<CẢNH BÁO! KHÔNG LÀM VIỆC>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ CẢNH BÁO! KHÔNG LÀM VIỆC>

mà làm việc nếu chỉ có một tên tệp như

touch $'w\nlf.aa'

nhưng thất bại nếu tôi tạo một tên tệp như thế này

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

Cuối cùng tôi đã nghĩ ra những gì tôi đặt bên dưới. Lưu ý Tôi đã cố gắng để có được một số lượng tất cả các tệp trong thư mục (không bao gồm bất kỳ thư mục con nào). Tôi nghĩ rằng, cùng với câu trả lời của @Mat và @Dan_Yard, cũng như có ít nhất hầu hết các yêu cầu được đặt ra bởi @mogsie (Tôi không chắc về bộ nhớ.) Tôi nghĩ rằng câu trả lời của @mogsie là chính xác, nhưng tôi luôn cố gắng tránh xa việc phân tích cú pháp lstrừ khi đó là một tình huống cực kỳ cụ thể.

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Dễ đọc hơn:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

Điều này đang thực hiện tìm kiếm cụ thể cho các tệp, phân định đầu ra bằng ký tự null (để tránh các vấn đề với khoảng trắng và nguồn cấp dữ liệu), sau đó đếm số lượng ký tự null. Số lượng tệp sẽ ít hơn một ký tự null, vì cuối cùng sẽ có một ký tự null.

Để trả lời câu hỏi của OP, có hai trường hợp cần xem xét

1) Tìm kiếm không đệ quy:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Tìm kiếm đệ quy. Lưu ý rằng những gì bên trong -nametham số có thể cần phải được thay đổi cho hành vi hơi khác nhau (các tệp ẩn, v.v.).

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Nếu bất cứ ai muốn bình luận về cách những câu trả lời này so với những câu tôi đã đề cập trong câu trả lời này, xin vui lòng làm.


Lưu ý, tôi đã nhận được quá trình suy nghĩ này trong khi nhận được câu trả lời này .


1

Đây là những gì tôi luôn luôn làm:

đăng nhập * | awk 'HẾT {in NR}'


awk 'END{print NR}'nên tương đương với wc -l.
musiphil

0
ls -1 log* | wc -l

Có nghĩa là liệt kê một tệp trên mỗi dòng và sau đó chuyển nó thành lệnh đếm từ với chuyển tham số sang đếm dòng.


Tùy chọn "-1" là không cần thiết khi đường ống đầu ra ls. Nhưng bạn có thể muốn ẩn thông báo lỗi ls nếu không có tệp nào khớp với mẫu. Tôi đề nghị "ls log * 2> / dev / null | wc -l".
JohnMudd

Các cuộc thảo luận dưới câu trả lời của Daniel cũng có liên quan ở đây. Điều này hoạt động tốt khi bạn không có thư mục phù hợp hoặc tên tệp với dòng mới, nhưng ít nhất một câu trả lời tốt nên chỉ ra các điều kiện biên này và một câu trả lời tuyệt vời không nên có chúng. Nhiều lỗi là do ai đó sao chép / dán mã mà họ không hiểu; Vì vậy, chỉ ra những sai sót ít nhất giúp họ hiểu những gì cần chú ý. (Được cho phép, nhiều lỗi khác xảy ra vì họ bỏ qua các cảnh báo và sau đó mọi thứ đã thay đổi sau khi họ nghĩ rằng mã có thể đủ tốt cho mục đích của họ.)
tripleee

-1

Để đếm tất cả mọi thứ, chỉ cần ống ls đến dòng đếm từ:

ls | wc -l

Để đếm với mẫu, đường ống đến grep trước:

ls | grep log | wc -l
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.