Як порахувати кількість конкретного символу в кожному рядку?


87

Мені було цікаво, як підрахувати кількість конкретного символу в кожному рядку за допомогою деяких утиліт обробки тексту?

Наприклад, порахувати "в кожному рядку наступного тексту

"hello!" 
Thank you!

Перший рядок має два, а другий - 0.

Ще один приклад - підрахунок (у кожному рядку.


1
Просто додамо, що ви отримали значно більшу продуктивність, написавши для цього свою власну програму 10 рядків C, а не використовуючи регулярні вирази з sed. Вам слід розглянути можливість виконання залежно від розміру вхідних файлів.
користувач606723

Відповіді:


104

Ви можете це зробити за допомогою sedта awk:

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

Де datтекст вашого прикладу, sed видаляє (для кожного рядка) всі не "символи та awkдрукує для кожного рядка його розмір (тобто lengthеквівалент length($0), де $0позначає поточний рядок).

Для іншого символу потрібно просто змінити вираз sed. Наприклад для (:

's/[^(]//g'

Оновлення: sed це вид надмірності для завдання - trдостатньо. Еквівалентне рішення з tr:

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

Значення, що trвидаляє всі символи, які не є ( -cозначає доповнення) в наборі символів "\n.


3
+1 має бути ефективнішим, ніж версія tr& wc.
Stéphane Gimenez

1
Так, але чи може він обробляти Unicode?
амфетамахін

@amphetamachine, так - принаймні , швидкий тест з ß(UTF HEX: c3 9F) (замість ") працює , як очікувалося, тобто tr, sedі awkзробити доповнення / заміну / підрахунку без проблем - на 10,04 системі Ubuntu.
maxschlepzig

1
Більшість версій tr, включаючи GNU tr і класичний Unix tr, працюють на однобайтових символах і не сумісні з Unicode .. Цитується з Вікіпедії tr (Unix) . Спробуйте цей фрагмент: echo "aā⧾c" | tr "ā⧾" b... на Ubuntu 10.04 ... ß- це один байт Розширена латинська картка та її обробляє tr... Справжня проблема тут не в тому, trщо не обробляється Unicode (адже ВСІ символи є Unicode), це дійсно, що trодночасно обробляє лише один байт ..
Peter.O

@fred, ні, ß не є однобайтовим символом - його позиція Unicode - U + 00DF, що в UTF-8 кодується як 'c3 9f', тобто два байти.
maxschlepzig

49

Я просто використаю awk

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

Тут ми встановлюємо символом роздільник поля (з прапорцем -F), "тоді все, що ми робимо, - це кількість друку полів NF- 1. Кількість входжень цільового символу буде на одну меншу, ніж кількість розділених полів.

Для смішних персонажів, які інтерпретуються оболонкою, вам просто потрібно переконатися, що ви уникаєте їх, інакше командний рядок спробує та інтерпретує їх. Тож для обох "і )вам потрібно уникнути роздільника поля (з \).


1
Можливо, відредагуйте свою відповідь, щоб використати цитати синглів, а не для втечі. Він буде працювати з будь-яким персонажем (крім '). Також вона має дивну поведінку з порожніми рядками.
Стефан Гіменез

Питання використовується спеціально, "тому я відчуваю обов'язок змусити код працювати з ним. Це залежить від того, якою оболонкою ви користуєтесь погодою, якого потрібно уникати персонажу, але обом потрібно втекти "bash / tcsh"
Мартін Йорк

Звичайно, але проблем із цим немає -F'"'.
Стефан Гіменез

+1 Яка гарна ідея використовувати FS .... Це дозволить вирішити порожній рядок, що показує -1, і, наприклад, "$ 1" з командної лінії bash. ...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Пітер.O

Також працюйте з декількома символами як роздільник ... корисно!
COil

14

Використання trard wc:

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

Використання:

$ 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
Примітка. trне обробляє символи, які використовують більше одного байта .. див. Wikipedia tr (Unix) .. тобто. trне сумісний з Unicode.
Пітер.О


вам потрібно видалити символи пробілів $IFS, інакше readобріжте їх від початку та до кінця.
Стефан Шазелас


@ Peter.O, деякі trреалізації підтримують багатобайтові символи, але wc -cрахують байти, а не символи (все ж потрібні wc -mсимволи).
Стефан Шазелас

11

Ще одна реалізація , яка не залежить від зовнішніх програм, в bash, zsh, yashта деякі реалізації / версії ksh:

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

Використовувати line="${line//[!(]}"для підрахунку (.


Якщо в останньому рядку немає останнього \ n, цикл while виходить, оскільки, хоча він читає останній рядок, він також повертає ненульовий код виходу, щоб вказати EOF ... щоб обійти його, наступний фрагмент працює (..Під час мене eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
клопочуть

@Gilles: ви додали простір, /який не потрібен в bash. Це ksh вимога?
enzotib

1
Trailing /потрібен і в старих версіях ksh, і IIRC в старих версіях bash.
Жиль

10

Відповіді з використанням awkневдач, якщо кількість збігів занадто велика (що трапляється в моїй ситуації). Для відповіді від loki-astari повідомляється про наступну помилку:

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

Для відповіді від enzotib (і еквівалента манатурки ) виникає помилка сегментації:

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

sedРішення по maxschlepzig працює правильно, але повільно (таймінги нижче).

Тут ще не запропоновано деякі рішення. По-перше, використовуючи grep:

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

І використовуючи perl:

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

Ось декілька термінів для кількох рішень (впорядковані найповільніші та найшвидші); Я обмежився речами тут. 'foo.txt' - це файл з одним рядком і одним довгим рядком, який містить 84922 збігів.

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

+ гарна ідея! Я розширив вашу таблицю, у новій відповіді сміливо редагуйте (остаточне зображення не так зрозуміло, але я вважаю, що @maxschlepzig - це швидше рішення)
JJoao

Рішення maxschlepzig дуже швидко!
okwap


8

Інша можлива реалізація за допомогою awk та gsub:

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

Функція gsubеквівалентна sed 's///g'.

Використовувати gsub("[^(]", "")для підрахунку (.


Ви можете зберегти один символ, тобто при видаленні перенаправлення stdin ...;)
maxschlepzig

@maxschlepzig: так, звичайно;)
enzotib

1
awk '{print gsub(/"/,"")}' input-fileБуло б достатньо, оскільки "Для кожної підрядки, що відповідає регулярному виразу r у рядку t, підмініть рядок s та поверніть кількість підстановок." (man awk)
манатура

6

Я вирішив написати програму на С, бо мені нудно.

Ймовірно, слід додати перевірку вводу, але крім цього все встановлено.

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

Дякую! Дякуємо, що нудьгували, щоб я міг чогось навчитися. О зачекайте, чи потрібно повернення?
Тім

* знизує плечима * , якщо ви хочете бути повністю коректними, вам також потрібно додати ще декілька #includes, але попередження за замовчуванням для мого компілятора, здається, не хвилюються.
користувач606723

Ви можете залишити, free(line)тому що, виходячи з програми, неявно звільняє всю виділену пам'ять - тоді є місце для return 0;...;). Навіть у прикладах це не гарний стиль залишати код повернення невизначеним. Btw, getlineце розширення GNU - на випадок, коли хтось цікавиться.
maxschlepzig

@maxschlepzig: Чи пам'ять вказана лінією, виділеною getline ()? Чи розподіляється він динамічно на купі маликом або статично на стеці? Ви сказали, що звільняти це не потрібно, так це не виділяється динамічно?
Тім

1
@Tim, так, наприклад, якщо ви перефактуруєте код таким чином, що він є окремою функцією - скажімо - f, яка викликається кілька разів від іншого коду, то вам потрібно буде зателефонувати freeпісля останнього дзвінка getlineв кінці цієї функції f.
maxschlepzig

6

Для рядка найпростішим було б з ( trі wcне потрібно зайву кількість з awkабо sed), але зауважте вищенаведені коментарі про tr, рахує байти, а не символи -

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

де $xзмінна, яка містить рядок (а не файл) для оцінки.


4

Ось ще одне рішення C, яке потребує лише STD C та менше пам’яті:

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

Це не буде повідомляти про останній рядок, якщо в ньому немає останнього "\ n"
Peter.O

1
@fred, так, це цілеспрямовано, тому що лінія без трейлінгу \n- це не реальна лінія. Це така ж поведінка, як і в моїй іншій відповіді sed / awk (tr / awk).
maxschlepzig

3

Ми можемо використовувати grepз , regexщоб зробити його більш простим і ефективним.

Для підрахунку конкретного персонажа.

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

Для підрахунку спеціальних символів, включаючи символи пробілу.

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

Тут ми вибираємо будь-який символ із [\S\s]і за допомогою -oопції, яку ми робимо grepдля друку кожної відповідності (тобто кожного символу) в окремому рядку. А потім використовуйте wc -lдля підрахунку кожного рядка.


ОП не хочуть друкувати кількість усіх символів у файлі! Він хоче порахувати / роздрукувати номер конкретного символу. наприклад, скільки "в кожному рядку; і для будь-яких інших символів. побачити його питання, а також прийняту відповідь.
αғsnιη

3

Можливо, більш прямим, суто дивним варіантом відповіді буде використання спліт. Розбиття бере рядок і перетворює його в масив, значення повернення - кількість створених елементів масиву + 1.

У наступному коді буде надруковано кількість разів "на кожному рядку.

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

більше інформації про спліт http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html


2

Ось простий скрипт Python, щоб знайти кількість "у кожному рядку файлу:

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

Тут ми використали countметод вбудованого strтипу.


2

Для чистого рішення bash (однак, це специфічно для bash): Якщо $xце змінна, що містить рядок:

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

${x//Річ видаляє всі символи , за винятком ", ${#x2}обчислює довжину цього спокою.

(Оригінальна пропозиція, з exprякою виникають проблеми, див. Коментарі:)

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

Зауважте, що він характерний для GNU exprі рахує байти, а не символи. З іншими expr:expr "x${x...}" : "x.*" - 1
Стефан Шазелас

О так, дякую! Я змінив його, використовуючи іншу в мене ідею, яка має перевагу взагалі не використовувати зовнішню програму.
Маріан

2

Замініть aна таблицю, яку потрібно підрахувати. Вихід - лічильник для кожного рядка.

perl -nE 'say y!a!!'

2

Порівняння часу представлених рішень (не відповідь)

Ефективність відповідей не важлива. Тим не менш, дотримуючись @josephwb підходу, я намагався вчасно встановити всі подані відповіді.

Я використовую в якості португальського переклад Віктора Гюго "Les Miserables" (чудова книга!) І рахую випадки "a". Моє видання має 5 томів, багато сторінок ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

Відповіді C були складені за допомогою gcc, (оптимізації немає).

Кожну відповідь виконували 3 рази і вибирали найкращу.

Не надто довіряйте цим цифрам (моя машина виконує інші завдання тощо, тощо). Я ділюсь цими часом з вами, тому що я отримав кілька несподіваних результатів і впевнений, що ви знайдете ще кілька ...

  • 14 з 16 приурочених рішень зайняли менше 1 с; На 9 менше 0,1 с, багато з них використовують труби
  • 2 рішення, використовуючи bash рядок за рядком, обробляли лінії 30k, створюючи нові процеси, обчислювали правильне рішення за 10s / 20s.
  • grep -oP aшвидше, ніж дерево grep -o a (10; 11 проти 12)
  • Різниця між С та іншими не настільки велика, як я очікував. (7; 8 проти 2; 3)
  • (висновки вітаються)

(результати у випадковому порядку)

=========================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 робить усі важкі підйоми: повідомляє про кожен символ, знайдений у кожному номері рядка. Решта - лише підбити підрахунок за рядком та відформатувати результат.

Видаліть -n і отримайте кількість за весь файл.

Підрахунок текстового файлу 1,5Мег за 0,015 сек здається швидким.
І працює з символами (не байтами).


1

Розчин для баш. Жодна зовнішня програма не викликається (швидше для коротких рядків).

Якщо значення є змінною:

$ a='"Hello!"'

Це дозволить надрукувати кількість "вмісту:

$ b="${a//[^\"]}"; echo "${#b}"
2
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.