概要
Linuxサーバの構築時に非常に面倒で厄介な「動いているサービス(systemctl status hoge.service でenabledになっているもの)だけを取り出し、表に転記するという作業が
- 限りなく単調で
- とてもミスが多い
重箱の隅をつつくような作業を一発で解消するワンライナーの紹介です。
前提
RHEL系Linuxで動きます。(筆者環境Rocky Linux)
ワンライナー
※これは
sudo su
としてから入力した方が無難です。(パッケージによっては一般的権限では見られないため)
{ echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"; systemctl list-unit-files --type=service --no-legend | awk '{print $1}' | grep -v '@\.service$' | xargs -r systemctl show -p FragmentPath | sed 's/^FragmentPath=//' | grep '^/' | sort -u | xargs -I {} sh -c 'if rpm -qf "{}" >/dev/null 2>&1; then rpm -qf "{}" --qf "| %{NAME} | %{VERSION}-%{RELEASE} |\n"; else echo "| $(basename "{}") | (not owned by any package) |"; fi' | sort -u; }
スクリプトの解説
このコマンドは大きく分けて「ヘッダーの出力」「サービスファイルのパス特定」「パッケージ情報の取得と整形」の3つのフェーズで動いています。
1. 表のヘッダーを作成
{ echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"; ... }- 最初にMarkdown形式の表の1行目(項目名)と2行目(区切り線)を出力します。
- 全体を
{ }で囲むことで、ヘッダーと後の実行結果を一つの出力ストリームとしてまとめています。
2. サービス一覧からファイルパスを特定
ここからがメインのパイプラインです。
systemctl list-unit-files --type=service --no-legend- システム上の全サービスユニットを表示します。
--no-legendでヘッダー行を省きます。
- システム上の全サービスユニットを表示します。
awk '{print $1}'- 出力結果から1列目(サービス名)だけを抜き出します(例:
sshd.service)。
- 出力結果から1列目(サービス名)だけを抜き出します(例:
grep -v '@\.service$'- インスタンス化されたユニット(
user@.serviceなど)を除外します。これらは実ファイルが少し特殊なためです。
- インスタンス化されたユニット(
xargs -r systemctl show -p FragmentPath- 各サービスに対して、その定義ファイル(ユニットファイル)がディスク上のどこにあるか(
FragmentPath)を取得します。
- 各サービスに対して、その定義ファイル(ユニットファイル)がディスク上のどこにあるか(
sed 's/^FragmentPath=//'- 出力に含まれる
FragmentPath=という文字列を削除し、純粋なパス名だけにします。
- 出力に含まれる
grep '^/'- 空行や無効なパスを除外し、
/から始まる絶対パスのみを残します。
- 空行や無効なパスを除外し、
sort -u- 重複したパスを削除します。
3. RPMによるパッケージ照会と整形
特定されたファイルパスを一つずつ RPM データベースと照合します。
xargs -I {} sh -c '...'- 各パス(
{})に対して、シェルスクリプトを実行します。
- 各パス(
if rpm -qf "{}" >/dev/null 2>&1; then ...- そのファイルが RPM パッケージによって管理されているかを判定します。
- 管理されている場合:
rpm -qfを使い、パッケージ名とバージョンを Markdown の行形式| 名前 | バージョン |で出力します。 - 管理されていない場合: (手動で作成したサービスなど)「(not owned by any package)」と出力します。
sort -u(最後)- 最終的なリストをソートし、重複を排除して綺麗に並べます。
実行結果のイメージ
コマンドを実行すると、以下のような結果が得られます。
| ソフトウェア名 | バージョン |
|---|---|
| chrony | 4.1-3.el9 |
| openssh-server | 8.7p1-10.el9 |
| my-custom-script.service | (not owned by any package) |
| systemd | 250-6.el9 |
このワンライナーの利点
- 徹底した合理性。
- なにせ「目視確認」という手段が排除されます。一切のヒューマンエラーがなくなります。
- 一覧性と整合性。
- 「最新の情報を全て表示せよ」という要求に対してもコマンドを叩くだけで済みます。
- 再利用性
- これが一番大きいです。2020年代ITで最も使いやすいMarkdownのテーブル形式。そのため、
- Redmine
- VS Code
- Notion
などにいくらでも反映可能。そして、一度テーブルにしてしまえばExcelへの転記も一発です。
- これが一番大きいです。2020年代ITで最も使いやすいMarkdownのテーブル形式。そのため、
まとめ
「ウサギとカメの寓話」は、コツコツやる者が強いパターンではありますが、「60分の道を頑張って55分に縮めるよりも、30分かけて『1分で終わる手段を考える』」方が、本当の「タイムパフォーマンス」だと思った次第です。
備考
Ubuntuサーバでも、このようにすれば動きます。
{
echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"
# 1. サービスファイルのパス一覧を取得
systemctl list-unit-files --type=service --no-legend | \
awk '{print $1}' | \
grep -v '@\.service$' | \
xargs -r systemctl show -p FragmentPath | \
sed 's/^FragmentPath=//' | \
grep '^/' | \
sort -u | \
# 2. 1行ずつ読み込んで処理(whileループでシェル起動を最小化)
while read -r path; do
# dpkg-query -S は dpkg -S より高速で安定しています
res=$(dpkg-query -S "$path" 2>/dev/null)
if [ $? -eq 0 ]; then
pkg=$(echo "$res" | cut -d: -f1)
# 複数のパッケージがヒットする場合があるため、最初の1つを取得して詳細表示
dpkg-query -W -f="| \${Package} | \${Version} |\n" "${pkg%%,*}"
else
echo "| $(basename "$path") | (not owned by any package) |"
fi
done | sort -u
}





















