ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

Chromeの履歴をShellScriptで弄り倒す

ShellScriptでドヤりたいGWアドベントカレンダー1日目いくで。

ブラウザの履歴ってその人がその日何をやっていたのか示すものとして結構な情報詰まってると思うのよね。
「俺今日何やってたっけ・・・」っていう時の思い出し作業や、「さっき見てたページもう一回開きたい」ときとか ターミナルからさくっと開けたら便利じゃねってことで作った。

github.com

https://user-images.githubusercontent.com/17779386/56350061-dc7bc180-6204-11e9-84cc-11f0cf919426.gif

Requirement

  • Mac
  • fzf

Chrome履歴のDBの場所

Chromeに関するファイルは~/Library/Application\ Support/Google/Chrome/にある。
履歴に関するものはSQLiteのDBで保存されている。

~/Library/Application\ Support/Google/Chrome/Default/History 

ChromeHistoryにアクセスする

SQLiteなのでsqlite3コマンドで扱える。

$ sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History 

でテーブル等を見ることができるが、Chromeを開いているときはChromeがLockしているので

Error: database is locked

とアクセスが弾かれてしまう。 Chromeを一旦閉じて接続するか、一旦DBのコピーをしてそれに接続するといった形を取る。

# Lockされているのでコピーを取る
$ cp ~/Library/Application\ Support/Google/Chrome/Default/History ~/

# コピーしたDBに接続する
$ sqlite3 ~/History

こうすればChromeを開いたままChromeHistoryにアクセスできる。

ChromeHistoryからアクセス履歴を取得する

Historyにはいくつかテーブルがあるが、アクセス履歴を保存しているのはurlsテーブル。
sqlite3のコマンドは他で調べたらいっぱい出てくるので適当にググってね。

$ sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History 

SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .headers on
sqlite> select * from urls;

id|url|title|visit_count|typed_count|last_visit_time|hidden
57647|https://www.google.co.jp/save?authuser=0|コレクション|2|0|13200489910808958|0
57648|https://number333.org/|iPhone・Mac・ガジェットブログ "monograph(モノグラフ)"|1|0|13200489927232416|0
57649|https://qiita.com/luccafort/items/e36faff2d7e4320f2bf3|tigがLibrary not loadedとエラーになってしまう現象の対応 - Qiita|1|0|13200491048935113|0
57650|https://orebibou.com/2019/01/macos%E3%81%A7%E3%80%8Cdyld-library-not-loaded-usr-local-opt-readline-lib-libreadline-7-dylib%E3%80%8D%E3%81%AA%E3%82%8B%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E5%87%BA%E3%82%8B%E3%82%88%E3%81%86/|MacOSで「dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib」なるエラーが出るようになる | 俺的備忘録 〜なんかいろいろ〜|1|0|13200491115865059|0

アクセス時刻を表すlast_visit_timeカラムの値が通常のunixtimeとは異なるから注意。
どうやら基本時刻が1601-01-01 00:00:00に設定されているらしいので、変換が必要になる。

時刻の変換、ソート等をしたら最終的には下のようなクエリになった。

SELECT
    url,
    title,
    DATETIME(last_visit_time / 1000000 + (strftime('%s', '1601-01-01') ), 'unixepoch', '+9 hours') AS date
FROM
    urls
GROUP BY
    title
ORDER BY
    date DESC
LIMIT
    10000
;

実行結果

url|title|date
https://orebibou.com/2019/01/macos%E3%81%A7%E3%80%8Cdyld-library-not-loaded-usr-local-opt-readline-lib-libreadline-7-dylib%E3%80%8D%E3%81%AA%E3%82%8B%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E5%87%BA%E3%82%8B%E3%82%88%E3%81%86/|MacOSで「dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib」なるエラーが出るようになる | 俺的備忘録 〜なんかいろいろ〜|2019-04-23 20:05:15
https://www.google.co.jp/search?q=dyld:%20Library%20not%20loaded:%20/usr/local/opt/readline/lib/libreadline.7.dylib|dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib - Google 検索|2019-04-23 20:05:12
https://qiita.com/luccafort/items/e36faff2d7e4320f2bf3|tigがLibrary not loadedとエラーになってしまう現象の対応 - Qiita|2019-04-23 20:04:08
https://qiita.com/nwtgck/items/f5427c0d0f7827658bd5|Macでawkを実行するとエラー: "dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib" となる問題の解決法 - Qiita|2019-04-23 20:02:08

あとはこれをShellで包んであげればOKだ。

ShellScriptでイジイジする

基本的な流れは

  1. ChromeHistoryをコピーする
  2. コピーしたChromeHistoryにexpectでクエリ投げる
  3. クエリの結果をawkで整形してfzfに食わせる

でfin。イージーですね。

chromeHistory.sh

# ChromeのDBの内容をcsvで一時保存するパス
PATH_CHROME_HISTORY=/Users/`whoami`/chrome_history.csv

function export_chrome_history() {
    local USER=`whoami`
    # Chromeを開いているとdbがロックされるのでコピーしたものを参照する
    cp ~/Library/Application\ Support/Google/Chrome/Default/History ~/

    local SQL="
    SELECT
        url,
        title,
        DATETIME(last_visit_time / 1000000 + (strftime('%s', '1601-01-01') ), 'unixepoch', '+9 hours') AS date
    FROM
        urls
    GROUP BY
        title
    ORDER BY
        date DESC
    LIMIT
        10000
    ;
    "
    # そのままSQLを流すとエラーが出るので改行を消す
    local SQL=$(echo "${SQL}" | tr '\n' ' ')

    # コマンドで参照できるようにDBの内容をcsvに書き出す
    expect -c "
        spawn sqlite3 /Users/$USER/History
        expect \">\"
        send \".mode csv\r\"
        expect \">\"
        send \".output $PATH_CHROME_HISTORY\r\"
        expect \">\"
        send \"$SQL\r\"
        expect \">\"
    " >/dev/null
}

function show_chrome_history() {
    local filter=${1:-""}
    local chrome_history=$(cat $PATH_CHROME_HISTORY | tr -d '"')

    # 見栄えを良くするためURLは表示しない
    local select_history=$(
        echo "$chrome_history" \
        | grep "$filter" \
        | awk -F ',' '!a[$2]++' \
        | awk -F ',' '{print $3"\t"$2}' \
        | tr -d "\r" \
        | fzf \
        | tr -d "\n"
    )

    # URL取得処理
    if [ -n "$select_history" ]; then
        # URLを取得する際タイトルでgrepするため
        local title=`echo "$select_history" | awk -F '\t' '{print $1}'`
        local url=`echo "$chrome_history" | grep "$title"  | head -n 1 |awk -F ',' '{print $1}'`
        open $url
    fi
}

export_chrome_history
show_chrome_history $1

実行

$ sh chromehistory.sh

https://rasukarusan.github.io/blog-assets/gif/chromehistory/demo1.gif

sh chromehistory.sh PATTERNでgrepできるようにしている。予めある程度絞り込んだ結果を出したいときやURLで絞り込みたい時に使う。
fzfの選択画面では見栄えのため、URLを表示させていないから用意した。ぶっちゃけ使った試しはない。

日付ごとに出す

直近のアクセスしたページを出力するのならば上記で事足りるが、「昨日みたあのページなんだっけ・・・」という時にfzfの画面で一々下に行くのはなんとも愚かだと思うので、日付ごとに出力するようにする。

function show_by_date() {
    local chrome_history=$(cat $PATH_CHROME_HISTORY | tr -d '"')
    # 表示したい日付を選択する
    local select_date=$(
        echo "$chrome_history" \
        | awk -F ',' '{print $3}' \
        | awk -F ' ' '{print $1}' \
        | grep -P '^[0-9]{4}-.*' \
        | sort -ur \
        | tr -d "\r" \
        | xargs -I {} gdate '+%Y-%m-%d (%a)' -d {} \
        | fzf \
        | awk -F '(' '{print $1}'
    )
    show_chrome_history $select_date
}

GNU系のdateコマンドで曜日込みの日付を出力して、日付を第一引数に渡してgrepさせているだけですね。
sh chromehistory.sh -dみたいな形で実行したかったので最終的には以下。

chromehistory.sh

PATH_CHROME_HISTORY=/Users/`whoami`/chrome_history.csv

function export_chrome_history() {
    ...
}

function show_chrome_history() {
    ...
}

function show_by_date() {
    ...
}

function main() {
    export_chrome_history
    if [ "$1" = '-d' ]; then 
        show_by_date
    else
        show_chrome_history $1
    fi
}

main $1

https://rasukarusan.github.io/blog-assets/gif/chromehistory/demo2.gif

終わり

fzfに食わせるだけでいろんなものがだいぶ便利になるよね。
大体面倒くさい作業あったらどうにかしてfzfに食わせられないか考えちゃうんだけどそれだけで午前の業務が終わることが多々ある。ごめんなさいね。