シェルスクリプトで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
読ませていただきました!
本記事を参考にさせていただき、自分も再現できました!