🔖

普段利用している自作コマンドを詳解

に公開

今回は、普段開発する時に利用している自作コマンドについて共有しようと思います。

Taskfileを伴うプロジェクトの初期化

まずはTaskfileを利用してプロジェクトを初期化するためのコマンドの紹介です。Taskfileをタスクランナーとして利用しており、長いコマンド文の省略などに利用しています。利用する言語ごとに大体使う定型分があるので、それをテンプレートとして用意して、コピーしつつ初期化するコマンドを実装しています。Taskfileについては以下で解説しています。

https://zenn.dev/akasan/articles/f4a13b2e62a637

uvのカスタマイズ

Pythonの環境構築はuvを利用しています。通常であればuv init ...のような形で初期化しますが、以下のようなコマンドを定義してuvで初期化しつつTaskfileも用意するようにしています。

function uv_new_with_taskfile() {
    if [[ $# -eq 0 ]]; then
        echo "Usage: uv_new_with_taskfile <project_name> [uv_init_options...]"
        return 1
    fi
    
    local project_name=$1
    shift
    uv init "$project_name" "$@"
    
    if [[ $? -eq 0 ]]; then
        cd "$project_name"
        uv add --dev ruff pytest mypy
        cd ..
        cp ./templates/Taskfile_uv.yml $project_name/Taskfile.yml
        echo "✅ Created Python project '$project_name' with Taskfile.yml"
    else
        echo "❌ Failed to create Python project"
        return 1
    fi
}

# Alias for convenience
alias uvnew="uv_new_with_taskfile"

uv_new_with_taskfileという関数名で、uv初期化後にTaskfileのテンプレートを該当フォルダにコピーするようにしています。呼び出しはuvnew ...となっており、オプションはuvと全て同じものとなっております。なお、コピーされるTaskfileのテンプレートは以下になります。

version: '3'

tasks:
  install:
    desc: Install dependencies
    cmd: uv sync

  run:
    desc: Run the project
    cmd: uv run python -m {{.PROJECT_NAME | default (base .ROOT_DIR)}}

  test:
    desc: Run tests
    cmd: uv run pytest

  lint:
    desc: Run linter
    cmd: uv run ruff check

  format:
    desc: Format the code
    cmd: uv run ruff format

  check:
    desc: Run type checker
    cmd: uv run mypy .

  clean:
    desc: Clean cache and build artifacts
    cmd: |
      rm -rf .pytest_cache/
      rm -rf __pycache__/
      find . -name "*.pyc" -delete
      find . -name "*.pyo" -delete

  shell:
    desc: Activate virtual environment shell
    cmd: uv shell

  venv:
    desc: Create and activate virtual environment
    cmd: |
      uv venv
      source .venv/bin/activate
      echo "Virtual environment created and activated"

  venv-off:
    desc: Deactivate virtual environment
    cmd: |
      if [ -n "$VIRTUAL_ENV" ]; then
        deactivate
        echo "Virtual environment deactivated"
      else
        echo "No virtual environment is currently active"
      fi

  add:
    desc: Add a dependency
    cmd: uv add {{.CLI_ARGS}}

  remove:
    desc: Remove a dependency
    cmd: uv remove {{.CLI_ARGS}}

  lock:
    desc: Update lock file
    cmd: uv lock

https://zenn.dev/akasan/articles/39f81f8bd15790

cargoのカスタマイズ

Rustを使いたい時はcargoを利用しますが、こちらもuvと同様にしてコマンドを実装しています。

function cargo_new_with_taskfile() {
    if [[ $# -eq 0 ]]; then
        echo "Usage: cargo_new_with_taskfile <project_name> [cargo_new_options...]"
        return 1
    fi
    
    local project_name=$1
    shift
    
    # Run cargo new with provided arguments
    cargo new "$project_name" "$@"
    
    if [[ $? -eq 0 ]]; then
        # Create Taskfile.yml in the new project directory
        cp ./templates/Taskfile_cargo.yml $project_name/Taskfile.yml
        echo "✅ Created Rust project '$project_name' with Taskfile.yml"
    else
        echo "❌ Failed to create Rust project"
        return 1
    fi
}

# Alias for convenience
alias cargonew="cargo_new_with_taskfile"

引数はcargoのものと一致しております。コマンドはcargonewという名前で呼び出せるようにしています。コピーするTaskfileは以下になります。

version: '3'

tasks:
  build:
    desc: Build the project
    cmd: cargo build

  run:
    desc: Run the project
    cmd: cargo run

  build_and_run:
    desc: Build and run
    deps: [build]
    cmd: cargo run

  test:
    desc: Run tests
    cmd: cargo test

  check:
    desc: Check the project for errors
    cmd: cargo check

  fmt:
    desc: Format the code
    cmd: cargo fmt

  clippy:
    desc: Run clippy linter
    cmd: cargo clippy

  clean:
    desc: Clean build artifacts
    cmd: cargo clean

  release:
    desc: Build for release
    cmd: cargo build --release

  doc:
    desc: Generate documentation
    cmd: cargo doc --open

Kubernetesのカスタマイズ

kubectlおよびgcloudを普段利用していますが、コマンドすぐに忘れちゃうので、Taskfileを利用できるようにコマンドを作りました。

function k8s_with_taskfile() {
    if [[ $# -eq 0 ]]; then
        echo "Usage: k8s_with_taskfile <project_name> [k8s_new_options...]"
        return 1
    fi
    
    local project_name=$1
    shift
    
    # Create folder
    mkdir $project_name
    
    if [[ $? -eq 0 ]]; then
        # Create Taskfile.yml in the new project directory
        cp ./templates/Taskfile_k8s.yml $project_name/Taskfile.yml
        cp ./templates/k8s_sample_pod.yml $project_name/sample-pod.yaml
        echo "✅ Created k8s project '$project_name' with Taskfile.yml"
    else
        echo "❌ Failed to create k8s project"
        return 1
    fi
}

alias k8snew="k8s_with_taskfile"

ここでコピーするTaskfileとサンプルのPodを適用するためのファイルは以下になってます。

version: '3'

vars:
  CLUSTER_NAME: hoge
  SERVER_VERSION: 1.33.1-gke.158400
  ZONE: asia-northeast1-a
  NUM_NODES: 3
  MACHINE_TYPE: n1-standard-4

tasks:
  create_cluster:
    cmds:
      - gcloud container clusters create {{.CLUSTER_NAME}} --cluster-version {{.SERVER_VERSION}} --zone {{.ZONE}} --num-nodes {{.NUM_NODES}} --machine-type {{.MACHINE_TYPE}} --enable-network-policy --enable-vertical-pod-autoscaling

  check_server_version:
    cmds:
      - gcloud container get-server-config --zone {{.ZONE}}

  set_cluster_admin:*:
    vars:
      EMAIL: "{{index .MATCH 0}}"
    cmds:
      # task set_cluster_admin:hoge@hoge.com
      - kubectl create clusterrolebinding user-cluster-admin-binding --clusterrole=cluster-admin --user={{.EMAIL}}

  delete_cluster:
    cmds:
      - gcloud container clust ers delete {{.CLUSTER_NAME}} --zone {{.ZONE}}

  apply:
    vars:
      YAML_FILE: "{{index .MATCH 0}}"
    cmds:
      - kubectl apply -f {{.YAML_FILE}} 

  get_pod_yaml:*:
    vars:
      POD_NAME: "{{index .MATCH 0}}"
    cmds:
      - kubectl get pod {{.POD_NAME}} -o yaml

  list_pods:
    cmds:
      - kubectl get pods

  list_pods_detail:
    cmds:
      - kubectl get pods --output wide

  exec_bash:*:
    vars:
      POD_NAME: "{{index .MATCH 0}}"
    cmds:
      - kubectl exec -it {{.POD_NAME}} -- /bin/bash
apiVersion: v1
kind: Pod
metadata:
  name: sample-pod
spec:
  containers:
    - name: nginx-container
      image: nginx:1.16

pecoの応用

pecoを普段結構利用しているのですが、|でコマンド結果を受け渡したりするのがめんどくさかったのでpeco ...という形式でファイル入力からコマンド実行結果まで全部受け渡したいなと思って、pecaというコマンドでそれができるようにしてみました。

function peca() {
  if [ $# -eq 0 ]; then
    echo "Usage: peca <file_or_command> [args...]"
    echo "If argument is a file, cat it and pipe to peco"
    echo "Otherwise, execute command and pipe output to peco"
    return 1
  fi
  
  local first_arg="$1"
  shift
  
  if [ -f "$first_arg" ]; then
    cat "$first_arg" | peco
  elif [ -e "$first_arg" ]; then
    echo "Error: $first_arg exists but is not a regular file"
    return 1
  else
    local cmd="$first_arg $*"
    local output
    output=$(eval "$cmd" 2>&1)
    local exit_code=$?
    
    if [ $exit_code -ne 0 ]; then
      echo "Command failed with exit code $exit_code:"
      echo "$output"
      return $exit_code
    fi
    
    if [ -z "$output" ]; then
      echo "No output from command"
      return 0
    fi
    
    echo "$output" | peco
  fi

使い方としては以下のようにファイルからコマンド結果までなんでもいけます。

peca hoge.csv
peca ls -la

まとめ

今回は私が普段よく使ってる自作コマンドについて紹介しました。シェルスクリプトはまだまだ下手でAIの協力とかももらいつつコマンド実装方法を日々調べて実装しています。みなさんのおすすめの自作コマンドもあればいろいろ教えてくれると嬉しいです。

Discussion