Làm thế nào để đếm số lượng của một nhân vật cụ thể trong mỗi dòng?


87

Tôi đã tự hỏi làm thế nào để đếm số lượng một ký tự cụ thể trong mỗi dòng bằng một số tiện ích xử lý văn bản?

Ví dụ: để đếm "trong từng dòng của văn bản sau

"hello!" 
Thank you!

Dòng đầu tiên có hai và dòng thứ hai có 0.

Một ví dụ khác là tính (trong mỗi dòng.


1
Chỉ cần thêm rằng bạn đã nhận được hiệu suất tăng nhiều bằng cách viết chương trình 10 dòng C của riêng bạn cho việc này thay vì sử dụng các biểu thức thông thường với sed. Bạn nên xem xét thực hiện tùy thuộc vào kích thước của các tập tin đầu vào của bạn.
user606723

Câu trả lời:


104

Bạn có thể làm điều đó với sedawk:

$ sed 's/[^"]//g' dat | awk '{ print length }'
2
0

Trường hợp datvăn bản ví dụ của bạn là gì, sed xóa (cho mỗi dòng) tất cả các "ký tự và awkbản in cho mỗi dòng có kích thước của nó (nghĩa lengthlà tương đương với length($0), trong đó $0biểu thị dòng hiện tại).

Đối với một nhân vật khác, bạn chỉ cần thay đổi biểu thức sed. Ví dụ cho (:

's/[^(]//g'

Cập nhật: sed là loại quá mức cho nhiệm vụ - trlà đủ. Một giải pháp tương đương với tr:

$ tr -d -c '"\n' < dat | awk '{ print length; }'

Có nghĩa là trxóa tất cả các ký tự không ( -ccó nghĩa là bổ sung) trong bộ ký tự "\n.


3
+1 nên hiệu quả hơn phiên bản tr& wc.
Stéphane Gimenez

1
Có, nhưng nó có thể xử lý Unicode không?
amphetamachine

@amphetamachine, có - ít nhất là một thử nghiệm nhanh với ß(utf hex: c3 9f) (thay vì ") hoạt động như mong đợi, nghĩa là tr, sedawkthực hiện bổ sung / thay thế / đếm mà không gặp sự cố - trên hệ thống Ubuntu 10.04.
maxschlepzig

1
Hầu hết các phiên bản tr, bao gồm GNU tr và Unix tr cổ điển, hoạt động trên các ký tự byte đơn và không tuân thủ Unicode .. Được trích dẫn từ Wikipedia tr (Unix) .. Hãy thử đoạn trích này: echo "aā⧾c" | tr "ā⧾" b... trên Ubuntu 10.04 ... ßlà một byte đơn Char Latin mở rộng và được xử lý bởi tr... Vấn đề thực sự ở đây không phải là trkhông xử lý Unicode (vì TẤT CẢ các ký tự là Unicode), thực sự trchỉ xử lý một byte mỗi lần ..
Peter.O

@fred, không, ß không phải là một ký tự byte đơn - vị trí Unicode của nó là U + 00DF, được mã hóa là 'c3 9f' trong UTF-8, tức là hai byte.
maxschlepzig

49

Tôi sẽ chỉ sử dụng awk

awk -F\" '{print NF-1}' <fileName>

Ở đây chúng ta đặt dấu tách trường (với cờ -F) là ký tự "thì tất cả những gì chúng ta làm là in số trường NF- 1. Số lần xuất hiện của ký tự đích sẽ nhỏ hơn một số trường được tách.

Đối với các ký tự ngộ nghĩnh được giải thích bởi shell, bạn chỉ cần đảm bảo rằng bạn thoát chúng nếu không dòng lệnh sẽ thử và giải thích chúng. Vì vậy, cho cả hai ")bạn cần phải thoát khỏi dấu phân cách trường (với \).


1
Có thể chỉnh sửa câu trả lời của bạn để sử dụng dấu ngoặc đơn thay vì thoát. Nó sẽ làm việc với bất kỳ nhân vật (ngoại trừ '). Ngoài ra, nó có một hành vi kỳ lạ với các dòng trống.
Stéphane Gimenez

Câu hỏi đặc biệt sử dụng "vì vậy tôi cảm thấy bắt buộc phải làm cho mã hoạt động với nó. Nó phụ thuộc vào lớp vỏ bạn đang sử dụng thời tiết mà nhân vật cần phải thoát nhưng bash / tcsh cả hai sẽ cần phải thoát "
Martin York

Tất nhiên, nhưng không có vấn đề với -F'"'.
Stéphane Gimenez

+1 Thật là một ý tưởng hay khi sử dụng FS .... Điều này sẽ giải quyết dòng trống hiển thị -1 và, ví dụ: "$ 1" từ dòng lệnh bash. ...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Peter.O

Cũng làm việc với nhiều ký tự như dấu phân cách ... hữu ích!
COil

14

Sử dụng trard wc:

function countchar()
{
    while IFS= read -r i; do printf "%s" "$i" | tr -dc "$1" | wc -m; done
}

Sử dụng:

$ countchar '"' <file.txt  #returns one count per line of file.txt
1
3
0

$ countchar ')'           #will count parenthesis from stdin
$ countchar '0123456789'  #will count numbers from stdin

3
Ghi chú. trkhông xử lý các ký tự sử dụng nhiều hơn một byte .. xem Wikipedia tr (Unix) .. tức là. trkhông tuân thủ Unicode.
Peter.O


bạn cần xóa các ký tự khoảng trắng khỏi $IFS, nếu không readsẽ cắt chúng từ đầu và cuối.
Stéphane Chazelas


@ Peter.O, một số trtriển khai hỗ trợ các ký tự đa nhân, nhưng wc -cđếm byte, không phải ký tự nào (cần wc -mký tự).
Stéphane Chazelas

11

Tuy nhiên, một thực hiện mà không dựa vào các chương trình bên ngoài, trong bash, zsh, yashvà một số hiện thực / phiên bản của ksh:

while IFS= read -r line; do 
  line="${line//[!\"]/}"
  echo "${#line}"
done <input-file

Sử dụng line="${line//[!(]}"để đếm (.


Khi dòng cuối cùng không có dấu \ n, vòng lặp while sẽ thoát, bởi vì mặc dù nó đọc dòng cuối cùng, nó cũng trả về mã thoát không bằng 0 để chỉ ra EOF ... để đi xung quanh nó, đoạn mã sau hoạt động (.. Nó đã làm phiền tôi một lúc và tôi mới phát hiện ra công việc này) ... eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
Peter.O

@Gilles: bạn đã thêm một dấu vết /không cần thiết trong bash. Đó là một yêu cầu ksh?
enzotib

1
Việc theo dõi /là cần thiết trong các phiên bản cũ của ksh và IIRC trong các phiên bản cũ hơn của bash.
Gilles

10

Các câu trả lời sử dụng awkkhông thành công nếu số lượng trận đấu quá lớn (đó là tình huống của tôi). Đối với câu trả lời từ loki-astari , lỗi sau đây được báo cáo:

awk -F" '{print NF-1}' foo.txt 
awk: program limit exceeded: maximum number of fields size=32767
    FILENAME="foo.txt" FNR=1 NR=1

Đối với câu trả lời từ enzotib (và tương đương từ manatwork ), một lỗi phân đoạn xảy ra:

awk '{ gsub("[^\"]", ""); print length }' foo.txt
Segmentation fault

Các sedgiải pháp của maxschlepzig hoạt động chính xác, nhưng là chậm (timings dưới đây).

Một số giải pháp chưa được đề xuất ở đây. Đầu tiên, sử dụng grep:

grep -o \" foo.txt | wc -w

Và sử dụng perl:

perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt

Dưới đây là một số thời gian cho một vài giải pháp (được đặt hàng chậm nhất đến nhanh nhất); Tôi giới hạn mọi thứ cho một lớp lót ở đây. 'foo.txt' là một tệp có một dòng và một chuỗi dài chứa 84922 kết quả khớp.

## sed solution by [maxschlepzig]
$ time sed 's/[^"]//g' foo.txt | awk '{ print length }'
84922
real    0m1.207s
user    0m1.192s
sys     0m0.008s

## using grep
$ time grep -o \" foo.txt | wc -w
84922
real    0m0.109s
user    0m0.100s
sys     0m0.012s

## using perl
$ time perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt
84922
real    0m0.034s
user    0m0.028s
sys     0m0.004s

## the winner: updated tr solution by [maxschlepzig]
$ time tr -d -c '\"\n' < foo.txt |  awk '{ print length }'
84922
real    0m0.016s
user    0m0.012s
sys     0m0.004s

+ ý kiến ​​hay! Tôi đã mở rộng bảng của bạn, trong một câu trả lời mới, thoải mái chỉnh sửa (hình ảnh cuối cùng không rõ ràng lắm, nhưng tôi tin rằng @maxschlepzig là giải pháp nhanh hơn)
JJoao

giải pháp của maxschlepzig là siêu nhanh!
okwap


8

Một cách thực hiện khác có thể với awk và gsub:

awk '{ gsub("[^\"]", ""); print length }' input-file

Các chức năng gsubtương đương với sed 's///g'.

Sử dụng gsub("[^(]", "")để đếm (.


Bạn có thể lưu một ký tự, tức là khi xóa chuyển hướng stdin ...;)
maxschlepzig

@maxschlepzig: vâng, tất nhiên rồi;)
enzotib

1
awk '{print gsub(/"/,"")}' input-filesẽ là đủ, vì "Đối với mỗi chuỗi con khớp với biểu thức chính quy r trong chuỗi t, thay thế chuỗi s và trả về số lượng thay thế." (man awk)
manatwork

6

Tôi quyết định viết một chương trình C vì tôi thấy chán.

Có lẽ bạn nên thêm xác thực đầu vào, nhưng khác với tất cả đã được đặt.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
        char c = argv[1][0];
        char * line = NULL;
        size_t len = 0;
        while (getline(&line, &len, stdin) != -1)
        {
                int count = 0;
                char * s = line;
                while (*s) if(*s++ == c) count++;
                printf("%d\n",count);
        }
        if(line) free(line);
}

Cảm ơn! Cảm ơn vì đã chán để tôi có thể học được điều gì đó. Oh chờ đã, bạn có cần trở lại?
Tim

* nhún vai * , nếu bạn muốn hoàn toàn chính xác, bạn cũng cần thêm một vài #incol nữa, nhưng các cảnh báo mặc định trên trình biên dịch của tôi dường như không quan tâm.
dùng606723

Bạn có thể bỏ qua free(line)vì thoát khỏi chương trình hoàn toàn giải phóng tất cả bộ nhớ được phân bổ - sau đó có chỗ cho return 0;...;). Ngay cả trong các ví dụ, việc để lại mã trả lại không xác định là không tốt. Btw, getlinelà một phần mở rộng GNU - trong trường hợp ai đó đang tự hỏi.
maxschlepzig

@maxschlepzig: Bộ nhớ được chỉ bởi dòng được phân bổ bởi getline ()? Nó được phân bổ động trên heap bởi malloc hoặc tĩnh trên stack? Bạn nói giải phóng nó là không cần thiết, vì vậy nó không được phân bổ động?
Tim

1
@ Tim, vâng, ví dụ như nếu bạn cấu trúc lại các mã như vậy mà nó là một chức năng độc lập - nói - f, được gọi là nhiều lần từ mã khác, sau đó bạn phải gọi freesau khi cuộc gọi cuối cùng của getlineở phần cuối của chức năng này f.
maxschlepzig

6

Đối với một chuỗi, đơn giản nhất sẽ là với trwc(không cần phải quá mức với awkhoặc sed) - nhưng lưu ý các ý kiến ​​trên về tr, đếm byte, không phải ký tự -

echo $x | tr -d -c '"' | wc -m

trong đó $xbiến là chứa chuỗi (không phải tệp) để đánh giá.


4

Đây là một giải pháp C khác chỉ cần STD C và ít bộ nhớ hơn:

#include <stdio.h>

int main(int argc, char **argv)
{
  if (argc < 2 || !*argv[1]) {
    puts("Argument missing.");
    return 1;
  }
  char c = *argv[1], x = 0;
  size_t count = 0;
  while ((x = getc(stdin)) != EOF)
    if (x == '\n') {
      printf("%zd\n", count);
      count = 0;
    } else if (x == c)
      ++count;
  return 0;
}

Điều này sẽ không báo cáo trên dòng cuối cùng nếu nó không có dấu '\ n'
Peter.O

1
@fred, vâng, đó là mục đích, bởi vì một dòng không có dấu \nlà không có dòng thực. Đây là hành vi tương tự như với câu trả lời sed / awk (tr / awk) khác của tôi.
maxschlepzig

3

Chúng ta có thể sử dụng grepvới regexđể làm cho nó đơn giản hơn và mạnh mẽ.

Để tính nhân vật cụ thể.

$ grep -o '"' file.txt|wc -l

Để đếm các ký tự đặc biệt bao gồm các ký tự khoảng trắng.

$ grep -Po '[\W_]' file.txt|wc -l

Ở đây chúng tôi đang chọn bất kỳ ký tự nào có [\S\s]và với -otùy chọn chúng tôi thực hiện grepđể in từng trận đấu (nghĩa là, mỗi ký tự) trong một dòng riêng biệt. Và sau đó sử dụng wc -lđể đếm từng dòng.


OP không muốn in số lượng tất cả các ký tự trong một tệp! Anh ta muốn đếm / in số của một nhân vật cụ thể. ví dụ có bao nhiêu "trong mỗi dòng; và cho bất kỳ ký tự khác. xem câu hỏi của anh ấy và cũng chấp nhận câu trả lời.
αғsнιη

3

Có lẽ một câu trả lời thẳng thắn hơn, hoàn toàn awk sẽ là sử dụng chia. Split lấy một chuỗi và biến nó thành một mảng, giá trị trả về là số lượng các mục mảng được tạo + 1.

Đoạn mã sau sẽ in ra số lần "xuất hiện trên mỗi dòng.

awk ' {print (split($0,a,"\"")-1) }' file_to_parse

thêm thông tin về việc tách http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html


2

Đây là một tập lệnh Python đơn giản để tìm số đếm "trong mỗi dòng của một tệp:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        print line.count('"')

Ở đây chúng tôi đã sử dụng countphương pháp tích hợp sẵn str.


2

Đối với giải pháp bash thuần túy (tuy nhiên, đó là bash cụ thể): Nếu $xlà biến chứa chuỗi của bạn:

x2="${x//[^\"]/}"
echo ${#x2}

Điều ${x//này loại bỏ tất cả các ký tự ngoại trừ ", ${#x2}tính toán độ dài của phần còn lại này.

(Đề xuất ban đầu sử dụng exprcó vấn đề, xem bình luận :)

expr length "${x//[^\"]/}"

Lưu ý rằng nó dành riêng cho GNU exprvà đếm byte, không phải ký tự. Với người khác expr:expr "x${x...}" : "x.*" - 1
Stéphane Chazelas

Ồ đúng rồi, cảm ơn! Tôi đã sửa đổi nó bằng một ý tưởng khác mà tôi vừa có, có lợi thế là không sử dụng chương trình bên ngoài nào cả.
Mary

2

Thay thế abằng char để được tính. Đầu ra là bộ đếm cho mỗi dòng.

perl -nE 'say y!a!!'

2

So sánh thời gian của các giải pháp được trình bày (không phải là một câu trả lời)

Hiệu quả của các câu trả lời là không quan trọng. Tuy nhiên, theo cách tiếp cận @josephwb, tôi đã cố gắng tính thời gian cho tất cả các câu trả lời được trình bày.

Tôi sử dụng làm đầu vào bản dịch tiếng Bồ Đào Nha của Victor Hugo "Les Miserables" (cuốn sách tuyệt vời!) Và đếm số lần xuất hiện của "a". Phiên bản của tôi có 5 tập, nhiều trang ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

Câu trả lời C được biên dịch với gcc, (không tối ưu hóa).

Mỗi câu trả lời được chạy 3 lần và chọn câu trả lời hay nhất.

Đừng tin tưởng quá nhiều vào những con số này (máy của tôi đang thực hiện các tác vụ khác, v.v.). Tôi chia sẻ những khoảng thời gian này với bạn, vì tôi nhận được một số kết quả bất ngờ và tôi chắc chắn bạn sẽ tìm thấy thêm một số ...

  • 14 trong số 16 giải pháp tính thời gian mất ít hơn 1 giây; 9 ít hơn 0,1 giây, nhiều người trong số họ sử dụng đường ống
  • 2 giải pháp, sử dụng bash line theo dòng, xử lý các dòng 30k bằng cách tạo các quy trình mới, tính toán giải pháp chính xác trong 10 giây / 20 giây.
  • grep -oP alà lần cây nhanh hơn grep -o a (10; 11 so với 12)
  • Sự khác biệt giữa C và những người khác không quá lớn như tôi mong đợi. (7; 8 so với 2; 3)
  • (kết luận hoan nghênh)

(kết quả theo thứ tự ngẫu nhiên)

=========================1 maxschlepzig
$ time sed 's/[^a]//g' mis.txt | awk '{print length}' > a2
real    0m0.704s ; user 0m0.716s
=========================2 maxschlepzig
$ time tr -d -c 'a\n' < mis.txt | awk '{ print length; }' > a12
real    0m0.022s ; user 0m0.028s
=========================3 jjoao
$ time perl -nE 'say y!a!!' mis.txt  > a1
real    0m0.032s ; user 0m0.028s
=========================4 Stéphane Gimenez
$ function countchar(){while read -r i; do echo "$i"|tr -dc "$1"|wc -c; done }

$ time countchar "a"  < mis.txt > a3
real    0m27.990s ; user    0m3.132s
=========================5 Loki Astari
$ time awk -Fa '{print NF-1}' mis.txt > a4
real    0m0.064s ; user 0m0.060s
Error : several -1
=========================6 enzotib
$ time awk '{ gsub("[^a]", ""); print length }' mis.txt > a5
real    0m0.781s ; user 0m0.780s
=========================7 user606723
#include <stdio.h> #include <string.h> // int main(int argc, char *argv[]) ...  if(line) free(line); }

$ time a.out a < mis.txt > a6
real    0m0.024s ; user 0m0.020s
=========================8 maxschlepzig
#include <stdio.h> // int main(int argc, char **argv){if (argc < 2 || !*argv[1]) { ...  return 0; }

$ time a.out a < mis.txt > a7
real    0m0.028s ; user 0m0.024s
=========================9 Stéphane Chazelas
$ time awk '{print gsub(/a/, "")}'< mis.txt > a8
real    0m0.053s ; user 0m0.048s
=========================10 josephwb count total
$ time grep -o a < mis.txt | wc -w > a9
real    0m0.131s ; user 0m0.148s
=========================11 Kannan Mohan count total
$ time grep -o 'a' mis.txt | wc -l > a15
real    0m0.128s ; user 0m0.124s
=========================12 Kannan Mohan count total
$ time grep -oP 'a' mis.txt | wc -l > a16
real    0m0.047s ; user 0m0.044s
=========================13 josephwb Count total
$ time perl -ne '$x+=s/a//g; END {print "$x\n"}'< mis.txt > a10
real    0m0.051s ; user 0m0.048s
=========================14 heemayl
#!/usr/bin/env python2 // with open('mis.txt') as f: for line in f: print line.count('"')

$ time pyt > a11
real    0m0.052s ; user 0m0.052s
=========================15 enzotib
$ time  while IFS= read -r line; do   line="${line//[!a]/}"; echo "${#line}"; done < mis.txt  > a13
real    0m9.254s ; user 0m8.724s
=========================16 bleurp
$ time awk ' {print (split($0,a,"a")-1) }' mis.txt > a14
real    0m0.148s ; user 0m0.144s
Error several -1

1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

trong đó grep thực hiện tất cả các công việc nặng: báo cáo từng ký tự được tìm thấy ở mỗi số dòng. Phần còn lại chỉ là tổng số đếm trên mỗi dòng và định dạng đầu ra.

Xóa -nvà lấy số lượng cho toàn bộ tập tin.

Đếm tệp văn bản 1,5Meg dưới 0,015 giây có vẻ nhanh.
Và không hoạt động với các ký tự (không phải byte).


1

Một giải pháp cho bash. Không có chương trình bên ngoài được gọi (nhanh hơn cho chuỗi ngắn).

Nếu giá trị nằm trong một biến:

$ a='"Hello!"'

Điều này sẽ in bao nhiêu "nó chứa:

$ b="${a//[^\"]}"; echo "${#b}"
2
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.