シェルスクリプトで副文字列の出現回数を数える

最近、サーバのコンフィグバックアップ・リストアが簡単にできるようにシェルスクリプトを書いているのですが、例えば /etc/sysconfig/network とか /etc/nginx/cond.d/default.conf というコンフィグで最後のファイル名 networkdefault.conf だけループ処理内で自動的に判別して取得する必要がありました。

これを実現させるために、個々のコンフィグファイルの絶対パス内に / がいくつ含まれているかカウントして cut する必要がありました。

その流れで、副文字列の出現回数を数える substr_count() 的なものがほしかったので、offset 指定まではできませんが、シェルスクリプトで特定文字が何回現れるかカウント出来るように書いてみました。

substr_count() 関数作成

本来ならだらだらコーディングしないと行けないところ、Linux 環境だと強力なコマンドが色々あるので、それをうまく使えば意外と簡単にできちゃったりします。

やり方は単純で、以下のようにします。

  • 検索対象文字列から検索する文字・文字列を見つけて改行文字「\n」を付ける
  • その後、grep で検索する文字列が何回現れるかカウントする
  • オプションとして「i」を指定すると大文字・小文字を区別しないようにする

ちなみに、オプションを処理する際に getopts をよく使いますが、私はあまり好きじゃないので、使っていません。

substr_count.sh
#!/bin/bash

#### $OPTION
####    i : 大文字・小文字区別するか
#### $STRING : 検索対象文字列
#### $SEARCH : 検索する文字列
function substr_count()
{
    local OPTION=""
    local STRING=""
    local SEARCH=""

    #### オプションなし
    if [ $# -eq 2 ]; then

        STRING=$1
        SEARCH=$2

        echo $STRING | sed "s/${SEARCH}/${SEARCH}\n/g" | grep -c "${SEARCH}"
        return $?

    #### オプションあり
    elif [ $# -eq 3 ]; then

        OPTION=$1
        STRING=$2
        SEARCH=$3

        [ "$OPTION" != "i" ] && return 101

        echo $STRING | sed "s/${SEARCH}/${SEARCH}\n/gi" | grep -c "${SEARCH}"
        return $?

    #### その他
    else

        return 99

    fi
}

使って見る

substr_count [OPTION] [STRING] [SEARCH]

  • OPTION は必須ではないが、指定可能なのは「i」だけ (大文字・小文字区別しない)
  • STRING は、検索対象文字列 :「' '」で囲む
  • STRING に変数を使う際には、「" "」で囲む
  • SEARCH は、検索する文字列 :「' '」で囲む
  • SEARCH に特殊文字を使う際には、各文字の頭に「\」を付ける

通常文字カウント

substr_count.sh
#!/bin/bash

function substr_count()
{
    ・・・
}

substr_count 'aaabbbccc \ AAA BBB CCC \' 'aaa'    #### 大文字・小文字区別あり
substr_count i 'aaabbbccc \ AAA BBB CCC \' 'aaa'  #### 大文字・小文字区別なし
実行結果
$ ./substr_count.sh
1
2

特殊文字カウント

substr_count.sh
#!/bin/bash

function substr_count()
{
    ・・・
}

substr_count '/etc/hosts' '\/'
substr_count '/etc/sysconfig/network' '\/'

echo

substr_count '\ \\ \\\ $ $$ $$$' '\$'
substr_count '\ \\ \\\ $ $$ $$$' '\$\$'
実行結果
$ ./substr_count.sh
2
3

6
2

ファイルの中身の文字列カウント

/tmp/test.txt
aaa bbb ccc ddd eee fff AAA BBB CCC DDD EEE FFF
aaa bbb ccc ddd fff AAA BBB CCC DDD EEE
aaa bbb ccc ddd AAA BBB CCC DDD
aaa bbb ccc AAA BBB CCC
aaa bbb AAA BBB
aaa AAA
aaa
substr_count.sh
#!/bin/bash

function substr_count()
{
    ・・・
}

substr_count "`cat /tmp/test.txt`" 'aaa'     #### 大文字・小文字区別あり
substr_count i "`cat /tmp/test.txt`" 'aaa'   #### 大文字・小文字区別なし
実行結果
$ ./substr_count.sh
7
13

応用 : コンフィグファイルの絶対パスからディレクトリとファイル名を別々に取得

以下のスクリプトは、シェルスクリプトでコンフィグファイルが記述されたリスト (例えば、conf_list.txt) を 1行ずつ読込みながら、バックアップ専用のディレクトリ内に元コンフィグファイルのディレクトリ階層をそのまま作成してバックアップをとるときに使っています。

こうすることによって、同じ名前を持つファイルが上書きされる問題を回避できます。

ファイル、もしくはコマンドの実行結果をシェルスクリプトで 1行ずつ読み込みたい場合には以下の記事を参考にしてください。

先ほど作成した関数を使って、コンフィグファイルの絶対パスからディレクトリとファイル名を取得してみます。

substr_count.sh
#!/bin/bash

function substr_count()
{
    ・・・
}

CONF_PATH="/etc/sysconfig/network"

SLASH_CNT=`substr_count "$CONF_PATH" '\/'`   #### 3
FIELD_CNT=`expr $SLASH_CNT + 1`              #### 4

CONF_DIR=`echo $CONF_PATH  | cut -d'/' -f1-${SLASH_CNT}`
CONF_FILE=`echo $CONF_PATH | cut -d'/' -f${FIELD_CNT}`

echo "PATH : $CONF_PATH"
echo "DIR  : $CONF_DIR"
echo "FILE : $CONF_FILE"
実行結果
$ ./substr_count.sh
PATH : /etc/sysconfig/network
DIR  : /etc/sysconfig
FILE : network

終わりに

特定文字をカウントするために、スクリプトを作成するまでもないですが、ちょっとやって見たかっただけなので、あまり深い意味はないです。

ポイントは、ここですね。

  • echo $STRING | sed "s/${SEARCH}/${SEARCH}\n/g" | grep -c "${SEARCH}"
  • echo $STRING | sed "s/${SEARCH}/${SEARCH}\n/gi" | grep -c "${SEARCH}"

色々試して見てください。

以上、シェルスクリプトで特定文字が何回現れるかカウントする でした。