Her satırdaki belirli bir karakterin sayısı nasıl sayılır?


87

Her metin satırında belirli bir karakterin sayısının bazı metin işlem araçlarından nasıl sayılacağını merak ediyordum.

Örneğin ", aşağıdaki metnin her satırında saymak için

"hello!" 
Thank you!

İlk satırda iki, ikinci satırda 0 var.

Başka bir örnek (her satırda saymaktır .


1
Sadece sed ile düzenli ifadeler kullanmak yerine, kendi 10 satırlık C programınızı yazarak çok daha fazla performans aldığınızı ekleyeceğim. Giriş dosyalarınızın boyutuna bağlı olarak yapmayı düşünmelisiniz.
user606723

Yanıtlar:


104

Bunu sedve ile yapabilirsiniz awk:

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

Nerede datsenin örneğin metin, (her hat için) sed siler tüm olmayan bir "karakter ve awkher satır boyutuna için baskılar (yani lengtheşdeğerdir length($0)nerede, $0geçerli satırı belirtir).

Başka bir karakter için, sed ifadesini değiştirmeniz yeterlidir. Örneğin (:

's/[^(]//g'

Güncelleme: sed görev için fazla abartılmış - tryeterlidir. Eşdeğer bir çözüm tr:

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

Karakter kümesinde trolmayan ( -ctamamlayıcı anlamına gelen) tüm karakterleri silen anlam "\n.


3
+1, tr& wcsürümünden daha verimli olmalıdır .
Stéphane Gimenez

1
Evet, ama Unicode ile başa çıkabilir mi?
amfetamachine

@amphetamachine, evet - en az bir hızlı testi ß(utf onaltılık: c3 9f) (yerine "yani beklendiği gibi) çalışır tr, sedve awk- bir Ubuntu 10,04 sistemde bir sorun olmadan sayma / / değiştirme tamamlayacak yoktur.
maxschlepzig

1
Çoğu sürümü trGNU tr ve klasik Unix tr dahil, tek baytlık karakterler üzerinde işlem ve uyumlu Unicode .. Alıntı sahibi olmayan Vikipedi tr (Unix) .. bu pasajı deneyin: echo "aā⧾c" | tr "ā⧾" b... Ubuntu 10.04 üzerinde ... ßTek bir bayt olduğunu Genişletilmiş Latin karakter ve ele alınır tr... Buradaki asıl mesele trUnicode'la başa çıkmadığıdır (çünkü TÜM karakterler Unicode'dur), gerçekte trbir seferde yalnızca bir bayt
işliyordur

@fred, no, ß tek baytlık bir karakter değildir - Unicode pozisyonu UTF-8'de 'c3 9f' olarak kodlanan U + 00DF'dir, yani iki bayt.
maxschlepzig

49

Sadece awk kullanırdım

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

Burada alan ayırıcısını (-F bayrağıyla) karakter olarak ayarlıyoruz, "sonra yaptığımız tek şey alan sayısını yazdırmak. NF- 1. Hedef karakterin oluşum sayısı, ayrılmış alanların sayısından daha az olacak.

Kabuk tarafından yorumlanan komik karakterler için, onlardan kaçtığınızdan emin olmanız gerekir, aksi takdirde komut satırı onları dener ve yorumlar. Hem Yani "ve )sen (ile alan ayırıcı kaçmak gerekir \).


1
Belki de cevabınızı kaçmak için tırnak işareti kullanmak için düzenleyin. Herhangi bir karakterle çalışacaktır (hariç '). Ayrıca, boş satırlarla garip bir davranış sergiliyor.
Stéphane Gimenez

Soru özellikle kullandığı "için kodun onunla çalışmasını sağlamak zorundayım. Bu, hangi havayı kullandığınıza bağlı olarak karakterin kaçması gerektiğine bağlı, ancak bash / tcsh'nin kaçması gerekecek "
Martin York

Tabii ki, ama hiçbir sorun yok -F'"'.
Stéphane Gimenez

+1 FS kullanmak için iyi bir fikir .... Bu, -1 satırındaki boş satırı ve örneğin bash komut satırından "$ 1" değerini çözecektir. ...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Peter.O

Ayırıcı olarak birden fazla karakterle de çalışın ... kullanışlı!
Bobin

14

Kullanılması trard wc:

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

Kullanımı:

$ 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
Not. trbirden fazla bayt kullanan karakterleri işlemez .. bkz. Wikipedia tr (Unix) .. ie. trUnicode uyumlu değil.
Peter.O


boşluk karakterlerini kaldırmanız gerekir $IFS, aksi takdirde readbaştan ve sondan itibaren kırpılır .
Stéphane Chazelas


@ Peter.O, bazı truygulamalar çok baytlı karakterleri destekler, ancak wc -cyine de karakterleri (baytları gerektirir) bayt sayar wc -m.
Stéphane Chazelas

11

Dış programlarda itimat içinde değil Henüz başka uygulama bash, zsh, yashve bazı uygulamaları / versiyonları ksh:

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

Saymak line="${line//[!(]}"için kullanın (.


Son satırın sonu gelmiyorsa \ n, while döngüsü çıkmaktadır, çünkü son satırı okumasına rağmen, EOF ... 'u aşmak için EOF ...' u belirtmek için sıfır olmayan bir çıkış kodu da verir. (..Bir süredir beni rahatsız ediyor ve bu geçici çözümü keşfettim) ... eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
Peter.O

@Gilles: bash'da gerekmeyen bir iz ekledin /. Bu bir ksh gereksinimidir?
enzotib

1
Sondaki /yanı Bash eski sürümlerinde ksh eski sürümlerinde ihtiyacı vardı ve IIRC edilir.
Gilles,

10

awkEşleşme sayısı çok büyükse (bu benim durumum olur) kullanan cevaplar başarısız olur. Loki-astari'nin cevabı için aşağıdaki hata bildirilmiştir:

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

Gelen yanıt enzotib (ve gelen eşdeğer manatwork ), bir segmentasyon hatası oluşur:

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

sedTarafından çözüm maxschlepzig düzgün çalışır, ancak yavaş (zamanlamaları aşağıda) 'dir.

Burada henüz önerilmeyen bazı çözümler. İlk önce, kullanarak grep:

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

Ve kullanarak perl:

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

İşte birkaç çözüm için bazı zamanlamalar: (en yavaşdan en hızlıya sipariş edilir); İşleri burada tek gömleklerle sınırlandırdım. 'foo.txt', bir satır ve 84922 eşleşme içeren uzun bir dize içeren bir dosyadır.

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

+ iyi fikir! Masanızı genişlettim, yeni bir cevapta, düzenleme yapmaktan çekinmeyin (son fotoğraf net değil, ama @maxschlepzig'in daha hızlı bir çözüm olduğu için çelik olduğuna inanıyorum)
JJoao

Maxschlepzig'in çözümü süper hızlı!
okwap


8

Awk ve gsub ile bir başka olası uygulama:

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

İşlev gsubsed'in eşdeğeridir 's///g'.

Saymak gsub("[^(]", "")için kullanın (.


Bir karakter kaydedebilirsiniz, örneğin stdin yeniden yönlendirmesini kaldırırken ...;)
maxschlepzig 14:11

@ maxschlepzig: evet, elbette;)
enzotib 14:11

1
awk '{print gsub(/"/,"")}' input-file"t dizesindeki r normal ifadesiyle eşleşen her alt dizgede, s dizesini değiştirin ve yer değiştirme sayısını döndürün." (adam awk)
eseri

6

Sıkıldığım için bir C programı yazmaya karar verdim.

Muhtemelen giriş onaylaması eklemelisiniz, ancak ayarlananların dışında.

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

Teşekkürler! Sıkıldığın için teşekkürler, böylece bir şeyler öğreneyim. Bekle bir dönüşe ihtiyacınız var mı?
Tim

* silkiyor * , eğer tamamen doğru olmak istiyorsanız, birkaç tane daha eklemek zorundasınız, ama derleyicimdeki varsayılan uyarılar umursamıyor.
user606723

Bunun dışında bırakabilirsiniz free(line)çünkü programdan çıkmak tüm ayrılan hafızayı dolaylı olarak serbest bırakır - o zaman bir return 0;...;) vardır. Örneklerde bile, dönüş kodunu tanımsız bırakmak iyi bir stil değildir. BTW, getlinebir GNU uzantısıdır - birinin merak etmesi durumunda.
maxschlepzig

@ maxschlepzig: Bellek, getline () tarafından ayrılan çizgiyle mi gösteriliyor? Dinamik olarak yığın halinde malloc ile mi yoksa statik olarak yığın üzerine mi yerleştirilmiş? Serbest bırakmanın gerekli olmadığını söylediniz, bu yüzden dinamik olarak tahsis edilmedi mi?
Tim

1
@Tim, evet, örneğin, kodu fbaşka bir koddan defalarca denilen bağımsız bir işlev - say - gibi olacak şekilde yeniden düzenlerseniz, bu işlevin sonunda freeyapılan son çağrıdan sonra çağırmanız getlinegerekir f.
maxschlepzig

6

Bir dize için, en basit ile olacağını trve wc(ile Overkill gerek awkya sed) - ama yaklaşık yukarıdaki yorumları dikkat tr, bayt karakter değil sayar -

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

Değerlendirilecek $xdizeyi (bir dosyayı değil) içeren değişken.


4

Yalnızca STD C ve daha az bellek gerektiren başka bir C çözümü:

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

Bu, '\ n' sonunda bir iz bırakmadığı takdirde son satırda rapor vermeyecektir
Peter.O

1
@ Fred, evet, bilerek, çünkü izleri olmayan bir \nçizgi gerçek bir çizgi değil. Bu, diğer sed / awk (tr / awk) cevabımdaki davranışların aynısıdır.
maxschlepzig

3

Biz kullanabilirsiniz grepile regexdaha basit ve güçlü hale getirmek için.

Belirli bir karakteri saymak için.

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

Boşluk karakterleri içeren özel karakterleri saymak için.

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

Burada, her bir eşleşmeyi (her karakter olan) ayrı bir satırda yazdırmak için yaptığımız seçenekle birlikte [\S\s]ve -oseçtiğimiz grepkarakterleri seçiyoruz. Ve sonra wc -lher satırı saymak için kullanın .


OP bir dosyadaki tüm karakterlerin sayısını yazdırmak istemiyor! Belirli bir karakterin numarasını saymak / yazdırmak istiyor. örneğin ", her satırda kaç tane olduğu; ve diğer karakterler için. sorusunu görmek ve ayrıca cevabı kabul etmek.
αғsнιη

3

Belki daha yalındır, tamamen garip bir cevap split kullanmak olacaktır. Bölünmüş bir dizge alır ve diziye dönüştürür, dönüş değeri + 1 dizge öğesi sayısıdır.

Aşağıdaki kod, her satırda kaç kez "çıktısını alacaktır.

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

split hakkında daha fazla bilgi http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html


2

"Dosyanın her satırındaki sayısını bulmak için basit bir Python betiği :

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

Burada countyerleşik strtip yöntemini kullandık .


2

Saf bir bash çözümü için (ancak bash'a özgüdür): Eğer $xdizenizi içeren değişken ise:

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

${x//Şey hariç tüm karakter kaldırır ", ${#x2}bu dinlenme süresini hesaplar.

( exprHangi sorunların kullanıldığı özgün öneri , yorumlara bakın:)

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

GNU'ya özgü olduğunu exprve karakterleri değil, baytları saydığını unutmayın. Diğer expr:expr "x${x...}" : "x.*" - 1
Stéphane Chazelas

Ah doğru, teşekkürler! Başka bir fikri kullanarak, harici bir program kullanmama avantajına sahip olanı değiştirdim.
Marian

2

aSayılacak char ile değiştirin . Çıktı, her satır için sayaçtır.

perl -nE 'say y!a!!'

2

Sunulan çözümlerin zaman karşılaştırması (cevap değil)

Cevapların etkinliği önemli değil. Yine de, josephwb yaklaşımını izleyerek sunulan tüm cevapları zamanlamaya çalıştım.

Victor Hugo "Les Miserables" (harika kitap!) 'In Portekizce tercümesini girdi olarak kullanıyorum ve "a" nın oluşumlarını sayıyorum. Sürümümde 5 cilt var, birçok sayfa ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

C cevapları gcc ile derlendi (optimizasyon yok).

Her cevap 3 kez çalıştırıldı ve en iyisini seçti.

Bu sayılara çok fazla güvenmeyin (makinem başka işler yapıyor, vb.). Bu zamanları seninle paylaşıyorum, çünkü beklenmedik sonuçlar aldım ve daha fazlasını bulacağına eminim.

  • 16 zamanlanmış çözümün 14'ü 1 saniyeden daha az sürdü; 9'u 0,1 saniyeden az, çoğu boru kullanıyor
  • Satır satır bash kullanarak 2 çözüm, yeni işlemler oluşturarak 30k satır işledi, 10s / 20s içinde doğru çözümü hesaplayın.
  • grep -oP aağaç zamanından daha hızlıdır grep -o a (10; 11 - 12)
  • C ve diğerleri arasındaki fark beklediğim kadar büyük değil. (7; 8 ve 2; 3)
  • (sonuçlar kabul edilir)

(rastgele sırayla sonuçlanır)

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

grep tüm ağır kaldırma işlemlerini yapar: her satır numarasında bulunan her karakteri rapor eder. Gerisi sadece satır başına sayımı toplamak ve çıktıyı biçimlendirmek içindir.

Kaldır -nve tüm dosyanın sayısını al.

1.5Meg metin dosyasını 0.015 sn'nin altında saymak hızlı görünüyor.
Ve karakterlerle çalışır (bayt değil).


1

Bash için bir çözüm. Harici bir program çağrılmadı (kısa dizeler için daha hızlı).

Değer bir değişkende ise:

$ a='"Hello!"'

Bu, "içerdiği kaç tane basacaktır :

$ b="${a//[^\"]}"; echo "${#b}"
2
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.