シェルスクリプトでZennの投稿をGitHubプロフィールに自動反映してみる
やりたいこと
- GitHubプロフィールにZennの投稿最新5件を表示したい
- それをよしなに自動で反映できるようにしたい
モチベ
mikkameさんのこれと
ikawahaさんのこれ
をみて、シェルスクリプトでできるんでは!?と思いついたからです。
かっこいいですよね、こういうのさっとできると・・・
成果物
よかったらスターください!!!
手順
- GitHub Actionsでcron動かし、シェルスクリプトで定期的にZennのRSSフィードを取得
- README.mdに上書きして、前回と差分があればコミットしてmainブランチにプッシュする
これだけです。
GitHub Actionsでシェルスクリプトを定期実行する
まず、GitHub Actionsで定期実行をする部分です。
ポイントは、以下。
- トリガーとして
cronを使う - テストやデバッグしやすいように手動で実行できるよう
workflow_dispatchもつけておく - シェルスクリプトをactions内で実行できるよう、
chmodで実行権限を付与する - シェルスクリプトへのパスを
${GITHUB_WORKSPACE}を使って表現する(使わなくてもOKだと思う) - スクリプト実行後に
README.mdを直前のコミットと比較して差分があればコミットしてプッシュ
name: Update README
# 1. トリガーとして`cron`を使う
on:
schedule:
- cron: '0 * * * *' # 1時間ごとに実行
# 2. テストやデバッグしやすいように手動で実行できるよう`workflow_dispatch`もつけておく
workflow_dispatch:
jobs:
execute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: update zenn rss feed
# 3. シェルスクリプトをactions内で実行できるよう、`chmod`で実行権限を付与する
# 4. シェルスクリプトへのパスを`${GITHUB_WORKSPACE}`を使って表現する(使わなくてもOKだと思う)
run: |
chmod +x "${GITHUB_WORKSPACE}/rss-feed.sh"
"${GITHUB_WORKSPACE}/rss-feed.sh"
- name: git commit
run: |
git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git config --local user.name "captain-blue210"
git add README.md
# 5. スクリプト実行後に`README.md`を直前のコミットと比較して差分があればコミットしてプッシュ
git diff --cached --quiet || git commit -m "Update Zenn RSS feed"; git push origin main
いくつか初めて知ったこともあるのでかいつまんで書いておきます。
- name: update zenn rss feed
run: |
chmod +x "${GITHUB_WORKSPACE}/rss-feed.sh"
"${GITHUB_WORKSPACE}/rss-feed.sh"
${GITHUB_WORKSPACE}は、ワークフローでcheckoutアクションを使用している場合は、リポジトリのコピーと同じになります。
- name: git commit
run: |
git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git config --local user.name "captain-blue210"
git add README.md
git diff --cached --quiet || git commit -m "Update Zenn RSS feed"; git push origin main
git diff --cachedで直前のコミットとのdiffを取ることができ、さらに--quietをつけると、
- 差分あり ->
exit codeとして1を出力 - 差分なし ->
exit codeとして0を出力
することができるので、||を使ってexit codeが1だった場合(差分があった場合)にgit commitとgit pushを実行させます。
※||は終了コードが0以外の場合に、右側のコマンドを実施してくれます。
シェルスクリプト本体について
最後に処理本体であるスクリプトについて解説してみます。ただ、私のシェル芸力が足らずあんまりスマートなスクリプトではありません・・・
つよつよエンジニアの方、こう書くといいよ!というのがあればコメントいただけると泣いて喜びます😂
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
RESULT=$(
curl https://zenn.dev/captain_blue/feed 2>/dev/null |
grep -oE '(<title>.*?</title>|<link>.*?</link>)' |
sed 1,4d |
awk '{
if(NR%2){
line=$0
}else{
print line $0;
line=""
}
}END{
if(line)
print line
}' |
sed -e "s/<title><\!\[CDATA\[\(.*\)\]\]><\/title><link>\(.*\)<\/link>/- [\1](\2)/" |
sed -n 1,5p
)
sed -i -z 's|<\!-- LATEST_ARTICLES_START -->.*<!-- LATEST_ARTICLES_END -->|<!-- LATEST_ARTICLES_START -->|g' ./README.md
echo "$RESULT" | while read -r line; do
echo "$line" >>./README.md
done
echo '<!-- LATEST_ARTICLES_END -->' >>./README.md
curl https://zenn.dev/captain_blue/feed 2>/dev/null |
grep -oE '(<title>.*?</title>|<link>.*?</link>)' |
sed 1,4d |
まず、curlコマンドを使ってZennのRSSを叩きます。(エラーは/dev/nullにポイしてます)。
次にgrepでtitleとlinkタグに一致した部分だけ抽出して、1~4行目は不要なので削除しています。
現時点だと以下のようにtitleとlinkが改行されてしまっています。
~省略~
<title><![CDATA[FlutterのSafeAreaを使って上下のあいつらを気にしない方法]]></title>
<link>https://zenn.dev/captain_blue/articles/introduce-sarearea-in-flutter</link>
~省略~
これを次の処理でマークダウンの形に整形できるように、titleとlinkを一行にしていきます。
<title><![CDATA[FlutterのSafeAreaを使って上下のあいつらを気にしない方法]]></title><link>https://zenn.dev/captain_blue/articles/introduce-sarearea-in-flutter</link>
そこでawkコマンドを使って一行にしています。
awk '{
if(NR%2){
line=$0
}else{
print line $0;
line=""
}
}END{
if(line)
print line
}' |
NR%2は、現在の行番号が奇数かどうかをチェックしています。省略しないて書くとNR%2==1ですが、awkでは1は真となるみたいで省略しても大丈夫なようです。
奇数だった場合は悪書としてpという変数に$0、現在処理している行全体を代入します。
※奇数行はすべてtitleなので、title行全体ということになりますね。
偶数だった場合は上記で代入しておいたtitleの行とlinkの行をprintを使って連結して出力し、pを空文字にします。
printでは改行は出力されないため、これでtitleとlinkを1行にすることができました。
ENDでは、最終行を読み込んだあとに実行されpに値が入っていればそれを出力します。
次に、sedコマンドと正規表現を組み合わせて、awkで一行にしたtitleとlinkから値を抜き出し、マークダウンの形に整形します。
sed -e "s/<title><\!\[CDATA\[\(.*\)\]\]><\/title><link>\(.*\)<\/link>/- [\1](\2)/" |
ex.
- [FlutterのSafeAreaを使って上下のあいつらを気にしない方法](https://zenn.dev/captain_blue/articles/introduce-sarearea-in-flutter)
取得して整形した記事データの1~5行目を取得しています。
sed -n 1,5p
そして、ここまでで取得->整形したデータをREADME.mdに書き込む処理が以下になります。
sed -i -z 's|<\!-- LATEST_ARTICLES_START -->.*<!-- LATEST_ARTICLES_END -->|<!-- LATEST_ARTICLES_START -->|g' ./README.md
echo "$RESULT" | while read -r line; do
echo "$line" >>./README.md
done
echo '<!-- LATEST_ARTICLES_END -->' >>./README.md
まず、下準備としてREADME.mdに書き込む目印となるコメントをつけておきます。
<!-- LATEST_ARTICLES_START -->と<!-- LATEST_ARTICLES_END -->の間に記事一覧を追記します。
<!-- 省略 -->
## Latest 5 articles
<!-- LATEST_ARTICLES_START -->
<!-- LATEST_ARTICLES_END -->
最初にsedコマンドでコメントの間と<!-- LATEST_ARTICLES_END -->を消してしまいます。
sed -i -z 's|<\!-- LATEST_ARTICLES_START -->.*<!-- LATEST_ARTICLES_END -->|<!-- LATEST_ARTICLES_START -->|g' ./README.md
そのあと、whileで取得したデータを一行ずつ追記していきます。
echo "$RESULT" | while read -r line; do
echo "$line" >>./README.md
done
最後に<!-- LATEST_ARTICLES_END -->を再度ファイルに追記して完了です。
※ 次回以降に記事一覧を全消しするときに必要。
echo '<!-- LATEST_ARTICLES_END -->' >>./README.md
ハマった点と対応策
ちょっとしたファイル編集に便利なsedコマンドですが、sed -e "s/置換前/置換後/で置換するとき今回のように置換後の値に改行が含まれているとうまく認識されず、sedのシンタックスエラーとなってしまい一括で置換ができず苦労しました。
置換前として複数行に含まれる値を対象にする、であれば-zオプションで可能なのですが、置換後の文字列が複数行に渡る場合はうまくいきません。
最終的に、一括で置換するのを諦めて一行ずつファイルに追記する形に方向転換することでやりたいことを達成できました💪
echo "$RESULT" | while read -r line; do
echo "$line" >>./README.md
done
あと、地味にtitleとlinkを連結して一行にする部分もハマりました。最初sedでやろうとしてうまくいかずawkに変更してif文を使うことで実現しました。
課題・・・
1点だけまだ対応できてない部分があって、GitHub Actions上からZennのRSSフィードを取得すると最新順になっていないようで、ちょっと古い5件が表示されてしまっています・・・
スクリプトでデータを取得したあとに、公開日でソートした上で出力すればよさげなんですがまだ未対応です🙄
気が向いたら対応してこの記事も更新しようと思います!

Discussion
読ませていただきました!
本記事を参考にさせていただき、自分も再現できました!