web-dev-qa-db-ja.com

各行の特定の文字の数を数える方法は?

一部のテキスト処理ユーティリティで、各行の特定の文字の数を数える方法を知りましたか?

たとえば、"次のテキストの各行

"hello!" 
Thank you!

最初の行には2つ、2番目の行には0があります。

もう1つの例は、(各行に。

97
Tim

sedawkでそれを行うことができます:

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

ここで、datはテキストの例です。sedは、すべての非_"_文字を(各行について)削除し、awkは各行についてそのサイズを出力します(つまり、lengthlength($0)に相当します。ここで、_$0_は現在の行を示します)。

別のキャラクターの場合は、sedの式を変更するだけです。たとえば、_(_の場合:

_'s/[^(]//g'
_

pdate:sedはタスクのやり過ぎの一種です-trで十分です。 trを使用した同等のソリューションは次のとおりです。

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

つまり、trは、文字セット_-c_にない(_"\n_は補数を意味する)すべての文字を削除します。

115
maxschlepzig

私はawkを使用します

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

ここでは、フィールド区切り文字(-Fフラグを使用)を文字"に設定し、フィールド数NF-1を出力するだけです。ターゲット文字の出現回数は区切られたフィールドの数よりも1つ少ない。

シェルによって解釈される変な文字については、必ずエスケープする必要があります。そうしないと、コマンドラインがそれらを試みて解釈します。したがって、")の両方で、フィールド区切り文字をエスケープする必要があります(\を使用)。

52
Martin York

tr ard 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
15

外部プログラムに依存しないさらに別の実装、bashzshyashおよびkshのいくつかの実装/バージョン:

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

使用する line="${line//[!(]}"カウント用(

11
enzotib

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

maxschlepzig によるsedソリューションは正しく機能しますが、低速です(以下のタイミング)。

ここではまだ提案されていないいくつかのソリューション。まず、grepを使用します。

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

そしてPerlを使用:

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

以下に、いくつかのソリューションのタイミングをいくつか示します(最も遅いものから最も速いものへの順序)。ここではワンライナーに限定しました。 'foo.txt'は、84922の一致を含む1行と1つの長い文字列を持つファイルです。

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

別のawkソリューション:

awk '{print gsub(/"/, "")}'
9

Awkとgsubを使用した別の可能な実装:

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

関数gsubは、sedの_'s///g'_と同等です。

_(_をカウントするには、gsub("[^(]", "")を使用します。

8
enzotib

退屈だったので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);
}
6
user606723

文字列の場合、最も単純なのはtrwcです(awkまたはsedでやり過ぎる必要はありません)。ただし、trに関する上記のコメントは、文字ではなくバイトをカウントします-

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

どこ $xは、評価する文字列(ファイルではない)を含む変数です。

6
Ocumo

これは、STD Cとより少ないメモリしか必要としない別の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;
}
4
maxschlepzig

多分もっと単純で、純粋に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

3
bleurp

grepregexとともに使用すると、よりシンプルで強力になります。

特定の文字を数える。

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

空白文字を含む特殊文字をカウントします。

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

ここでは、[\S\s]を使用して任意の文字を選択し、-oオプションを使用してgrepを作成し、各一致(つまり、各文字)を別々の行に出力します。次に、wc -lを使用して各行をカウントします。

3
Kannan Mohan

純粋なbashソリューションの場合(ただし、bash固有です):$xが文字列を含む変数の場合:

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

${x//"を除くすべての文字を削除し、${#x2}はこの残りの長さを計算します。

(問題のあるexprを使用した元の提案。コメントを参照してください:)

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

以下は、ファイルの各行の"の数を見つけるための簡単なPythonスクリプトです。

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

ここでは、組み込みcountタイプのstrメソッドを使用しました。

2
heemayl

カウントする文字でaを置き換えます。出力は各行のカウンターです。

Perl -nE 'say y!a!!'
2
JJoao

提示されたソリューションの時間比較(回答ではありません)

回答の効率は重要ではありません。それにもかかわらず、@ josephwbアプローチに従って、提示されたすべての回答の時間を計ろうとしました。

私は入力として、Victor Hugoのポルトガル語訳「Les Miserables」(素晴らしい本!)を使用し、「a」の出現をカウントします。私のエディションには5巻、多くのページがあります...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

Cの回答はgccでコンパイルされました(最適化なし)。

各回答は3回実行され、最良のものを選択しました。

これらの数値をあまり信用しないでください(私のマシンは他のタスクなどを実行しています)。私はこれらの時間をあなたと共有します、私はいくつかの予期しない結果を得たので、あなたはさらにいくつかを見つけると確信しています...

  • 16の時限ソリューションのうち14は1秒未満しかかかりませんでした。 9未満、0.1秒以下、多くはパイプを使用
  • 行ごとにbashを使用する2つのソリューションは、新しいプロセスを作成して30k行を処理し、10秒/ 20秒で正しいソリューションを計算しました。
  • grep -oP agrep -o aより10倍高速です(10; 11 vs 12)
  • Cと他の人の違いは思ったほど大きくありません。 (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
2
JJoao
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

ここで、grepはすべての重い作業を行います。各行番号で見つかった各文字を報告します。残りは、1行あたりのカウントを合計し、出力をフォーマットすることです。

-nを削除し、ファイル全体の数を取得します。

1.5Megのテキストファイルを0.015秒未満でカウントすると高速に見えます。
そして(バイトではなく)文字で動作します。

1
user79743

Bashのソリューション。外部プログラムは呼び出されませんでした(短い文字列の方が高速です)。

値が変数にある場合:

$ a='"Hello!"'

これにより、" を含む:

$ b="${a//[^\"]}"; echo "${#b}"
2
1
Isaac