コードリーディング(Bash):システム情報表示ツール”neofetch”

前書き

本記事は、システム情報表示ツール”neofetch“のコードリーディング結果を記載しています。
リーディング動機は、「neofetchは、システム情報をどこから集めているか」が気になったからです。neofetchは約2600Step程度の小規模なBash scriptであるため、関数レベルで解説します。

 

POINT

基本的に、neofetchはゴリ押しな作り

                               

neofetchとは

項目 説明
機能 システム情報(OS/Kernel/CPU/Memoryなど)をOSロゴと共に表示する。
Version 5.0.0(2018年9月時点。本記事ではDebian9 package版であるVer2.0.2を解説)
開発者 Dylan Araps
ライセンス MIT
Download https://github.com/dylanaraps/neofetch
紹介記事(日本語) システム情報を表示する便利なコマンド「neofetch

        

neofetchのコード規模

本記事で確認するneofetch(Ver2.0.2)のコード規模は、以下の通り(clocコマンドを使用)。

Language ファイル数 空白 コメント コード行数
Bourne Again Shell 3 718 773 2625
Markdown 5 61 0 83
make 2 6 0 22
YAML 1 3 0 10
合計 11 788 773 2740

       

インストール方法およびソースコード取得方法

以下の手順は、Debian9を前提としています。別環境の導入手順は、別サイトを参照してください(Debian系/RHEL系/Macに関して、記載があります)。

    

neofetchの実装に関する説明範囲

説明範囲は、neofetchのmain関数内に存在する関数(例:cache_uname)とします。

      

ヘッダ:Shebangおよびデバッグ用の記述

まず、1行目のShebang(#!)は、Shebangの後に記述されているコマンドを実行するという意味です。今回は#!/usr/bin/env bashであるため、環境変数(PATH)に存在するbashを実行します。この記述の場合、bashが”/bin/bash”に存在しない環境でも、Scriptが動作する可能性が高いです。

1行目を”#!/bin/sh”と記述している場合、shは他のshellへのシンボリックリンクである事が殆どなので、dashやfishを実行する可能性があります。その場合、Script内のBash固有の文法が実行できず、エラーになります。

次に、2行目のコメントset -xは、デバッグ情報を出力します(なので、デバッグ時はコメントを外す)。具体的には、Script内で処理された内容(変数代入、コマンド実行、関数実行など)を表示します。同じ動作をさせるために、bash -xというオプションがありますが、#!/usr/bin/env bash -xという記載はできません。この記載の場合、インタプリタに引数を渡せないからです。

ちなみに、デバッグを有効化する(2行目のコメントを外す)と、以下のように出力されます。

cache_uname():OSのKenel/Arch情報を取得

cache_uname()は、unameコマンドを用いて、Linux KernelバージョンとCPU Architectureを取得します。以下が実装の全体です。

unameコマンドのオプションは、
 ・s:カーネル名の表示
 ・r:カーネルリリースバージョンの表示
 ・m:PCのハードウェア名を表示
であるため、私の環境では以下の情報が出力されます。

後は、配列変数unameから順番に要素を取り出して、別変数で保持しているだけです。

     

get_os():KernelからOS名の決定

前述のcache_uname()で取得したカーネル情報から、OS名を決定します。以下が実装の全体です。基本的には、文字列分岐なので、実装で説明する部分はないです。

DarwinはOS XかiOSの事であり、sw_vers -productNameを実行すると”Mac OS X”/”iPhone OS”と表示されます。CYGWINは、Windows環境で仕方なくUNIXっぽい事をする際に用います。最新版のneofetchでは、MINIX/AIX/IRIX/FreeMiNTも判別します。

私がneofetchの実装を見て存在を認識したOSは、Haiku/AIX/IRIXあたり。

                          

       

get_default_config():neofetchの設定ファイルの読込

neofetchのconfigファイルを探し、その内容をsourceコマンドで読み込みます。このconfigファイルは、システム情報の出力順番などを実装したBash scriptです。公式情報では、このファイルをベースに、ユーザが出力形式をカスタマイズする事を目的としているようです。

このconfigファイルは、インストール方法によって、格納場所が異なります。私の環境では、”/usr/share/neofetch/config”に存在しました(aptパッケージマネージャでのインストール)。

       

get_args():引数の取得

まず、neofetchの書式は、以下の通りです。ロングオプション(–option)を書き、その後にオプション引数を書く形式です。各オプションの説明は、総数が60個以上あったため、省略します。

基本的には、オプションを有効にした場合、出力される情報が細かくなります。
なお、各オプションのデフォルト設定値は、デフォルトconfigファイルに記載されています。

get_args()の実装(抜粋)は、以下の通りです。
処理内容は、
 1. カスタムしたconfigファイルを使用するかを判定(最初のcase文からwhile文の前まで)
 2. オプション解析(while文の中身)

        

1. カスタムしたconfigファイルを使用するかを判定(最初のcase文からwhile文の前まで)

まず、最初のcase文において、関数に渡した全引数($@)をチェックします。本来の意図では、”–config none”のみがconfig無効化を意味しますが、”–config off”や”–config”などの誤った記載を認めるため、このようなcase文になっています。

前述のget_default_config()にて、configファイルを読み込んでいます。その場合、変数config=onであるため(configファイルで代入されているため)、[[ "${config:-on}" == "on" ]] && get_user_config 2>/dev/nullにおいて、関数get_user_configが実行されます。

なお、左辺値(${config:-on})の文字抽出が冗長に思えますが、意図が分からず。

関数get_user_config(本記事に未掲載)はデフォルトconfigファイルを再び読み込んだ後、”${HOME}/.config/neofetch”以下にコピーします。このタイミングでしたい事は、本当はコピーだけです。しかし、次のオプション解析内でユーザ指定configを読み込む際、同関数を使いまわしたいようです。そのため、無駄ですが、デフォルトconfigファイルを2回読み込んでいるようです。 

                                                       

2. オプション解析(while文の中身)

1番目の引数が空文字でない限りループを繰り返し、case文でロングオプション毎に処理を分岐させます。分岐後は、オプション引数を別変数に格納するのがメインです(例:os_arch=”$2″の部分)。ループの最後で、shiftコマンドを用いて、引数を1つずらします。

         

old_flags():deprecatedしたオプション使用時の警告

既に使用していないオプション設定(フラグ変数)に値が入っている場合、警告を出力します。この関数は、読み込むconfigファイルが古い場合に備えて、存在します。(オプション設定変数の一部は、configファイルに存在)。

get_distro():ディストリビューション名およびVersionの取得

ディストリビューション(Version)を特定するために、
 ・ 各ディストリビューション固有の設定ファイルを確認
 ・ コマンド実行
のいずれかを行います。

確認するファイル・実行コマンドは、OS(※)によって異なります。
※OS = Linux(GNU)、Mac OS X、iOS、BSD、Windows、Solaris、Haiku

各ディストリビューションによって確認するファイル、実行するコマンドは下表の通りです。

ディストリビューション ファイル・コマンド
Microsoft(WSL) /proc/version
/proc/sys/kernel/osrelease
lsb_releaseコマンドでディストリ特定
Gobo Linux /etc/GoboLinuxVersion
Red Star OS /etc/redstar-release
LSB準拠Linux OS lsb_releaseコマンドの実行
GuixSD guixパッケージマネージャの存在確認
CRUX cruxコマンドの存在確認
Andloid /system/app/ディレクトリ
/system/priv-appディレクトリ
その他のLinuxディストリビューション /etc/os-release
/usr/lib/os-release
/etc/release
/usr/lib/
release
Mac OS X sw_versコマンドの実行
iPhone OS sw_versコマンドの実行
Windows wmicコマンドの実行
Solaris /etc/release
Haiku “uname -sv”の実行

上の表の方法で特定できなかった場合は、Unknownとなります。また、os_archオプションが有効の場合、ディストリビューション名にCPU architectureを付与します。最後の変数ascii_distroは、各ディストリビューションのアスキーアートを取得する際に使用します(後述)。

       

get_bold:太字用のANSIエスケープコードを取得

コード中の”\033″はESCと同等で、太字指定(“ESC[1m”)となります。

      

get_distro_colors():ディストリビューション毎の表示色を取得

ディストリビューション毎に、色設定が決め打ちされており、ターミナルに表示する文字色変更はset_colors()内のcolorコマンドで行います。

         

get_image_backend():背景画像表示に関する設定

ここでの処理は、w3m-imgおよびImageMagickパッケージの導入が前提となっています。
w3m-imgは、ターミナル上に画像を表示するコマンドであり、w3m(Terminalブラウザ)の派生物です。ImageMagickは、画像表示や編集用のソフトウェアです。

余談ですが、Terminalに画像を表示させるには、X Window SystemのXlibなどを使用します。
私は、Low Level X Window Programming: An Introduction by Examples(書籍)が欲しいけど、買えてない……

コード全体を示すと長いため、処理の概要だけを以下に示します。
 1. w3mimgdisplayコマンド(画像表示コマンド)の存在確認
 2. ImageMagickが提供するconvertコマンド(画像変換コマンド)の存在確認
 3. デスクトップ環境(例:Cinnamon/MATE)に応じた背景画像(PATH)を取得
 4. Terminalに画像を表示する際に使用するコマンドの決定(iterm2/tycat/w3mのいずれか)
 5. 現在のTerminalの縦横を取得
 6. 画像の縦横を取得
 7. 画像のサムネイルをconvertコマンドで作成

       

old_functions():古いconfigファイル上に実装された関数と互換性を取る

neofetchは、一部の関数実装をconfigファイルに分離しています。そのため、古いconfigファイルを読み込んでいる場合、deprecatedな関数が存在する可能性があります。そこで、deprecatedな関数を新しい関数名でWrappingする事により、互換性を保っています。

      

get_cache_dir():キャッシュディレクトリの決定

Macか、それ以外のOSでキャッシュディレクトリを変更します。

         

print_info():システム情報の表示

システム情報を表示します。print_info()はconfigファイルに実装されており、print_info()内のinfo()はneofetch本体に実装があります。info()で重要な部分は、"get_${2:-$1}" 2>/dev/nullであり、get_XXX関数を用いて各システム情報(文字列)を取得しています。Mac OSのGPU情報のみ、cache()関数でキャッシュディレクトリに格納します(頻繁にキャッシュ化しない理由は不明)。

システム情報の取得方法(一部)を下表に示します。

システム情報 参照ファイル・コマンド
OS 前述のディストリビューション取得までの流れを参照
Model [Linux系]
getpropコマンド
/sys/devices/virtual/dmi/id/product_name
/sys/devices/virtual/dmi/id/product_version
/sys/firmware/devicetree/base/model
/tmp/sysinfo/model

[Apple/BSD]
sysctlコマンド

[Windows]
wmicコマンド

[Solaris]
prtconfコマンド
Kernel unameコマンド
Uptime [Haiku]
uptimeコマンド

[Linux/Windows/GNU]
/proc/uptime

[Apple/BSD]
sysctlコマンド

[Solaris]kstatコマンド
Packages 各ディストリビューションのパッケージマネージャ
Shell 環境変数$SHELL
Resolution [Linux/BSD/Solaris/GNU]
xrandrコマンド
xdpyinfoコマンド

[Mac OS X]
screenresolutionコマンド
system_profilerコマンド

[Windows]
wmicコマンド

[Haiku]screenmodeコマンド
DE [Windows/Mac]
決め打ち(スクリプト内にハードコーディング)

[Linux]
環境変数$XDG_CURRENT_DESKTOP
WM [Mac]
決め打ち(スクリプト内にハードコーディング)

[Windows]
tasklistコマンド

[その他]
xpropコマンド

                       

                 

ディストリビューションアスキーアートの格納先

私の環境では、/usr/share/neofetch/ascii/distro以下に存在します。
知らないディストリビューションが沢山……Kali Linux環境が欲しい。

       

最後に

読み終わった印象は、

  • 予想以上に力技
  • Bash以外で実装した方が楽そう(色んな環境で動作させるには、Bashが適切だった?)
  • おおよそ実装を理解したので、何らかの形でContributeしたい

ちなみに、公式サイトの出力で表示されている画像の作者は、Kuvshinov Ilya(twitter)。中国・韓国の人が書く絵に似ていると思いましたが、まさかのロシア人。私はこの絵の雰囲気が好き。

       

おすすめ

1件の返信

  1. 2022年2月5日

    […] osinfoライブラリは、前述のMimixBoxにneofetch(golang実装)を組み込むために開発を始めました。neofetchのコード自体は一度読んだ事があったので「すぐに実装できるだろう」と安易に考えていました。 […]