yad : シェルスクリプトで簡単にGUIを作って活用したい

# はじめに

- Githubの組織管理をしているときに、「新規ユーザのAさん、Bさん、…を組織Xに加えて」とかの依頼をよく受けます。
  - 最近は、Github Rest APIを用いて、スクリプトベースで登録処理ができるのでだいぶ楽になったのですが…、いちいちコマンドラインに打ち込むのも面倒。
  - slackで依頼を受けるので、slackの内容をコピペしたいな…、と思っておりました。
  - ちょっとしたGUIツールを作るのに、よくzenityを使用していたのですが、設定できる項目が少ないのが欠点でした。
- 今回、zenityのforkである```yad```を使用してみました。そこで、yadによるGUIの構築と得られるデータの処理についてまとめます。
  - yadについてまとめるつもりが、参考ページの「[yad メモ](https://www.kabipan.com/densan/yad.html)」にすべてが書いてあります。感謝申し上げます。

## 0. 参考ページ

- [yad メモ](https://www.kabipan.com/densan/yad.html)
  - yadの使い方を非常に参考にさせていただきました。

## 1. 動作確認環境

- OS : Ubuntu 20.04
- CPU : Intel(R) Pentium(R) CPU G4560 @ 3.50GHz
- RAM : 8GB
- Software
  - bash : 5.0.17(1)-release 
  - yad : 0.40.0 (GTK+ 3.24.20)
  - Xorg : 1.20.13

### ＊ yadのインストール

- Ubuntuの場合、aptでインストール可能

```bash
sudo apt install yad
```

## 2. yadのお試し

- 「[yad メモ](https://www.kabipan.com/densan/yad.html)」を参考に、以下の情報が入力できるウィンドウを起動してみます。
  - 登録したい組織名（プルダウンリスト）：TeamA, TeamB, TeamC
  - 登録したいユーザリスト（テキストボックス）　＊複数行を想定
- コマンドを実行するだけでGUIが表示されます。これはzenityと同じですが、やはり神ツールです。

```bash
yad --title="ユーザ登録ウィンドウ" \
    --height=400 \
    --width=400 \
    --mouse \
    --fixed \
    --form \
      --item-separator='!'\
      --separator='|'\
      --field="所属チーム                   ":CB    'TeamA!TeamB!TeamC' \
      --field="登録するユーザを入力してください":TXT   ""
```

- 下のような画面が表示されます。この状態で、適当入力してOKを押下すると、標準出力に入力情報が表示されます。
  - ```TeamA|hogehoge@gmail.com\nfugafuga@gmail.com\npiyopiyo@gmail.com|```が出力となります。

<img width="800" alt="yad_entry_window.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/5aa671dd-48f9-0af0-cabb-0680f5746e3f.png">

```bash
$ yad --title="ユーザ登録ウィンドウ" \
>     --height=400 \
>     --width=400 \
>     --mouse \
>     --fixed \
>     --form \
>       --item-separator='!'\
>       --separator='|'\
>       --field="所属チーム                   ":CB    'TeamA!TeamB!TeamC' \
>       --field="登録するユーザを入力してください":TXT   ""
TeamA|hogehoge@gmail.com\nfugafuga@gmail.com\npiyopiyo@gmail.com|
```

### ＊ 出力内容のポイント

- yadの表示項目ごとに|(パイプ)を区切り文字で連結した文字列が取得できる。
  - |(パイプ)となるのは、--form --separator='|'を設定しているからです。他の文字を指定することも可能です。
- ただし、テキストボックスの"\n"は改行コードではなく```ただの文字```
  - 実は、IFSを一時的に改行コードに修正して文字列分解を試みて、小一時間ハマってました。

## 3. yad出力結果を加工する

```bash
function main() {
  # ユーザ登録ウィンドウの表示
  local input=$(ここにyadによるウィンドウ処理を書く)

  # input情報をグループとユーザリストに分割する
  # input = グループ名|登録するユーザ|
  (
    # IFSを一時的に変更するため、サブシェルを利用する
    IFS=\|; ARR=(${input})
    GROUP_NAME=${ARR[0]}
    tmp_USERLIST=${ARR[1]}

    # 登録ユーザの分解
    USERLIST=(${tmp_USERLIST//\\n/|})
    for user in "${USERLIST[@]}"
    do
      // ここに登録処理とかを書く
      register ${GROUP_NAME} ${user}
    done
  )
}
```

- yad処理を``か$()でサブ処理で実行し、その出力結果をbashスクリプトの変数に格納します。
- その後、IFSを変数して、区切り文字を|(パイプ)にして、文字列をArray分割します。
  - ユーザは複数入力されるので、"\n"を|(パイプ)に置換して、文字列を再度Array分割します。
  - ちなみに、IFSを一時的に修正するので、サブシェルを利用すると安全です。
- 以下のようなスクリプトとなりました。

```bash
#-----------------------------------------------------------
# @file ui_register.sh
# @brief ユーザ登録ウィンドウ、および入力情報を取得する
# @auther tomoten-umino
# @date 2022-01-09
#-----------------------------------------------------------
#!/bin/bash

#-----------------------------------------------------------
# @fn open_window
# @brief ユーザ登録ウィンドウ描画処理
# @param なし
# @return 0 : 正常終了, 1 : それ以外
#-----------------------------------------------------------
function open_window() {
  yad --title="ユーザ登録ウィンドウ" \
      --height=400 \
      --width=400 \
      --mouse \
      --fixed \
      --form \
        --item-separator='!'\
        --separator='|'\
        --field="所属チーム                   ":CB    'TeamA!TeamB!TeamC' \
        --field="登録するユーザを入力してください":TXT   ""
}


#-----------------------------------------------------------
# @fn register
# @brief 登録処理
# @param group : グループ名
# @param user : ユーザ名
# @return 0 : 正常終了, 1 : それ以外
# @remark ダミー処理
#-----------------------------------------------------------
function register() {
  local group=$1
  local user=$2

  echo "グループ名: ${group} , ユーザ名: ${user}"

  return 0
}


#-----------------------------------------------------------
# @fn main
# @brief メイン処理
# @param なし
# @return 0 : 正常終了, 1 : それ以外
#-----------------------------------------------------------
function main() {

  # errorチェック：yadインストール確認
  type yad >/dev/null 2>&1
  if [ $? != 0 ]; then
    # インストールされていない場合、エラー分を表示して終了する。
    echo "This script needs \"yad\". Please install yad"
    echo "ex) sudo apt install yad, dnf install yad, ..."
    return 1
  fi

  # errorチェック：DISPLAY変数は設定されているか
  # GUIアプリなので、DISPLAY変数は必須
  if [ x$DISPLAY = x ]; then
    # DISPLAY変数が空なので失敗
    echo "Please set DISPLAY"
    return 1
  fi

  ## 定数
  local GROUP_NAME=""
  local USERLIST=""
  local tmp_USERLIST=""

  # ユーザ登録ウィンドウの表示
  local input=$(open_window)

  # input情報をグループとユーザリストに分割する
  # input = グループ名|登録するユーザ|
  (
    # IFSを一時的に変更するため、サブシェルを利用する
    IFS=\|; ARR=(${input})
    GROUP_NAME=${ARR[0]}
    tmp_USERLIST=${ARR[1]}

    # 登録ユーザの分解
    USERLIST=(${tmp_USERLIST//\\n/|})
    for user in "${USERLIST[@]}"
    do
      register ${GROUP_NAME} ${user}
    done
  )
}

# 処理を実行する
main
```

## 4. デモ

![regist_user.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/362dbdb0-1d7b-9d33-14f0-187d1d5b1667.gif)


## 5. おわりに

- デモでの起動では、出力結果を見るためにあえてスクリプト起動をしていますが、Desktopにアイコンを作成して、アイコンダブルクリックから実行すれば、なんちゃってデスクトップアプリケーションが作成できます。
- register処理は、このページに載せているのは登録情報をechoするだけのdummyですが、実際にGithub Rest APIを叩けば登録処理を実行できます。
- 凝ったGUIを作成することはできませんが、zenityよりもはるかに自由度が高いため、いろいろと応用が効くと感じました。