シェルでPacmanのデータベースを解析するお話 ②

Shell Script

こんにちは、山田ハヤオです。前回はパッケージファイルについての簡単な説明からPacmanデータベースのだいたいの大まかな構造を見ていきました。

今回は実際にそのデータベースをシェルスクリプトで解析していこうと思います。

本来の目的は「インストールされているファイルからパッケージファイルを生成する」ことなのでそれのための関数ですね。

スポンサーリンク

データベースについて

前回に引き続きGoogleChromeを見ながら実装を進めていこうと思います。

まずは/var/lib/pacman/local/google-chrome-91.0.4472.77-1/descの解析を行っていきます。

このファイルはだいたい以下のような内容になっています。

%NAME%
google-chrome

%VERSION%
91.0.4472.77-1

%BASE%
google-chrome

%DESC%
The popular and trusted web browser by Google (Stable Channel)

%URL%
Google Chrome - The Fast & Secure Web Browser Built to be Yours
Chrome is the official web browser from Google, built to be fast, secure, and customizable. Download now and make it yours.
%ARCH% x86_64 %BUILDDATE% 1622128621 %INSTALLDATE% 1622128640 %PACKAGER% Unknown Packager %SIZE% 265734962 %LICENSE% custom:chrome %VALIDATION% none %DEPENDS% alsa-lib gtk3 libcups libxss libxtst nss %OPTDEPENDS% pipewire: WebRTC desktop sharing under Wayland kdialog: for file dialogs in KDE gnome-keyring: for storing passwords in GNOME keyring kwallet: for storing passwords in KWallet libunity: for download progress on KDE ttf-liberation: fix fonts for some PDFs - CRBug #369991 xdg-utils

「%%」でセクションが区切られ1行ごとにデータが入っているようです。

VALIDATIONまでのセクションは必ず1行に収まりそう(他のパッケージも見て)なので、これらは変数として扱っていこうと思います。

逆にDEPENDSやOPTDEPENDSは複数のデータを持っているようなので配列として扱っていこうと思います。

他の言語ならJSONに変換するのが良いのでしょうけど、シェルスクリプトで使う場合はかえって非効率なので今回はしません。

大いに参考になった記事

今回のこの解析用のコードを書くにあたって、大きく役に立った記事があります。

シェルスクリプト リファクタリング ~遅いシェルスクリプトが供養されてたので蘇生して256倍に高速化させました~

このQiitaの記事は、ハヤオのコードの問題点を指摘して改善していくという内容です。

実装方法がわからなくてしかたなくjqやcrudiniといった低速な外部コマンドを使った結果とても遅くなってしまいました。それを改善、修正するまでの過程をまとめています。

シェルスクリプトの利点や欠点、POSIX互換なども考える非常に良い内容になっています。

正直めちゃくちゃすごくて勉強になりました。ありがとうございます。

この記事の最後の方でiniファイルの解析を独自で実装している部分があります。

この実装を参考にして今回のPacmanのデータベースの解析を実装しました。

また同様に様々な部分で記事の内容を反映し以前よりかはまともなコードになっているかと思います。

(printfコマンドで変数に代入したり配列への代入にreadarrayを使ったりとか)

解析のコード

まずは完成したコードです。

# readdb <file> <section>
readdb(){
    local _path="${1}" _section="${2}" _dblines _line _in_section=false
    readarray -t _dblines < "${_path}"
    for _line in "${_dblines[@]}"; do
        case "${_line}" in
            "%${_section}%")
                _in_section=true
                continue
                ;;
            "%"*"%")
                _in_section=false
                continue
                ;;
        esac
        [[ "${_in_section}" = true ]] && [[ -n "${_line}" ]] && echo "${_line}"
    done
}

コメントにもある通り、readdb <データベースへのパス> <セクション名>という構文で使える関数です。

データベース(テキストファイル)の内容を1行ずつ_dblinesという配列に入れ、1行ずつそれを評価しています。

指定されたセクション内にいるのならその内容をechoで標準出力に返します。

この実装はcrudiniのシェルスクリプト実装を参考にしました。

こんな感じでデータベースの内容を取り出すことができます。(Zshだとこれ動かないですね…)

データベースの内容をまとめて解析、取得する

descファイルとfilesファイルのデータベースの構文は一緒だったので、どちらも同じコードで解析できます。

今回のスクリプトではprintfコマンドを使って変数に解析結果を入れていきました。

    msg_info "Loading package datebase ..."
    readarray -t _db_filelist < <(readdb "${_datebase}/files" FILES | grep -v "/$")
    readarray -t _db_depends < <(readdb "${_datebase}/desc" "DEPENDS")
    for _data in "NAME" "VERSION" "BASE" "DESC" "URL" "ARCH" "BUILDDATE" "INSTALLDATE" "PACKAGER" "SIZE" "LICENSE" "VALIDATION"; do
        printf -v "_db_${_data,,}" "$(readdb "${_datebase}/desc" "${_data}")"
    done

printf -vによって指定された変数に値を代入できます。evalを使うより見やすくでエレガントですね。

filesファイルに記述されているパスには規則があり、行の最後が/で終わっているものはディレクトリという規則があります。

ディレクトリの情報はいらないので、今回は行末が/のものを除外してファイルのみを抽出し配列に入れています。

MTEEファイルについて

前回ローカルのデータベースを調べた時、1つだけgzipで圧縮されたファイルがありました。

それを展開してみます。(Xarchiverで中身を見ました)

中にはどうやら同名のテキストファイルが1つだけあるようです。

どうやら.MTREEは展開されるディレクトリやファイルの権限や更新日時などのファイルシステム用の情報を保存しているようです。(細かいですね…)

今回はファイルをそのままコピーするのでこのファイルを生成し直すのではなく流用する形でいきます。

(厳密じゃないけどまぁだいたいこれでいいでしょ())

データベースの解析ができた!

上記のBash関数でローカルにインストールされているパッケージのデータベースを完全に参照することができました。

次回はPacmanのパッケージファイルであるpkg.tar.zstの内容を解析し、それを組み立てるシェルスクリプトを書いていこうと思います。

それでは、また今度。

コメント

タイトルとURLをコピーしました