Bagaimana cara menghitung jumlah karakter tertentu di setiap baris?


87

Saya bertanya-tanya bagaimana cara menghitung jumlah karakter tertentu di setiap baris dengan beberapa utilitas pemrosesan teks?

Misalnya, untuk menghitung "di setiap baris teks berikut

"hello!" 
Thank you!

Baris pertama memiliki dua, dan baris kedua memiliki 0.

Contoh lain adalah menghitung (di setiap baris.


1
Hanya akan menambahkan bahwa Anda menerima banyak peningkatan kinerja dengan menulis program 10 baris C Anda sendiri untuk ini daripada menggunakan ekspresi reguler dengan sed. Anda harus mempertimbangkan untuk melakukan tergantung pada ukuran file input Anda.
user606723

Jawaban:


104

Anda dapat melakukannya dengan seddan awk:

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

Di mana datteks contoh Anda, sed menghapus (untuk setiap baris) semua non- "karakter dan awkmencetak untuk setiap baris ukurannya (yaitu lengthsetara dengan length($0), di mana $0menunjukkan baris saat ini).

Untuk karakter lain, Anda hanya perlu mengubah ekspresi sed. Misalnya untuk (ke:

's/[^(]//g'

Pembaruan: sed agak berlebihan untuk tugas - trcukup. Solusi yang setara dengan tradalah:

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

Arti yang trmenghapus semua karakter yang bukan ( -cberarti pelengkap) dalam set karakter "\n.


3
+1 harus lebih efisien daripada versi tr& wc.
Stéphane Gimenez

1
Ya, tetapi bisakah itu menangani Unicode?
amphetamachine

@amphetamachine, ya - setidaknya tes cepat dengan ß(utf hex: c3 9f) (bukan ") berfungsi seperti yang diharapkan, yaitu tr, seddan awklakukan pelengkap / penggantian / penghitungan tanpa masalah - pada sistem Ubuntu 10,04.
maxschlepzig

1
Sebagian besar versi tr, termasuk GNU tr dan Unix tr klasik, beroperasi pada karakter byte tunggal dan tidak sesuai dengan Unicode .. Dikutip dari Wikipedia tr (Unix) .. Coba cuplikan ini: echo "aā⧾c" | tr "ā⧾" b... di Ubuntu 10.04 ... ßadalah satu byte Diperpanjang char Latin dan ditangani oleh tr... Masalah sebenarnya di sini bukan yang trtidak menangani Unicode (karena SEMUA karakter adalah Unicode), itu benar-benar trhanya menangani satu-byte pada suatu waktu ..
Peter.O

@ fred, tidak, ß bukan karakter byte tunggal - posisi Unicode-nya adalah U + 00DF, yang dikodekan sebagai 'c3 9f' di UTF-8, yaitu dua byte.
maxschlepzig

49

Saya hanya akan menggunakan awk

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

Di sini kita mengatur pemisah bidang (dengan bendera -F) menjadi karakter, "maka yang kita lakukan hanyalah mencetak jumlah bidang NF- 1. Jumlah kemunculan karakter target akan lebih sedikit daripada jumlah bidang yang dipisahkan.

Untuk karakter lucu yang ditafsirkan oleh shell, Anda hanya perlu memastikan bahwa Anda dapat menghindarinya jika tidak, baris perintah akan mencoba dan mengartikannya. Jadi untuk keduanya "dan )Anda harus lolos dari pemisah bidang (dengan \).


1
Mungkin edit jawaban Anda untuk menggunakan tanda kutip tunggal sebagai gantinya untuk melarikan diri. Ini akan bekerja dengan karakter apa pun (kecuali '). Juga, ia memiliki perilaku aneh dengan garis kosong.
Stéphane Gimenez

Pertanyaan khusus digunakan "jadi saya merasa berkewajiban untuk membuat kode bekerja dengannya. Itu tergantung pada cangkang apa yang Anda gunakan saat karakter harus diloloskan tetapi bash / tcsh harus melarikan diri "
Martin York

Tentu saja, tetapi tidak ada masalah dengan itu -F'"'.
Stéphane Gimenez

+1 Gagasan yang bagus untuk menggunakan FS .... Ini akan menyelesaikan baris-kosong yang menunjukkan -1, dan, misalnya, "$ 1" dari bash commandline. ...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Peter.O

Juga bekerja dengan banyak karakter sebagai pemisah ... berguna!
COil

14

Menggunakan trard wc:

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

Pemakaian:

$ 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
Catatan. trtidak menangani karakter yang menggunakan lebih dari satu byte .. lihat Wikipedia tr (Unix) .. yaitu. trtidak sesuai dengan Unicode.
Peter.O


Anda perlu menghapus karakter spasi $IFS, jika tidak readakan memangkasnya dari awal dan akhir.
Stéphane Chazelas


@ Peter.O, beberapa trimplementasi mendukung karakter multibyte, tetapi wc -cmenghitung byte, bukan karakter (memerlukan wc -mkarakter).
Stéphane Chazelas

11

Namun implementasi lain yang tidak bergantung pada program eksternal, di bash, zsh, yashdan beberapa implementasi / versi ksh:

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

Gunakan line="${line//[!(]}"untuk menghitung (.


Ketika baris terakhir tidak memiliki trailing \ n, loop sementara keluar, karena meskipun ia membaca baris terakhir, ia juga mengembalikan kode keluar non-nol untuk menunjukkan EOF ... untuk menyiasatinya, potongan berikut berfungsi (..Itu telah mengganggu saya untuk sementara waktu, dan saya baru saja menemukan pekerjaan ini eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
sejak muda

@Gilles: Anda menambahkan trailing /yang tidak diperlukan di bash. Apakah ini persyaratan ksh?
enzotib

1
Trailing /dibutuhkan di versi ksh yang lebih lama, dan IIRC di versi bash yang lebih lama juga.
Gilles

10

Jawaban menggunakan awkgagal jika jumlah kecocokan terlalu besar (yang terjadi pada situasi saya). Untuk jawaban dari loki-astari , kesalahan berikut dilaporkan:

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

Untuk jawaban dari enzotib (dan setara dengan manatwork ), kesalahan segmentasi terjadi:

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

The sedsolusi dengan maxschlepzig bekerja dengan benar, tetapi lambat (timing bawah).

Beberapa solusi belum disarankan di sini. Pertama, menggunakan grep:

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

Dan menggunakan perl:

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

Berikut adalah beberapa pengaturan waktu untuk beberapa solusi (dipesan paling lambat hingga tercepat); Saya membatasi hal-hal menjadi satu-baris di sini. 'foo.txt' adalah file dengan satu baris dan satu string panjang yang berisi 84922 kecocokan.

## 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

+ ide bagus! Saya memperluas meja Anda, dalam jawaban baru, jangan ragu untuk mengedit (gambar terakhir tidak begitu jelas, tapi saya percaya @maxschlepzig adalah baja solusi yang lebih cepat)
JJoao

solusi maxschlepzig sangat cepat!
okwap


8

Kemungkinan implementasi lain dengan awk dan gsub:

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

Fungsinya gsubsetara dengan sed 's///g'.

Gunakan gsub("[^(]", "")untuk menghitung (.


Anda dapat menyimpan satu karakter, yaitu saat menghapus pengalihan stdin ...;)
maxschlepzig

@maxschlepzig: yeah, tentu saja;)
enzotib

1
awk '{print gsub(/"/,"")}' input-fileakan cukup, karena "Untuk setiap substring yang cocok dengan ekspresi reguler r dalam string t, gantikan string s, dan kembalikan jumlah penggantian." (man awk)
manatwork

6

Saya memutuskan untuk menulis program C karena saya bosan.

Anda mungkin harus menambahkan validasi input, tetapi selain itu sudah diatur.

#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);
}

Terima kasih! Terima kasih sudah bosan sehingga saya bisa belajar sesuatu. Oh, tunggu, apakah Anda perlu kembali?
Tim

* mengangkat bahu * , jika Anda ingin sepenuhnya benar, Anda juga perlu menambahkan beberapa lagi #include, tetapi peringatan default pada kompiler saya tampaknya tidak peduli.
user606723

Anda dapat free(line)mengabaikannya karena keluar dari program secara implisit membebaskan semua memori yang dialokasikan - maka ada tempat untuk return 0;...;). Bahkan dalam contoh itu bukan gaya yang baik untuk membiarkan kode kembali tidak terdefinisi. Btw, getlineadalah ekstensi GNU - kalau-kalau ada yang bertanya-tanya.
maxschlepzig

@maxschlepzig: Apakah memori ditunjuk oleh baris yang dialokasikan oleh getline ()? Apakah dialokasikan secara dinamis pada heap oleh malloc atau secara statis pada stack? Anda bilang membebaskan itu tidak perlu, jadi tidak dialokasikan secara dinamis?
Tim

1
@Tim, ya, misalnya jika Anda membuat ulang kode sedemikian rupa sehingga merupakan fungsi mandiri - katakan - f, yang dipanggil beberapa kali dari kode lain, maka Anda harus memanggil freesetelah panggilan terakhir getlinepada akhir fungsi ini f.
maxschlepzig

6

Untuk string, yang paling sederhana adalah dengan trdan wc(tidak perlu berlebihan dengan awkatau sed) - tetapi perhatikan komentar di atas tentang tr, menghitung byte, bukan karakter -

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

di mana $xvariabel yang berisi string (bukan file) untuk dievaluasi.


4

Berikut ini adalah solusi C lain yang hanya membutuhkan STD C dan lebih sedikit memori:

#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;
}

Ini tidak akan melaporkan pada baris terakhir jika tidak ada trailing '\ n'
Peter.O

1
@Fred, ya, yang sengaja, karena garis tanpa trailing \nbukanlah garis nyata. Ini adalah perilaku yang sama dengan jawaban sed / awk (tr / awk) saya yang lain.
maxschlepzig

3

Kita dapat menggunakannya grepdengan regexmembuatnya lebih sederhana dan kuat.

Untuk menghitung karakter tertentu.

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

Untuk menghitung karakter khusus termasuk karakter spasi.

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

Di sini kita memilih karakter apa saja dengan [\S\s]dan dengan -oopsi yang kita buat grepuntuk mencetak setiap kecocokan (yaitu, masing-masing karakter) dalam baris terpisah. Dan kemudian gunakan wc -luntuk menghitung setiap baris.


OP tidak ingin mencetak jumlah semua karakter dalam file! Dia ingin menghitung / mencetak nomor karakter tertentu. misalnya berapa banyak "di setiap baris; dan untuk karakter lainnya. lihat pertanyaannya dan juga jawaban yang diterima.
αғsнιη

3

Mungkin jawaban yang lebih jujur, murni awk adalah menggunakan split. Split mengambil string dan mengubahnya menjadi array, nilai kembalinya adalah jumlah item array yang dihasilkan +1.

Kode berikut akan mencetak berapa kali "muncul di setiap baris.

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

info lebih lanjut tentang split http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html


2

Berikut ini adalah skrip Python sederhana untuk menemukan hitungan "di setiap baris file:

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

Di sini kita telah menggunakan countmetode strtipe bawaan.


2

Untuk solusi bash murni (khusus untuk bash): If $xadalah variabel yang berisi string Anda:

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

Benda ${x//menghapus semua karakter kecuali ", ${#x2}menghitung panjang istirahat ini.

(Saran asli menggunakan expryang memiliki masalah, lihat komentar:)

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

Perhatikan bahwa ini khusus untuk GNU exprdan menghitung byte, bukan karakter. Dengan yang lain expr:expr "x${x...}" : "x.*" - 1
Stéphane Chazelas

Oh benar terima kasih! Saya telah memodifikasinya menggunakan ide lain yang baru saja saya miliki, yang memiliki keuntungan tidak menggunakan program eksternal sama sekali.
Marian

2

Ganti adengan char yang akan dihitung. Output adalah penghitung untuk setiap baris.

perl -nE 'say y!a!!'

2

Perbandingan waktu dari solusi yang disajikan (bukan jawaban)

Efisiensi jawaban tidak penting. Namun demikian, mengikuti pendekatan @josephwb, saya mencoba mengatur waktu semua jawaban yang disajikan.

Saya menggunakan sebagai input terjemahan bahasa Portugis dari Victor Hugo "Les Miserables" (buku hebat!) Dan menghitung kemunculan "a". Edisi saya memiliki 5 volume, banyak halaman ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

Jawaban C dikompilasi dengan gcc, (tanpa optimisasi).

Setiap jawaban dijalankan 3 kali dan pilih yang terbaik.

Jangan terlalu mempercayai angka-angka ini (mesin saya melakukan tugas-tugas lain, dll, dll.). Saya berbagi waktu ini dengan Anda, karena saya mendapat beberapa hasil yang tidak terduga dan saya yakin Anda akan menemukan lagi ...

  • 14 dari 16 solusi waktunya kurang dari 1s; 9 kurang dari 0,1, banyak dari mereka menggunakan pipa
  • 2 solusi, menggunakan bash baris demi baris, memproses baris 30k dengan menciptakan proses baru, menghitung solusi yang tepat dalam 10 detik / 20 detik.
  • grep -oP aadalah waktu pohon lebih cepat grep -o a (10; 11 vs 12)
  • Perbedaan antara C dan yang lainnya tidak begitu besar seperti yang saya harapkan. (7; 8 vs 2; 3)
  • (kesimpulan diterima)

(hasil dalam urutan acak)

=========================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

di mana grep melakukan semua tugas berat: melaporkan setiap karakter yang ditemukan di setiap nomor baris. Sisanya hanya untuk menjumlahkan jumlah per baris, dan memformat output.

Hapus -n dan hitung untuk seluruh file.

Menghitung file teks 1,5Meg di bawah 0,015 detik tampaknya cepat.
Dan berfungsi dengan karakter (bukan byte).


1

Solusi untuk bash. Tidak ada program eksternal yang disebut (lebih cepat untuk string pendek).

Jika nilainya dalam variabel:

$ a='"Hello!"'

Ini akan mencetak berapa banyak di "dalamnya:

$ b="${a//[^\"]}"; echo "${#b}"
2
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.