Diary

Diary

日々学んだことをアウトプットする場として初めてみました

mac と Linux の違い [sed command]

手元の mac で動作確認をし、リモートマシン(GitHub Actions)で実行したところ、上手くいかないことがありました。

過去にも同じこと調べたな〜と思ったので、メモしておきます。

やりたいこと

ファイルの中の

<link href="https://your.domain/style.css?20220720-1408">

という文字列から、style.css?20220720-1408を削除したい。

要点

  • 正規表現による置換(削除)
  • ファイルの書き換え

ファイル書き換え時のオプションの違い

sed では -i オプションをつけることで in-place でファイルを置き換えることが可能となってます。

Mac の場合は別途 -e オプションが必要です。

# Linux
sed -i -E "s@style.css\?[0-9]*-[0-9]*@@g" test_file

# For Mac (BSD)
## -e が必要
sed -i -e "s@style.css\?[0-9]*-[0-9]*@@g" test_file

正規表現の違い

Mac ではオプションなしで基本正規表現は使えそうですが、Linux正規表現を使うには -r正規表現を使う)や -E拡張正規表現を使う)のオプションが必要そうです。

# Linux
## -r や -E などをつける必要がある
sed -i -r "s@style.css\?[0-9]*-[0-9]*@@g" test_file
sed -i -E "s@style.css\?[0-9]*-[0-9]*@@g" test_file

# For Mac (BSD)
## 追加オプションは不要
sed -i -e "s@style.css\?[0-9]*-[0-9]*@@g" test_file

おわりに

BSD ベース(Mac)と GNU ベース(Linux)のコマンドの違いをこれからは意識していきたいです。

それはそうと、移植性の高いスクリプトを書けるようになります。

HTML の更新を検知する action を作りました [GitHub Actions]

URL を監視し、HTML の内容に更新を検知する Github action を作成しました。Githubリポジトリに最新の情報を保存し、実行時にはそのファイルと比較を行うことで更新を検知する仕組みです。

Marketplace にも公開してますので、よろしければご覧ください(Github へのリンク)。

[目次]

なにができるか

もう少し具体的に何ができるか説明します。

  • 監視する URL の指定
    • curl で引っ張ってきてます
  • 監視対象から除外するパターン
  • ファイルの保存先フォルダ名の指定
  • ファイル名の指定

『監視対象から除外するパターン』を作成した経緯

静的ファイルなどで古い情報を読み込ませないようにするため、クエリパラメータにアクセス時の日時を入れるような設計があるようです。

具体的には以下のような URL を観測しました。

<link href="https://example.com/style.css?20220720-1408" />

ここの style.css? 以下の文字列がアクセス日時毎に異なるため、毎回差分検知してしまい意図した挙動になりません。そこで、sed を用いることで無理やり対象ファイルから削除をおこなっています。

補足説明

  • Githubリポジトリに最新の情報を保存する』のは bot に行わせてます
  • 更新検知後に各 SNS 等で通知を行えるよう、output としてフラグを出力しています

どのように作ったか

Github actions にカスタムアクションを作成する方法として、次の3 つがあります

  • Docker
  • JavaScript
  • Compose(いくつかの workflow の step を組み合わせて作成)

今回は既に他のワークフローの中に骨組みを作成済みだったことから、Compose の方法で作成しました。

チュートリアルとしては公式のものに沿っていけばできます。

使用例

2 つ目の step の『Diff check』が今回作成したアクションで、そこでの結果により更新があれば slack への通知をおこなっています。

name: url_watcher

on:
  workflow_dispatch:
  schedule:
    # 日本時間23時00分に定期実行
    - cron: "0 14 * * *"

jobs:
  checker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Diff check
        id: diff-check
        uses: kokoichi206/action-URL-watcher@v0.1.1-alpha
        with:
          # 変更を監視したい URL
          url: https://example.com/
          # 変更監視から除外したい正規表現(; 区切りで複数指定可)
          excluded-patterns: 'style.min.css\?[0-9]*-[0-9]*;common.js\?[0-9]*-[0-9]*'
          save-dir: ./url_watcher
          save-file: index.txt

      - name: Notify if diff found
        if: ${{ steps.diff-check.outputs.diff == true }}
        run: |
          # Slack API を使って通知
          ## see: https://api.slack.com/methods/chat.postMessage
          curl -X POST 'https://slack.com/api/chat.postMessage' \
            -d "token=${{ secrets.SLACK_API_TOKEN_HACKATHON }}" \
            -d 'channel=#times_john_doe' \
            -d 'text=HPの更新を検知しました。'

おわりに

はじめて github action を marketplace に公開してみました。結構学ぶことが多かったので、作成手順も次回まとめてみようと思います。
また、今度は JavaScript や Docker を使った方法でも独自アクションを作成してみたいです。

IFS を変えてセミコロンで文字列を分割する

IFS とは

IFS とは Internal Filed Separator の略語であり、シェルの環境変数で設定されています。

現在の値確認

では実際に、現在設定されている IFS を確認してみます。

# ubuntu: 
# bash: version 5.0.17(1)-release (aarch64-unknown-linux-gnu) 
$ echo $IFS

# mac: 
# zsh 5.8.1 (x86_64-apple-darwin21.0)
$ echo $IFS

目視では何も確認できませんが、mac の方は余計な改行が入ってるようにも見えます。
そこで、次は 16 進数で表示させてみます。
なおここでは echo 標準の改行を無視するため -n オプションをつけてます。

# ubuntu: 
# bash: version 5.0.17(1)-release (aarch64-unknown-linux-gnu)
$ echo -n $IFS | od -ax
0000000

# mac: 
# zsh 5.8.1 (x86_64-apple-darwin21.0)
$ echo -n $IFS | od -ax
0000000   sp  ht  nl nul                                                
             0920    000a                                                
0000004

ubuntu の方は何も指定されておらず、mac の方は『スペース(sp, 20)』『水平タブ(ht, 09)』『改行(nl, 0a)』の 3 つが指定されていることが確認できました。

IFS を変えてセミコロンで文字列を分割する

目標
『複数の変数を 1 つの文字列として定義し、シェル内で再度配列として取得する。ただし、変数はスペースを含み得る。』

達成方法例
ここでは表題にもあるように、IFS を変更しリストとして読み取る方法を使います。
またシェルスクリプト内で使用する場合、続く動作に影響を与えないよう、実行前の値を保存しておき実行後に元に戻すのがベターです。

# 分割したい文字列(;区切り)
line="hoge;pien;paon"

# 実行前の IFS の値を一時避難
OLD_IFS=$IFS

IFS=";"
# 文字列を配列に変換
lines=($line)
for a in "${lines[@]}"; do
    echo "$a"
done

# 実行前の IFS の状態に戻す
IFS=$OLD_IFS

参考

おわりに

もっとスクリプト書けるようになりたいです。

IntelliJ IDEA での便利な設定

IntelliJ IDEA は JetBrains 社が開発する JVM 向け IDE です。
デフォルト状態でも十分便利な IntelliJ IDEA を、今回は自分用にアレンジしてみたいと思います。
チームのコーディングスタイル等に合わせて拡張してみてください。

以下で紹介する設定の Tips は、PyCharm などの JetBrains 社が開発する IDE, また Android Studio 等でも似たような設定があると思われます。

バージョン

- IntelliJ IDEA 2021.3.2 (Community Edition)

個人用設定

以下で設定名を検索するときは、『shift 2 回タップし表示されるウィンドウ』を使って検索しています。

最終行に改行を入れる(保存時)

「Ensure every saved file ends with a line break」を有効にする。

これで、「Ctrl + S」で保存をすると、自動的に最終行に改行が入るようになります。

保存時にフォーマットを適応

「actions on save」と入力。

「Reformat code」を有効にし、対象ファイルと対象範囲を選択してください。
他必要に応じて「Optimize imports」等にもチェックを入れましょう。

これで、「Ctrl + S」で保存をすると、自動的にフォーマットが走るようになります。kotlint, detekt 等の個人で設定したフォーマットを使用する方法は分かりません。

おわりに

全員が IntelliJ IDEA を使って開発する際には、今回紹介したような自動フォーマット等を上手に利用して、無駄なところで使う時間を減らしたいですね!

Jetpack Compose で縦方向のスタック(SwiftUI の ZStack)

Jetpack Compose を触っていて画像の上に画像を重ねたいケースが出てきました。
そこで今回は、SwiftUI での ZStack, CSS での z-index のようなものを実現する方法を紹介します。

実装例

Modifier に zIndex が用意されているので、そちらを使用します。
CSSと同様に値が大きいほど手前に表示されます。

Box, Column, Row, Image, など様々な Composable に適応可能でした。

@Composable
fun Map() {
    ...
    Column(
        modifier = Modifier
            .zIndex(1f)
    ) {
        ...
    }
    Image(
        painter = painterResource(id = R.drawable.player_2),
        contentDescription = null,
        modifier = Modifier
            .height(size)
            .width(size)
            .zIndex(2f)
    )
    Column(
        Modifier
            .padding(40.dp)
            .size(100.dp)
            .zIndex(3f),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Spacer(modifier = Modifier.weight(1f))
        Image(
            painter = painterResource(id = R.drawable.player_2),
            contentDescription = null,
            modifier = Modifier
                .zIndex(3f)
        )
        Spacer(modifier = Modifier.weight(1f))
    }
}

おわりに

zIndex のように数値で指定する系だと、他で書いた値を考慮する必要があります。
一方で、SwiftUI の ZStack は Column, Row のように Stack に積んでいくだけなのでより直感的でいいな、と思いました。

SwiftPM を使用中にテストコードで missing required module 'xxx'

Swift Package Manager(SwiftPM)を使ってパッケージ管理をしており、firebase 関連のインストールを行なっていました。

そんな中、swiftUI の UITest を記載しようとした時 missing required module 'FirebaseFirestore' のエラーに悩まされたので、その解決策についてメモしておきます。

Binary の Link 先に追加する

  1. Project トップのアイコンをクリックする
  2. TARGETS から UITest ターゲットを選択する
  3. Build Phase > Link Binary With Libraries を確認する
  4. エラーのでたライブラリがなければ追加する

f:id:kokoichi206:20220316204038p:plain

@testable でメインプロジェクトを import する

メインプロジェクトのコードを参照するには @testable をつけることが必要でした

import XCTest
@testable import MainProject

class MainProjectTests: XCTestCase {

}

おわりに

Android Studio に比べて、XCode はスムーズにいかない部分が多い気がします。。。

Bash スクリプトでよく使うテクニックまとめ

コマンド単位の使い方ではなく、作業効率化のためのスクリプトを書く際などに使えるテクニックをまとめてみました。

便利なものがあれば随時追加していきます。

[目次]

エラー対策

使用例

  • ほとんどのケースでつけておいて損がないエラー対策です

使用コマンド

  • set
    • Set Builtin
    • set -e
    • set -u
      • 未定義の変数を参照時にエラー終了する
    • set -o
      • シェルオプションを有効化
    • pipefail
      • シェルオプション
      • パイプで繋がれた中にエラーがあれば終了する
#!/bin/bash
set -euo pipefail

ファイル内容のコメント

個人的に、以下のようにファイルの目的・使用方法をファイルの先頭に書くようにしています。

#!/bin/bash
#
# Description
#    Update CURRENT_PROJECT_VERSION if needed
#
# Usage:
#    bash version_update.sh
#

オプション引数をパースする

使用コマンド

  • 「全ての引数」を表す特殊変数$@をループで処理しています

注意点

  • -n 2などのように『オプション + 値』を取るような場合は注意が必要です
# ======================
# parse arguments (options)
# ======================
for i in "$@"; do
    case $i in
    -h | --help | -help)
        usage_and_exit 0
        ;;
    -s | --slide)
        is_slide=true
        shift 1
        ;;
    -o | --output)
        if [[ -z "$2" ]]; then
            echo "option requires a file name -- $1"
            usage_and_exit 1
        fi
        OUTPUT_PATH="$2"
        shift 2
        ;;
    -*)
        echo "Unknown option $1"
        usage_and_exit 1
        ;;
    *)
        // 何も指定がなければファイル名として解釈したい場合
        if [[ -n "$1" ]] && [[ -f "$1" ]]; then
            FILE="$1"
            shift 1
        fi
        ;;
    esac
done

カスタマイズされたエラーメッセージ

使用例

  • 一部分だけ赤文字にしたエラーメッセージを表示します
  • 標準エラー出力にしています
# ===== print error =====
function print_error() {
    ERROR='\033[1;31m'
    NORMAL='\033[0m'
    echo -e "${ERROR}ERROR${NORMAL}: $1" >&2
}

# 使用例
if [ ! -f "$FILE" ]; then
    print_error "File $FILE doesn't exist"
fi

ファイル終了時に後処理を行う

使用例

  • 途中で処理が失敗したら、事前に作成しておいたバックアップファイルからの復元を行う

使用コマンド

  • trap

注意点

  • エラーが起こりうる行よりも前に trap を呼ぶ
// ファイル終了時に呼び出したい関数を定義する
function recover_from_backup() {
    EXIT_CODE="$?"
    // 終了コードが 0 より大きい場合、エラー終了したことを意味する
    if [[ "$EXIT_CODE" -gt 0 ]]; then
        echo "Something went wrong."
        echo "recovered from backup"
    else
        echo "Exit correctly"
    fi
}
# ERR is special feature to bash
trap recover_from_backup EXIT

helpを表示する

使用例

  • コマンドの使い方及び-hオプションを渡されたときの出力例です
PROGRAM="$(basename "$0")"

# ===== print usage =====
function print_usage() {
    echo "Usage: $PROGRAM [OPTION] FILE"
    echo "  -h, --help, -help"
    echo "      print manual"
    echo "  -o <filename>, --output <filename>"
    echo "      output filename"
}

help を表示して終了:ただし終了ステータスを変えたい

使用例

  • コマンドの使用方法(help)を表示 + 実行終了を行いたいが、終了コードを使い分けたい
    • --helpオプションに対しては 0(正常終了)
    • ファイルが存在しない等のエラーに対しては 1(異常終了)
usage_and_exit()
{
    print_usage
    exit "$1"
}

# オプションの -h に対する使用例
usage_and_exit 0

# エラー時の使用例
usage_and_exit 1

特定のファイルに対して1行ごとに処理を行う

while read -r line
do
    echo "$line"
done < "$FILE"

変数が正規表現に一致してるかチェック

注意点

  • [が2つの方法
  • =~で比較する
if [[ "$line" =~ ^\`\`\`.* ]]; then
    echo "Code block start (or end)"
fi

おわりに

スクリプトを書く際はテンプレに沿うケースが多いと思って、自分がよく使うものをまとめてみました。何か一つでも良いと思っていただけたら嬉しいです。