Как посчитать количество конкретного символа в каждой строке?

88

Мне было интересно, как подсчитать количество конкретного символа в каждой строке некоторыми утилитами обработки текста?

Например, чтобы посчитать "в каждой строке следующего текста

"hello!" 
Thank you!

Первая строка имеет две, а вторая строка имеет 0.

Другой пример - считать (в каждой строке.

Тим
источник
1
Просто добавлю, что вы получили гораздо более высокую производительность, написав для этого собственную программу на 10 строк, а не используя регулярные выражения с sed. Вы должны рассмотреть возможность в зависимости от размера ваших входных файлов.
user606723

Ответы:

105

Вы можете сделать это с помощью 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.

maxschlepzig
источник
3
+1 должен быть более эффективным, чем tr& wcверсия.
Стефан Гименес
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, которая кодируется как 'c3 9f' в UTF-8, то есть два байта.
maxschlepzig
50

Я бы просто использовал 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
Peter.O
Также работа с несколькими символами в качестве разделителя ... полезно!
Катушка
15

Используя 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не обрабатывает символы, которые используют более одного байта .. см. википедию tr (Unix) .. т.е. trне соответствует Unicode.
Peter.O
вам нужно удалить пробельные символы из $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//[!(]}"для подсчета (.

enzotib
источник
Когда в последней строке нет завершающего \ n, цикл while завершается, поскольку, хотя он и читает последнюю строку, он также возвращает ненулевой код выхода, указывающий EOF ..., чтобы обойти его, работает следующий фрагмент (... Это давало мне покои, и я только что обнаружил эту работу) ... eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
Peter.O
@Gilles: вы добавили трейлинг, /который не нужен в bash. Это требование кш?
энзотиб
1
Трейлинг /необходим в старых версиях 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 (и эквивалента от manatwork ) возникает ошибка сегментации:

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
josephwb
источник
+ хорошая идея! Я расширил вашу таблицу, в новом ответе, не стесняйтесь редактировать (окончательная картинка не так ясна, но я считаю, что @maxschlepzig - более быстрое решение)
JJoao
Решение Maxschlepzig очень быстро!
okwap
9

Другое awkрешение:

awk '{print gsub(/"/, "")}'
Стефан Шазелас
источник
8

Еще одна возможная реализация с awk и gsub:

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

Функция gsubявляется эквивалентом sed 's///g'.

Используйте gsub("[^(]", "")для подсчета (.

enzotib
источник
Вы можете сохранить один символ, т.е. при удалении перенаправления стандартного
ввода
@maxschlepzig: да, конечно;)
энзотиб
1
awk '{print gsub(/"/,"")}' input-fileбудет достаточно, так как «Для каждой подстроки, совпадающей с регулярным выражением r в строке t, подставьте строку s и верните число подстановок». (man awk)
manatwork
6

Я решил написать программу на C, потому что мне было скучно.

Вы, вероятно, должны добавить проверку ввода, но кроме этого все готово.

#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);
}
user606723
источник
Спасибо! Спасибо за то, что мне скучно, и я могу чему-то научиться. Ой, подождите, вам нужен возврат?
Тим
* shrugs * , если вы хотите быть полностью корректным, вам также нужно добавить еще несколько #include, но предупреждения по умолчанию на моем компиляторе, похоже, не волнуют.
user606723
Вы можете пропустить, free(line)потому что выход из программы неявно освобождает всю выделенную память - тогда есть место для return 0;...;). Даже в примерах не рекомендуется оставлять код возврата неопределенным. Кстати, getlineрасширение GNU - на случай, если кому-то интересно.
maxschlepzig
@maxschlepzig: Память указана линией, выделенной getline ()? Распределяется ли он динамически в куче с помощью malloc или статически в стеке? Вы сказали, что освобождение не является необходимым, поэтому оно не распределяется динамически?
Тим
1
@Tim, да, например, если вы реорганизуете код так, что это отдельная функция - скажем, - fкоторая вызывается несколько раз из другого кода, то вам придется вызывать freeпосле последнего вызова getlineв конце этой функции f.
maxschlepzig
6

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

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

где $xпеременная, которая содержит строку (не файл) для оценки.

Ocumo
источник
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;
}
maxschlepzig
источник
Это не будет сообщать о последней строке, если у нее нет завершающего '\ 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для подсчета каждой строки.

Каннан Мохан
источник
ОП не хочет печатать количество всех символов в файле! Он хочет посчитать / напечатать номер конкретного персонажа. например, сколько "в каждой строке; и для любых других символов. увидеть его вопрос, а также принял ответ.
αғsнιη
3

Возможно, более прямым, чисто awk-ответом будет использование split. Split берет строку и превращает ее в массив, возвращаемое значение - количество сгенерированных элементов массива + 1.

Следующий код распечатает количество раз "появляется в каждой строке.

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

больше информации о разделении http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html

bleurp
источник
2

Вот простой скрипт на Python для определения количества "в каждой строке файла:

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

Здесь мы использовали countметод встроенного strтипа.

heemayl
источник
2

Для чисто решения bash (однако, оно $xзависит от bash): If - это переменная, содержащая вашу строку:

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

Эта ${x//вещь удаляет все символы, кроме ", ${#x2}рассчитывает длину этого отдыха.

(Оригинальное предложение, при использовании exprкоторого возникают проблемы, см. В комментариях:)

expr length "${x//[^\"]/}"
Мэриан
источник
Обратите внимание, что он специфичен для GNU exprи учитывает байты, а не символы. С другими expr:expr "x${x...}" : "x.*" - 1
Стефан Шазелас
Ах да, спасибо! Я изменил его, используя другую идею, которая у меня была, и имеет то преимущество, что вообще не использует внешнюю программу.
Marian
2

Заменить aна символ, который будет засчитан. Выходной счетчик для каждой строки.

perl -nE 'say y!a!!'
JJoao
источник
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 построчно, обрабатывали 30 тыс. Строк, создавая новые процессы, вычисляя правильное решение в 10 с / 20 с.
  • 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
JJoao
источник
1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

где grep выполняет всю тяжелую работу: сообщает о каждом символе, найденном в каждом номере строки. Остальное просто сложить счетчик на строку и отформатировать вывод.

Удалите -nи получите счет для всего файла.

Подсчет 1,5Meg текстового файла менее чем за 0,015 секунды кажется быстрым.
И работает с символами (не байтами).


источник
1

Решение для Баш. Внешняя программа не вызывается (быстрее для коротких строк).

Если значение находится в переменной:

$ a='"Hello!"'

Это напечатает, сколько "он содержит:

$ b="${a//[^\"]}"; echo "${#b}"
2
sorontar
источник