0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第4回:GitHubのMarkdown内のプロフィールバッジを自動更新

0
Last updated at Posted at 2026-07-28

【GitHub Actions】プロフィールバッジのリンク変更時、全ファイル手動修正が面倒…タグで囲むだけで一括自動更新する仕組みを作った

この記事でわかること

  • <!-- PROFILE_BADGE_START --><!-- PROFILE_BADGE_END --> タグで囲むだけで一括自動更新
  • push時は変更された.mdのみ対象で高速化、workflow_dispatchで全ファイル一括更新
  • awk を使ったタグ間の精密な置換処理の実装方法
  • 前回(第3回)の自動挿入と組み合わせた完全自動化の運用設計

はじめに:この悩み、ありませんか?

前回(第3回)作成した「プロフィールバッジ自動挿入」の仕組みを導入して、新規Markdown作成時の手間は省けました。しかし、運用を続けると新たな悩みが出てきました。

「QiitaやZennのユーザー名を変更したら、全リポジトリのバッジリンクを一つずつ修正するのが地獄…」

「バッジのデザイン(色やアイコン)を変えたいのに、10個のREADMEを手動で書き換えるのが大変…」

「新しいSNS(Blueskyなど)のバッジを追加したいけど、過去の全ドキュメントに遡るのが現実的じゃない…」

挿入は自動化できても、更新が手動だと結局メンテナンスコストが残ります。 そこで、タグで囲まれたバッジ部分を自動的に一括置換する仕組みを作りました。

本記事では、そのワークフローを完全公開します。


なぜバッジの一括更新が必要なのか

プロフィールバッジは静的に見えますが、実際には頻繁に変更が必要になります。

変更シナリオ 影響
SNSアカウント変更 GitHub・Qiita・Zenn等のURL変更時、全ファイル修正が必要
バッジデザイン変更 色・ロゴ・スタイル変更時、全ファイルで一斉更新が必要
新規SNS追加 Bluesky・Mastodon等への展開時、過去ファイルにも追加したい
リンク切れ修正 サービス側のURL構造変更への対応

1ファイルなら手動でも済みますが、複数リポジトリ・数十ファイルとなると現実的ではありません。 そこで、タグを基準にした自動一括更新を実現します。

<!-- PROFILE_BADGE_START -->
(古いバッジ)
<!-- PROFILE_BADGE_END -->
        │
        │ GitHub Actions(自動)
        ▼
<!-- PROFILE_BADGE_START -->
(最新バッジ)
<!-- PROFILE_BADGE_END -->

このWorkflowで実現できること

  • 🔄 タグ間の自動置換 — START/ENDタグで囲まれた部分を完全に置き換え
  • 差分処理で高速化 — push時は変更された.mdのみを対象
  • 🎯 一括更新モード — workflow_dispatchで全ファイルを一斉更新
  • 🛡️ 無限ループ防止[skip ci]付きCommitでCI再発火を防ぐ
  • 📝 タグ外はそのまま — Markdown本文への影響を完全に排除

システム構成(処理の流れ)

Markdownファイルを Push
        │
        ▼
GitHub Actions 起動
        │
        ├─ push時:変更された .md のみ検出
        └─ workflow_dispatch時:全 .md を対象
        │
        ▼
PROFILE_BADGE_START/END タグを含むファイルを抽出
        │
        ▼
各ファイルでタグ間の内容を新しいバッジに置換
        │
        ▼
変更があれば自動 Commit & Push ([skip ci])

開発者がやることは「workflowファイル内のバッジ定義を更新してPushする」だけ。
全Markdownファイルのバッジが自動的に最新化されます。


事前準備

このWorkflowは 第3回の自動挿入Workflowと組み合わせることを前提に設計しています。

  • 第3回でバッジが <!-- PROFILE_BADGE_START --><!-- PROFILE_BADGE_END --> で囲まれている必要があります
  • 新規プロジェクトでは、第3回→第4回の順で導入するとスムーズです

Workflowファイルは以下に配置します。

.github/workflows/update-profile-badge.yaml

ワークフロー全文

name: Update Profile Badges in Markdown

on:
  push:
    paths:
      - '.github/workflows/update-profile-badge.yaml'
      - '**.md'
  workflow_dispatch:

jobs:
  update-badges:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Detect target Markdown files
        id: detect
        run: |
          if [ "${{ github.event_name }}" = "push" ]; then
            changed_files=$(git diff --name-only HEAD~1 HEAD -- '*.md')
          else
            changed_files=$(find . -name '*.md' -not -path './.git/*')
          fi

          # PROFILE_BADGE_START を含むファイルのみ抽出
          target_files=""
          while IFS= read -r file; do
            [ -z "$file" ] && continue
            if grep -q '<!-- PROFILE_BADGE_START -->' "$file"; then
              target_files="${target_files}${file}"$'\n'
            fi
          done <<< "$changed_files"

          if [ -z "$target_files" ]; then
            echo "No target Markdown files found."
            echo "files=" >> $GITHUB_OUTPUT
            exit 0
          fi

          echo "files<<EOF" >> $GITHUB_OUTPUT
          echo "$target_files" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Generate new badge content
        run: |
          cat > /tmp/new_badge.txt << 'BADGE_EOF'
<!-- PROFILE_BADGE_START -->

[![GitHub](https://img.shields.io/badge/GitHub-Profile-181717?logo=github)](https://github.com/xxxxxxxx)
[![Qiita](https://img.shields.io/badge/Qiita-Profile-55C500?logo=qiita&logoColor=white)](https://qiita.com/xxxxxxxx)
[![Zenn](https://img.shields.io/badge/Zenn-Profile-3EA8FF?logo=zenn&logoColor=white)](https://zenn.dev/xxxxxxxx)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-Profile-0A66C2?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/xxxxxxxx)

<!-- PROFILE_BADGE_END -->
BADGE_EOF

      - name: Replace badge sections with awk
        if: steps.detect.outputs.files != ''
        run: |
          echo "${{ steps.detect.outputs.files }}" | while IFS= read -r file; do
            [ -z "$file" ] && continue

            awk '
              /<!-- PROFILE_BADGE_START -->/ {
                print
                while ((getline line < "/tmp/new_badge.txt") > 0) {
                  if (line !~ /<!-- PROFILE_BADGE_(START|END) -->/) {
                    print line
                  }
                }
                close("/tmp/new_badge.txt")
                skip = 1
                next
              }
              /<!-- PROFILE_BADGE_END -->/ {
                skip = 0
                print
                next
              }
              !skip { print }
            ' "$file" > "$file.tmp"

            mv "$file.tmp" "$file"
            echo "Updated: $file"
          done

      - name: Commit and push if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -A
          if git diff --cached --quiet; then
            echo "No changes to commit."
          else
            git commit -m "chore: update profile badges in Markdown files [skip ci]"
            git push
          fi

ワークフローのポイント解説

① 対象ファイルの絞り込み

if grep -q '<!-- PROFILE_BADGE_START -->' "$file"; then
  target_files="${target_files}${file}"$'\n'
fi

バッジタグを含むファイルのみを抽出。タグがないファイル(第3回未導入のファイル等)は無駄に処理されません。

② 差分処理による高速化

トリガー 対象ファイル 用途
push 変更された.mdのみ 日常運用(高速)
workflow_dispatch .md 一括更新・初期導入時

③ awkによる精密なタグ間置換

/<!-- PROFILE_BADGE_START -->/ {
  print                    # STARTタグはそのまま出力
  while ((getline line < "/tmp/new_badge.txt") > 0) {
    if (line !~ /<!-- PROFILE_BADGE_(START|END) -->/) {
      print line           # 新しいバッジ内容を挿入
    }
  }
  close("/tmp/new_badge.txt")
  skip = 1                 # 古いバッジ行をスキップ開始
  next
}
/<!-- PROFILE_BADGE_END -->/ {
  skip = 0                 # スキップ終了
  print                    # ENDタグはそのまま出力
  next
}
!skip { print }            # タグ外の行はそのまま出力
  • START/ENDタグ自体は保持 — 次回更新時のマーカーとして機能
  • タグ間の内容のみ完全置換 — 古いバッジが残らない
  • タグ外のMarkdown本文は一切変更なし — 安全

④ 無限ループ防止

git commit -m "chore: update profile badges [skip ci]"

[skip ci] を付与することで、バッジ更新後のCommitが新たなWorkflowを発火する無限ループを完全に防ぎます


実際に運用して気づいた2つの落とし穴

🚨 落とし穴1:タグの空白違いで置換されなかった

初期版では、<!-- PROFILE_BADGE_START --><!-- PROFILE_BADGE_START-->(空白なし)の違いでマッチしなくなるケースがありました。

対処:タグの正規表現を厳密化し、余分な空白にも対応するよう修正しました。

# 改善前(厳密マッチ)
/<!-- PROFILE_BADGE_START -->/

# 改善後(空白許容)
/<!--[[:space:]]*PROFILE_BADGE_START[[:space:]]*-->/

🚨 落とし穴2:新規バッジ定義に古いタグが含まれて重複した

/tmp/new_badge.txtPROFILE_BADGE_START/END が含まれていると、awk処理でタグが二重に出力される問題がありました。

対処:awk内で line !~ /<!-- PROFILE_BADGE_(START|END) -->/ として、新規バッジ定義からタグ行を除外するよう制御しました。


導入後の効果

項目 Before After
プロフィールURL変更時の修正 全ファイル手動修正(30分〜) Workflow定義変更だけで全ファイル反映(1分)
新規SNSバッジ追加 過去ファイルを手動で遡る(不可能に近い) 一括追加が可能
バッジデザイン変更 ファイル数分の手作業 一括更新
更新漏れリスク 頻発 物理的に発生しない
更新対象の誤判定 タグなしファイルも処理していた タグ含むファイルのみ精密抽出

前回(第3回)との連携設計

第3回(自動挿入)と第4回(自動更新)を組み合わせることで、完全自動化が実現します。

【新規ファイル】
Markdown作成 → Push → 第3回Workflow → バッジ自動挿入
                                                    │
【既存ファイル】                                    ▼
Workflow定義変更 → Push → 第4回Workflow → 全ファイルバッジ自動更新

運用フロー

  1. 新規Markdownは第3回で自動的にバッジ挿入
  2. プロフィール変更時は第4回のWorkflowファイル内のバッジ定義を修正してPush
  3. 全ファイルのバッジが自動的に最新化

応用アイデア

この「タグ間自動置換」の考え方は、以下にも応用できます。

アイデア 内容
CIステータスバッジ一括更新 リポジトリ名変更時に全READMEのCIバッジURLを一括更新
API連携ダッシュボード 外部APIから取得した情報をタグ間に自動埋め込み
Zenn/Qiita記事一覧自動生成 最新記事リストをタグ間に自動反映
GitHub Stars数自動反映 https://img.shields.io/github/stars/... を動的に更新
ライセンスバッジ一括変更 ライセンス変更時に全ファイルのバッジを一括更新
バージョン表記の自動更新 リリース時にREADME内のバージョン表記を一括更新

今後の展開

このWorkflowは、GitHub Actions活用シリーズの第4弾です。

第3回と第4回を組み合わせることで、「新規挿入+既存更新」の完全自動化が実現できます。複数リポジトリ・多数ドキュメントを運用している方には特にオススメです。


まとめ

項目 内容
解決した課題 プロフィールバッジの一括更新手間・更新漏れ・手動修正の面倒さ
キーワード GitHub Actions, Markdown自動更新, awk, 一括置換, タグ間置換, CI/CD
得られる効果 メンテナンスコストゼロ、全ファイル同期、SNS変更時の即時対応

この仕組みを導入すれば、「プロフィール変更があっても、Workflow定義を1箇所修正するだけで全ファイルが自動更新される」 世界が実現します。複数リポジトリを運用している方は、ぜひ第3回と合わせて導入してみてください。

動作報告や改善案があれば、コメントやGitHub Issueでお知らせください!


ソースコード

今回ご紹介したGitHub Actions Workflowのソースコードは、GitHubでも公開しています。

以下のリポジトリから、Workflowファイルや設定内容を確認できます。

本記事がお役に立ちましたら、いいね❤️GitHub Star⭐ をいただけると励みになります!IssueやPull Requestも歓迎していますので、改善案や機能追加のアイデアがありましたら、お気軽にご連絡ください。


📢 掲載通知を受け取る
著者をフォローして次回または新掲載の通知をお待ちください!


0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?