動く！電光掲示板シェル芸


　**⚠注意⚠: 点滅GIF画像が出てくるので、明るい部屋で画面から十分画面から離れてご覧ください。**

　この記事は[シェル芸 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/shellgei)と[klis Advent Calender 2019](https://adventar.org/calendars/4206)のクロスポストになりました。

# まえがき
　今回の記事では、動くタイプの電光掲示板シェル芸について解説します。
　具体的には文字を電光掲示板ライクに変換して、文字を画像にする`textimg`コマンドのオプションを色々使って動かします。
　基本的(?)な電光掲示板シェル芸については@yami_buta氏が本AdventCalendarで過去3回ほど解説[^1]をされているので、先にそちらの方に目を通されることをおすすめします。
[^1]: [その1](https://qiita.com/yami_buta/items/8697b02f4afbeb1f0d8c), [その2](https://qiita.com/yami_buta/items/6a3e3a2c850670f1616f), [その3](https://qiita.com/yami_buta/items/f2384d0a1c6ac587e265)

# おさらい 〜電光掲示板シェル芸とは〜
　「LED表示みたいな画像を生成する」[^2]シェル芸。
[^2]: [電車の行先表示シェル芸（2019年6月12日） | 上田ブログ](https://b.ueda.tech/?post=20190612)
▼参考画像[^3]
![kohno.png](https://pbs.twimg.com/media/EGSRIhoVAAEnz5P?format=png&name=4096x4096)
[^3]: https://twitter.com/minyoruminyon/status/1181228580711235584
　今回はこれを動かします。
　こんなことができます。

▼点滅とか…[^4]
![gameover.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/8635ebb0-69e5-b798-5402-c5030b02f80f.gif)
[^4]: https://twitter.com/minyoruminyon/status/1206658567551344641
▼縦スクロール[^5]
![tenmetsu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/f1c6c400-13e3-5130-4abe-c90b5c388cda.gif)
[^5]: https://twitter.com/minyoruminyon/status/1209334806502346752
▼掲示板っぽい横スクロール[^6]
![kohnosc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/7601be44-9d18-472c-6ef4-125ed7ef665f.gif)
[^6]: https://twitter.com/minyoruminyon/status/1209367740609204225
▼明暗[^7]
![internet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/46641174-a0c2-b0ff-9d29-8abfc0649f82.gif)
[^7]: https://twitter.com/minyoruminyon/status/1185642956155281408

# やってみる
## STEP0: 電光掲示板文字っぽく出力

　今回は文字`Qiita`を動かしたいと思います。
　まず`denkoh`関数を定義します。([定義ツイート](https://twitter.com/egpl0_sh_2/status/1209367488879714305))
　この関数は文字を任意の文字でAAのように変換したものを返し、簡単に電光掲示板風に変換します。
　最近だと@jiro4989氏がコマンドにしたもの[^8]もあるのでそちらを使っても良いです。

[^8]: [dtimg](https://github.com/jiro4989/scripts/blob/master/bin/dtimg)

```bash:denkoh
# denkoh <電光掲示板風にしたい文> <背景文字> <前景文字> <粗さ>
# dtimgだと:
# dtimg <電光掲示板風にしたい文> -b <背景文字> -g <前景文字> -F <粗さ>
# .bash_aliases`などに追記しておくと便利。
denkoh(){
  echo -e "$1"|textimg -F$4|convert - txt:-|awk -F'[, ]' \
  'NR>2{printf($1==0)?"\n":($4==0)?a:b}' a="$2" b="$3";echo
}
```
これを使うと、

```bash:Terminal
$ denkoh "Qiita" "　" "■ " 20
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
　　　　　　　　　　　　　■■■　　　　　　　■■■　　　　　　　　　　　　　　　　　　　　　　　
　■■■■■■　　　　　　■■■　　　　　　　■■■　　　　　　　　　　　　　　　　　　　　　　　
■■■■■■■■　　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　　　　　　　　　　
■■■　　■■■　　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　　　　　　　　　　
■■　　　■■■　　　　　　　　　　　　　　　　　　　　　　　　■■　　　　　　　　　　　　　　　
■■　　　　■■■　　■■■■■　　　　　■■■■■　　　　　　■■　　　　　　　■■■■■■　　
■■　　　　■■■　　■■■■■　　　　　■■■■■　　　　■■■■■■■■　　■■■■■■■■　
■■　　　　■■■　　　　■■■　　　　　　　■■■　　　　■■■■■■■■　　■■■　　■■■　
■■　　　　■■■　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　　　　　　■■■　
■■　　　　■■■　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　　■■■■■■■　
■■　　　　■■■　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　■■■■■■■■　
■■　　　　■■■　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　■■■　　■■■　
■■　　　　■■　　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　■■　　　■■■　
■■■　　■■■　　　　　■■■　　　　　　　■■■　　　　　　■■　　　　　　■■　　　■■■　
■■■■■■■■　　■■■■■■■■　　■■■■■■■■　　　　■■■■■■　　■■■■■■■■　
　■■■■■■　　　■■■■■■■■　　■■■■■■■■　　　　■■■■■■　　■■■■■■■■　
　　■■■■■■　　　　　　　　　　　　　　　　　　　　　　　　　■■■■　　　　　■■■　　　　
　　　　　■■■■　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
　　　　　　■■■　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
```
　という具合に文字が出力できます。
　この出力を`textimg`に標準入力で渡すと画像になります。

```bash:Terminal
$ denkoh "Qiita" "　" "■ " 20|textimg -o qiita.png
```
![qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/b0eff3b3-76b9-21f6-4c7e-81b5ceecfba5.png)

## STEP1: 点滅
　点滅させてみましょう。
　同じ行数の空行を出力し、行数指定(`textimg`の`-l`オプション)で`[文字部分n行]->[空行部分n行]`となるように調整してコマを切り替えGIF化します。
　ここでは任意の空行を作るため、`eval 'echo;'{<任意数個カンマ>}`とします。`eval 'echo;'{,,,,}{,,,}`とすると5*4=20回`echo`が評価されて20行分空行が作れます。
　ちなみに数で明示的に空行を作るなら`seq <任意行数>|tr -d 0-9`でも同じことができます。
　また複数コマンドの標準出力をまとめてパイプ先コマンドに渡す時は`(cmd1;cmd2)|cmd3`とします。便利〜

```bash:Terminal
#全体の行数の半分を-lに指定
$ (denkoh "Qiita" "　" "■" 20;eval 'echo;'{,,,,,}{,,} )|wc -l
42
$ (denkoh "Qiita" "　" "■" 20;eval 'echo;'{,,,,}{,,,})|textimg -al21 -o tenmetsu1.gif
```
![tenmetsu1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/e28d7cf5-cb4b-d9dd-c9db-37b2831a7f81.gif)
　目に痛いときは`-d`オプションで速度を落としましょう。(デフォルトは20ms)

```bash:Terminal
$ (denkoh "Qiita" "　" "■" 20;eval 'echo;'{,,,,}{,,,})|textimg -al21 -d50 -o tenmetsu2.gif
```
![tenmetsu2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/b538ac81-ce90-70ab-bc85-6d87b0596f89.gif)
　いい感じです。
　色コードの指定[^9]で色をつけることもできますが、色のついた絵文字を指定することで簡単に色をつけられます。また後述の`lolcat`という出力を虹色にするコマンドとも親和性が良いのでそれを使用しても楽しいです。

[^9]: [シェル - echoで文字に色をつける その1 - Miuran Business Systems](http://site.m-bsys.com/linux/echo-color-1)
```bash:Terminal
$ (denkoh "Qiita" "　" "🍀" 20;eval 'echo;'{,,,,}{,,,} )|textimg -al21 -o tenmetsu3.gif -d50
```

![tenmetsu3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/42da5857-c466-e849-9a7e-c60f63d20d5c.gif)

## STEP2: 縦スクロール
　次は縦に動かします。
　`-S`オプションは、デフォルトで1行ずつ移動します。

```bash:Terminal
$ (eval 'echo;'{,,,,}{,,,};denkoh Qiita "　" "🍀" 20)|textimg -aSl21 -o tatesc1.gif
```
![tatesc1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/43896c12-fcc6-ac04-7d5c-bde3f5e92af5.gif)
　`-W`オプションで任意の行数移動。

```bash:Terminal
$ (eval 'echo;'{,,,,}{,,,};denkoh Qiita "　" "🍀" 20)|textimg -aSl21 -W3 -o tatesc2.gif
```
![tatesc2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/2c1195da-0726-23fe-3acd-d2d20f062ea9.gif)
　そして`-E`オプションで、EOSでまた行頭にループさせます。

```bash:Terminal
$ (eval 'echo;'{,,,,}{,,,};denkoh Qiita "　" "🍀" 20)|textimg -aSEl21 -W3 -o tatesc3.gif
```
![tatesc3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/2d3f8b22-50f1-9d5c-4cea-3ebfcc04de12.gif)
　空行を減らして`-d`でコマの切り替え速度をアップ！

```bash:Terminal
$ (eval 'echo;'{,,,,}{,,,};denkoh Qiita "　" "🍀" 20)|textimg -aSEl21 -W3 -o tatesc4.gif
```
![tatesc4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/bbda6ea0-3fd3-2170-8a0a-d5f3603917dd.gif)

## STEP3: 横スクロール
　電光掲示板でよくある横方向のスクロール。これを実現するには`sed`を使って`denkoh`出力を任意文字分を行末から行頭へ回転移動させることを繰り返します。

```bash:Terminal
#一旦出力を変数に格納
$ s="$(denkoh Qiita '　' '🍀' 20)"
$ for i in $(seq 10) #ずらす回数分ループ, 一度に5文字ずつずらす
    do echo "$s"|sed -E 's/(.{'$[i*5]'})(.*)/\2\1/' 
  done|textimg -al22 -o yoko.gif #もともとの行数+2
```
![yoko.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/23c78ba8-a47f-17ba-54dc-9207be7e5ba9.gif)
　いい感じです。
　この処理は、`denkohmove`という関数に定義しておくと便利です。([定義ツイート](https://twitter.com/egpl0_sh_2/status/1206646114159448067))[^10]

[^10]: 実行結果例[1](https://twitter.com/minyoruminyon/status/1206642157416673280),[2](https://twitter.com/minyoruminyon/status/1206648527167746048)

```bash:denkohmove
# denkohmove <電光掲示板風にしたい文> <背景文字> <前景文字> <粗さ> \
#            <移動する幅> <ずらす回数(=コマ数)> <出力時の解像度> <コマの切り替え速度>
# ex: denkohmove '　🚙　🚗　🚙　🚗' '　' '⚪' 10 \
#                8 7 5 10 
denkohmove(){
  local text back front line width flame resolute speed
  text="$1" back="$2" front="$3" line="$4"
  width="$5" flame="$6" resolute="$7" speed="$8"
  s="$(denkoh ${text} ${back} ${front} ${line})"
  for i in $(seq ${flame})
    do echo "$s"|sed -E 's/(.{'$[i*width]'})(.*)/\2\1/'
  done|textimg -asl$[line+1] -F${resolute} -d${speed}
}
```
## STEP4: 明暗
　複数文字での`denkoh`出力を組み合わせ繰り返すことで明暗の反復を表現できます。
　「明」は中身のある文字(例:`●,▲`)、「暗」はない文字(例:`○,△`)で表現します。

```bash:Terminal
$ s="`denkoh Qiita '　' ■ 20`"
$ for i in ● ○
    do echo -e "$s"|sed "s/■/$i/g"
  done|textimg -al22 -o meimetsu1.gif
```
![meimetsu1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/ab163170-6501-eff1-c5e6-facaf6129e5c.gif)



　明暗の文字を連続して切り替えるとメリハリ(?)が付きます。

```bash:Terminal
$ s="`denkoh Qiita '　' ■ 20`"
$ for i in ● ○ ▲ △ ● ○ ▲ △ #明暗の文字を交互に配置する
    do echo -e "$s"|sed "s/■/$i/g"
  done|textimg -al22 -o meimetsu2.gif
```

![meimetsu2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/e58c4aef-b544-0ca5-d14a-cd5726a44ec3.gif)

　また出力時の行数を2倍にすると明暗を並べて表現できます。

```bash:Terminal
$ s="`denkoh Qiita '　' ■ 20`"
$ for i in ● ○ ▲ △ ● ○ ▲ △
    do echo -e "$s"|sed "s/■/$i/g"
  done|textimg -al44 -o meimetsu3.gif
```
![meimetsu3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/5ecdaa88-4bdb-0de1-3654-0714dacd96cc.gif)

　コマンドラインを虹色にするのでおなじみ`lolcat`コマンド[^11]で着色してみます。`-f`オプションは、パイプで渡しても色コードが失われないようにするオプションです。また`-p`オプションは虹の次の色へ変わるまでの間隔、`-F`オプションは色の機微が遷移する間隔を指定します。

[^11]: https://github.com/busyloop/lolcat

```bash:Terminal
$ s="`denkoh Qiita '　' ■ 20`"
$ for i in ● ○ ▲ △ ● ○ ▲ △
    do echo -e "$s"|sed "s/■/$i/g"
  done|lolcat -f|textimg -al22 -o meimetsu4.gif
```
![meimetsu4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/832d524b-3fa1-0a55-0ad0-ba67bcab8b78.gif)

　いいですね。

　ちなみに、STEP3とこの手法を組み合わせて各ループごとの偶数番目と奇数番目で前景文字を異なる文字で置換することで、冒頭例のようにチカチカした感じを表現できます。ここでは「明」を白混じりの`🍺`、「暗」を黄いろい`🌟`に指定しています。

```bash:Terminal
#ずらす作業の関数化
$ f(){ echo "$d"|sed -E 's/(.{'$[i*9]'})(.*)/\2\1/';}
$ d="$(denkoh Qiita '　' '🍺' 20)"
$ for i in {1..5}
    do [[ $((i%2)) = 0 ]]&&f || { f|sed 's_🍺_🌟_g';} #奇数番目ループなら置換
  done|textimg -al22 -o chika.gif
```
![chika.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/9cd2878b-74be-008b-d30f-edcbc8669b2c.gif)　
　派手〜！


## STEP5: [シェル芸bot](https://twitter.com/minyoruminyon)に投稿！
　[シェル芸bot](https://twitter.com/minyoruminyon)は皆さんご存知、フォロワーの`#シェル芸`付きのツイートをコマンドとして評価し結果をツイートで返すbotです。
　シェル芸botには、コンテナ内の`/images/`直下に画像ファイルをコマンド内で配置することでその画像を投稿できる機能があります。(静/動画ともに最大4枚まで)
　`textimg`にはこのために画像の出力先を`/images/o.[png|gif]`にできる`-s`オプションが用意されています。上記の例の`-o`オプション部分を削除して代わりに`-s`を使いましょう。
　また、投稿が1ツイートに収まりきらない場合や、他人がツイートで定義した関数・変数を使用したいなら、コメント付き引用RTでそのツイートをRTすることで実現できます。(参考: [denkohの定義ツイート](https://twitter.com/egpl0_sh_2/status/1209367488879714305)とそれを[引用したツイート](https://twitter.com/egpl0_sh_2/status/1209375349290651648))

## 備考: 投稿できない時 && テスト環境の紹介
　しかし、今回取り上げたGIF画像を実行しようとしてもうまく結果がbotに返されないこともあります。理由としては、

- シェル芸botの実行TLE(制約20s)
    - `timeout 20 <ツイート>`で実行している(?)
- 画像サイズ超過
    - 静止画 : 5MB
    - GIF動画 : 15MB(スマホなら5MB)

　が挙げられ、殆どは前者が原因です。計算量を落とすには、

- `textimg`での出力時に`-F`オプションでデフォルトの20より少ない数を指定して解像度を落とす(`denkoh`なら最後の数字)
- `denkoh`で出力する行数を減らす
- STEP3ならずらす幅を大きくしループを少なくしてコマを減らす

　などの方法があります。投稿できる計算量にしてからツイートを投稿しましょう。
　シェル芸bot作者の@theoldmoon0602氏によってbotの最新dockerイメージが配布[^12]されているのでそれを使用してもいいですが、画像を逐一確認しながら20秒制限を調整するには、以下のWeb実行環境(各種ネタコマンド完備)でテストするのが無難です。[^13]

[^12]: [Github](https://github.com/theoremoon/ShellgeiBot-Image)と[dockerhub](https://hub.docker.com/r/theoldmoon0602/shellgeibot)
[^13]: 2019年12月現在

- SGWeb([https://shellgei-web.net/](https://shellgei-web.net/))
    - @kekeho氏作
    - OSS[^14]
    - 現在デプロイされているのは約半年前のイメージ
    - 一部の~~ネタ~~コマンドが使えない
 
 [^14]: https://github.com/kekeho/SGWeb

- websh([https://websh.jiro4989.com/](https://websh.jiro4989.com/))
    - @jiro4989氏作
    - OSS[^15]で、純Nim製
    - 最新イメージがデプロイされている

[^15]: https://github.com/jiro4989/websh

~~実行されるまで`#シェル芸`ツイートを連投しTLを汚しながらコマンドを調整する方法もありますが...~~
## 結論
 **`textimg`便利すぎ！！！！！**[^16]

[^16]: `textimg`はwebshと同じ作者の方が作成している。

## 最後に
　いかがでしたか？
　このようにコマンドライン上だけで多種多様なGIF画像ができてしまいます。
　またこの他にも今回紹介した例を応用して様々な画像が作れます。

▼例: 斜めスクロール
![kohnosc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/51103e9a-e4fb-db75-5262-48f9ed7c444f.gif) 

▼例: 複数文字を順に[^17]
![merry.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/2c8e5de5-09db-f26f-4e63-c4546b7a436a.gif)


[^17]: https://twitter.com/minyoruminyon/status/1209425831367282688

　お試しあれ。

　余談ですが、今回紹介したtextimgやdtimgはシェル上に画像を投稿するために作られたコマンドです。他にもシェル芸bot上には様々なシェル芸愛好家(通称:**シェル芸人**)の方々が、主にシェル芸botで使うことを目的として作成した~~クソ~~便利なコマンド群が導入されています。[^18]
　是非本記事で使用したコマンドや他の楽しいコマンド群を使って`#シェル芸`でシェル芸botに投稿してみましょう！
[^18]: 典型例:[super_unko](https://github.com/unkontributors/super_unko),[ojichat](https://github.com/greymd/ojichat)
　それでは皆様、良いシェル芸ライフを！

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/x68LPj5tnf">https://t.co/x68LPj5tnf</a> <a href="https://t.co/H8lXmzqh3U">pic.twitter.com/H8lXmzqh3U</a></p>&mdash; シェル芸bot (@minyoruminyon) <a href="https://twitter.com/minyoruminyon/status/1209375210643701760?ref_src=twsrc%5Etfw">December 24, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>