HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

指定するキーワードを全て含むファイルを探すスクリプト

指定するキーワードのいずれかを含むファイルを探す場合には、単純に

egrep -lr "(key1|key2|key3|key4|key5|key6)" data

としてやれば良くて、結果は以下のようになる。

data/1_2_3_4_5.txt
data/1_2_3_4_5_6.txt
data/1_2_3_5_6.txt

ただ、その逆、「指定するキーワードの全て」を含むファイルを探すとなるとなかなか簡単にはいかない。

一案として、

grep -lr key6 $(grep -lr key5 $(grep -lr key4 $(grep -lr key3 $(grep -lr key2 $(grep -lr key1 data)))))

なんていうのも一つの手だが、キーワードが増えるごとにネストの段数が増えるのが煩わしいし、なんか気持ち悪い。

なので、ちょっとしたシェルスクリプトを書いてみた。 なお、【パスに空白が含まれたりする】ケースは知らないなので、その辺りはご容赦を。

#! /bin/bash

#debug=1

dir=$1
if [ ! -d "${dir}" ]; then
    printf "%s: No such directory.\n" "${dir}"
    exit 1
fi
shift
keys=($@)
if [ ${#keys[@]} -eq 0 ]; then
    printf "Usage: %s dir keyword1 [keyword2 ...]\n" "${0}"
    exit 2
fi

if [ ${debug} ]; then
    echo "Count of keys is ${#keys[@]}"
fi

for f in $(find "${dir}" -type f) ; do
    ct=0
    for k in ${keys[@]} ; do
        if [ ${debug} ]; then
            echo "Checking ${f} for keyword '${k}'..."
            grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$'
        fi
        tmpCt=$(grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$' | wc -l)
        if [ ${tmpCt} -eq 0 ]; then
            break
        fi
        ct=$((ct+tmpCt))
    done
    if [ ${ct} -eq ${#keys[@]} ]; then
        if [ ${debug} ]; then
            echo "OK: ${f}"
        else
            echo "${f}"
        fi
    else
        if [ ${debug} ]; then
            echo "NG: ${f}"
        fi
    fi
done

これを

$ debug=1 ./grep-all.sh data key1 key2 key3 key4
Count of keys is 4
Checking data/1_2_3_4_5.txt for keyword 'key1'...
data/1_2_3_4_5.txt:1
Checking data/1_2_3_4_5.txt for keyword 'key2'...
data/1_2_3_4_5.txt:1
Checking data/1_2_3_4_5.txt for keyword 'key3'...
data/1_2_3_4_5.txt:1
Checking data/1_2_3_4_5.txt for keyword 'key4'...
data/1_2_3_4_5.txt:1
OK: data/1_2_3_4_5.txt
Checking data/1_2_3_4_5_6.txt for keyword 'key1'...
data/1_2_3_4_5_6.txt:1
Checking data/1_2_3_4_5_6.txt for keyword 'key2'...
data/1_2_3_4_5_6.txt:1
Checking data/1_2_3_4_5_6.txt for keyword 'key3'...
data/1_2_3_4_5_6.txt:1
Checking data/1_2_3_4_5_6.txt for keyword 'key4'...
data/1_2_3_4_5_6.txt:1
OK: data/1_2_3_4_5_6.txt
Checking data/1_2_3_5_6.txt for keyword 'key1'...
data/1_2_3_5_6.txt:1
Checking data/1_2_3_5_6.txt for keyword 'key2'...
data/1_2_3_5_6.txt:1
Checking data/1_2_3_5_6.txt for keyword 'key3'...
data/1_2_3_5_6.txt:1
Checking data/1_2_3_5_6.txt for keyword 'key4'...
NG: data/1_2_3_5_6.txt
Checking data/2_3_4_5_6.txt for keyword 'key1'...
NG: data/2_3_4_5_6.txt

としたり、

$ ./grep-all.sh data key1 key2 key3 key4 key5 key6
data/1_2_3_4_5_6.txt

としたり、という感じで。

【2023/06/11改版】 オリジナルのスクリプトは、key1にヒットしなくてもkey2~key6をチェックしてしまい、非常に効率が悪いと気付いたので改善。

オリジナルも残しておく。

#! /bin/bash

#debug=1

dir=$1
if [ ! -d "${dir}" ]; then
    printf "%s: No such directory.\n" "${dir}"
    exit 1
fi
shift
keys=($@)
if [ ${#keys[@]} -eq 0 ]; then
    printf "Usage: %s dir keyword1 [keyword2 ...]\n" "${0}"
    exit 2
fi

if [ ${debug} ]; then
    echo "Count of keys is ${#keys[@]}"
fi

for f in $(find "${dir}" -type f) ; do
    ct=0
    for k in ${keys[@]} ; do
        ct=$((ct+$(grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$' | wc -l)))
    done
    if [ ${ct} -eq ${#keys[@]} ]; then
        if [ ${debug} ]; then
            echo "OK: ${f}"
        else
            echo "${f}"
        fi
    else
        if [ ${debug} ]; then
            echo "NG: ${f}"
        fi
    fi
done