次ログ

次ログ

ゆるりと働いているSREの技術ブログのような何か。趣味の話も書く

シェル芸bot環境につぶやいてきたジョークシェル芸をひたすら書き連ねる

今までシェル芸botで実行してきたシェルのうち ボクが気に入ったものや、メモしておきたいものをひたすらここに残します。

ついでにどういうことをやっているのかといった解説もします。

LISP風の条件分岐を行う関数

[ifという関数を定義して]が見つかるまでをシェルのコマンドとみなす。 結果的に内部でifしてるんですけれどね。一応true、falseで実行されるコマンドが変化する。

[if(){
f=2
a=()
b=()
c=()
i=0
for o in "$@";do
case "$o" in
[)f=1;;
])f=2;let i++;;
*)
if [ $f = 1 ];then
case $i in
0)a+=($o);;
1)b+=($o);;
2)c+=($o);;
esac
fi
;;
esac
done
if $("${a[@]}");then
eval ${b[@]}
else
eval ${c[@]}
fi
}
[if [ true ] [ echo 1 ] [ echo 2 ] ]
#シェル芸

喘ぎ声生成

いわゆるみさくらなんこつ的な喘ぎ声っぽいなにかを生成するシェル芸。 下品すぎる。

p(){
paste -d "" - - - - - -
}
h(){
head -n 99
}
s(){
shuf -re $@
}
(s あ ぁ ん ♥ \"|p|grep -vE '^("|♥)'|h;
s ら め へ ぇ \"|p|grep -vEe '""|らら|めめ'|grep -E "^ら"|h;
s ん ほ お ぉ \"|p|grep -E "^ん"|h;
s い く ぅ \"|p|grep -E '^い'|grep -v '""'|h;)|shuf
らへめ"ら"
い"くぅ"く
ん"ぉんぉお
ぁぁぁあ♥ん
らぇへめ"ぇ
い"いくくく
らへへ"めら
ん""ん"ぁ
んおお"んお
いくいぅ"く
らへ"へめへ
らぇぇめぇぇ

「あなたは赤い部屋が好きですか」1文字ずつ増やして出力

かなり昔に作成されたホラーフラッシュの赤い部屋の最後の方で表示される 少しずつ文字が見えていくというのを再現したシェル(もとのフラッシュだと文字と文字の切れ目から出現していたが)。

本家の赤い部屋フラッシュはもう見れない。 公開が停止されている、という意味ではなく、 ブラウザがフラッシュプラグインをサポートしていないため。 Youtubeで我慢する。

これを当時見たときは本気で怖がっていたような記憶。

echo あなたは赤い部屋が好きですか? | grep -o . | awk '{s=s""$1; print s}'
あ
あな
あなた
あなたは
あなたは赤
あなたは赤い
あなたは赤い部
あなたは赤い部屋
あなたは赤い部屋が
あなたは赤い部屋が好
あなたは赤い部屋が好き
あなたは赤い部屋が好きで
あなたは赤い部屋が好きです
あなたは赤い部屋が好きですか
あなたは赤い部屋が好きですか?

技術的な話をすると、grep -o .は文字列を1列にする。 -oオプションはマッチした文字だけを出力する。 .正規表現での「任意の何らかの文字」にマッチする。 よって、あらゆる文字が1つマッチしたら出力し、次のマッチを待つようになる。

以下の例を示す。

[0-9]は数値にのみにマッチする正規表現。 結果も数値のみ出力されている。

% echo あ12い34う56え78お | grep -Eo "[0-9]" 
1
2
3
4
5
6
7
8

対して.ではすべての文字が1文字ずつ出力されている。

% echo あ12い34う56え78お | grep -o "." 
あ
1
2
い
3
4
う
5
6
え
7
8
お

最後はawkで1文字ずつ連結して出力する。

awkでは文字列の連結は+とかは不要。 文字列や変数をカンマで区切ったらスペース区切り出力され、 カンマをつけずに文字列や変数を続けてprintすると 文字列が連結される。

awk '{s=s""$1; print s}'

なので、こう書いても同じ結果になる。

awk '{s=s $1; print s}'

うんこで囲う

シェル芸bot環境に存在するいくつかのコマンドと組み合わせている。 手前味噌だけれど、alignという自作のコマンドも使用している。

(
  echo;
  (
    unko.tower -s うんこ神 5 \
    | align left -p " " \
    | owari kan -gi; echo
  ) \
  | align center -p1 -n 60 \
  | sed -r 's/111/うんこ/g;s/11/うん/g;s/1/う/g';echo
) \
| align center -p2 -n 72 \
| sed 's/222/ウンコ/g;s/22/ウン/g;s/2/ウ/g' \
| sed 'y/NHK/うんこ/' \
| imgout #シェル芸

unko.towerはsuper_unkoリポジトリのコマンドの1つ。 -sオプションではうんこの中にテキストを埋め込める。 alignはテキストの位置を揃える。 owariはギコネコが看板を背負っているAAを出力する。 -giでは標準入力を受け取って、看板内に中央寄せして埋め込んで出力する。

$ unko.tower -s うんこ | align left | owari kan -gi

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|       人          |
|     (うんこ)      |
|    (うんこうん)    |
|   (こうんこうんこ)  |
|       制作・著作       |
|   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄  |
|        N H K        |
|____________|
       ∧∧ ||        
      ( ゚д゚)||       
       /   づΦ        

一番深い階層でギコネコを出力するが、それ全体をサブシェルでラッピングし、 サブシェルをパイプしてalignに渡すことで結果全体を一揃えしている。

同じようなことを繰り返し、空白文字をうんこに置換。 sedのyコマンドでは1文字ずつ対応するように置換する。

相関図

これは2ツイート連携で実現している。280文字以内には収まった。

やっていることは結局前述のうんこで囲むのと同じことを組み合わせているだけ。

#!/bin/bash

(
echo
echo 'うんこシェル芸人とショル力の相関'|align center -n 70 -p ' '
(
echo 'y (シェル芸力)'
(
seq 10|while read i;do printf "%${i}s\n" '/';done|tac|sed 's/ / /g' | align left -p ' ' | sed -r 's/^/|/g'
echo
) | align left -p '~~' \
  | sed '11s/$/ x (うんこと呟く頻度)/g'
) \
| align left -n 40 -p ' ' \
| align center -p ' ' -n 70
echo '※1 サンプリング数:255名'|align right -n 70 -p ' '
echo
) | align center -p '-' -n 80

以下抜粋。 seq〜printfで空白埋めされた線を描画している。左上から右下に下がるように描画される。 線は斜め上に上がってほしかったので、whileの結果全体をtacで反転されている。 taccatの逆で、入力データの最終行から最初の行に向かって出力するコマンド。

$ seq 10 \
| while read i; do
    printf "%${i}s\n" '/'
  done \
| tac \
| sed 's/ / /g' \
| align left -p ' ' \
| sed -r 's/^/|/g'

|         /
|        / 
|       /  
|      /   
|     /    
|    /     
|   /      
|  /       
| /        
|/         

難読化うんこ

超絶読みづらくしたunko.tower。解説するのがしんどい。

+() { ! :;bc<<<obase=$?$#\;ibase=$(($?<<$?))\;${@:${?}:${?}}; };
$(echo -e "\x`! :;+ $?$?$?``! :;+ $?$?`\x`! :;+ $?$?$#``! :;+ $?$#$?`\x`! :;+ $?$?$#``! :;+ $?$#$#`") `! :`$((($?<<$?)+$?))$(echo -e "\x`! :;+ $?$?$?``! :;+ $?$?`")/     / ・__・ / <(unko.tower) #シェル芸


     人
   (   )
  ( ・__・ )
 (       )

+()はただの関数定義。関数名を+としている。 中でやっているのはbcコマンドに<<<(ヒアストリング)でbc用の式を渡している。

+の関数内の先頭の! :;$?を1にするための処理。 $(($?<<$?))bashの算術演算式で、左シフトを表現している。 よって$(($?<<$?))$((1<<1))になり、1の1左シフトで2になる。

あとのecho -eのところは、16進数で表現したsedコマンド。 ただし直接sedとしたくなかったので、echo -eで16進表現(\xFFみたいな)を文字に変換している。

あみだ線生成

あみだくじを生成する。 アルゴリズムが適当なので総当りで文字を生成して、余計なものが混じってる行を出力しないようにしているので 計算量は増えがち。 あみだくじの線の数を増やしたら出力されないほうが多くなる。

make_row() {
  seq $1 \
    | xargs -I@ bash -c 'printf "$(shuf -n1 -e ├ ┤ │)"' \
    | grep -vEe "├([^┤]|$)" -e "(^|[^├])┤"
  return $?
}

row=5
col=6

n=0
while [ $n -lt $row ]; do
  ! make_row $col
  n=$((n+$?))
done
├┤├┤││
││││├┤
││││├┤
├┤│├┤│
│├┤├┤│

やってることとしては、make_row関数であみだくじを生成し、その戻り値で あみだくじが生成されたかどうかを判定し加算し続けるというもの。 指定の数になったらwhileを抜ける。

grepは指定の文字が1つも見つからなかったときに終了コードが1になる。 1つでも文字が見つかった場合は終了コードが0になる。

今回のケースではその性質を利用し、あみだくじが出力されたとき、 「1行あみだくじの行を生成した」ということとして 生成した行数1を戻り値として返すということをやっている。

呼び出し元で!を使って0を1に反転しているので、 grepで行が見つかった際に1加算している。 (不正なくじが生成されていない)

終了コードの使い方としてはよろしくないアプローチだとは思う。

虹色のうんこ

super_unkoのunko.printpnmという自作のコマンドで出力したうんこのテキストを虹色に変換している。 PNMについてはWikipediaが参考になる。

a=("255 0 0" "255 165 0" "255 255 0" "0 128 0" "0 255 255" "0 0 255" "128 0 128")
i=0
while read -r line
do
  i=$((++i))
  sed -r 's/255 255 255/'"${a[$((i%7))]}"'/g' <<< "$line"
done <<< "$(unko.printpnm)" \
| convert - -scale 200x200 /images/t.png
#シェル芸

whileに対してヒアドキュメントを渡すところから処理が始まる。 パイプでwhileに渡していないことには理由があって、iというループカウンタを加算したいから。

パイプ後で実行されるコマンドは別シェルとして起動されるため、 その中で変数を定義したり値を変更しても、while外の変数には影響を与えない。

ループカウンタiは0〜6の値をループさせたいのでパイプは使えない、ということで ヒアドキュメントで渡している。

ループカウンタは虹色のRGBを定義した配列から色を取り出すときに使用している。 sedでもとの色の指定の箇所をループの都度sedの式を配列の色をもとに生成して置換している。

convert -ではパイプの結果を入力データとして利用することを指定している。 それをPNGとして保存する。

ちなみにwhileにテキストを渡す方法にはいくつかアプローチがある。 ファイルをいきなり渡したりコマンドの実行結果を渡したりパイプで渡したり いろいろ方法がある。 いくつかやり方を知っていると芸の幅が広がる。以下はその一例。

パイプせずにファイルをwhileに渡す

while read -r line; do
  echo $line
done < text.txt

パイプしてファイルを渡す

cat text.txt | while read -r line; do
  echo $line
done

パイプせずに複数行のテキストを渡す

while read -r line; do
  echo $line
done << EOS
1
22
333
EOS

パイプせずにコマンドの実行結果を渡す

while read -r line; do
  echo $line
done <<< "$(seq 3)"

色のついたテキストを画像に変換する

手前味噌で恐縮だが、textimgという自作のコマンドを使う。 これは渡されたテキストのエスケープシーケンスを解析してRGB値に変換して画像出力するコマンド。 シェル芸bot環境で画像を描画したくて作った。

bigunko.show | textimg -o /images/t.png

シェル芸bot環境では以下のように短くかける。

bigunko.show | textimg -s

grepの結果やscreenfetchなどの結果も画像に反映できる。

screenfetch | textimg -s

ちなみに上にスクロールできるオプションがある。

殺意のうんこ

前述のbigunko.showの色を変更して赤黒い配色に変更するシェル。

bigunko.show \
| sed 's/100/0/g;s/94/16/g;s/58/1/g;s/243/0/g' \
| sed 's/\x1b\[0m$//g;:a;s/ /う/;s/ /ん/;s/ /こ/;/ $/ba;s/[うんこ]/\x1b[31m&/g' \
| textimg -s
#シェル芸

bigunko.showの結果をlessで確認してみるとESC[48;5;243mとかって文字が大量に表示される。

これは端末上で背景色の変更を表現するエスケープシーケンス。 ESC[4nから始まると背景色、ESC[3nから始まると文字色が変わる。 このシェルでやっているsedはこのエスケープシーケンスの数値を別の色の数値に置換する。

置換が終わったら全角空白文字をうんこ文字に置換する。 文字色を変更するためESC[31を文字の置換に指定する。

色のエスケープシーケンスについて

前述のESC[48;とかって文字を端末上で再現する場合は\x1b[48あるいは\e[48と書く。 色だけ試したい場合は以下のシェルを実行してみるととりあえず色は見れる。

for i in 3 4; do
  for j in {0..7}; do
    echo -ne "\e[${i}${j}m  test  \e[m"
  done
  echo
done

ESC[3nは文字の色を変更する。 ESC[4nは文字の背景色を変更する。

nの値は0~8になる。0~7については色が固定だが、8を指定するとさらに別の色を指定できるようになる。

0~7の色についてはQiitaの記事 とかを見ればすぐに見つかる。

で、8の指定は特殊。 8を指定した場合は2通りの色の指定の仕方がある。 書式は以下の通り。

分類 ESC ESC 色指定
文字色 ESC[38 (固定) 5 (固定) 0 ~ 255m ESC[38;5;128m
背景色 ESC[48 (固定) 5 (固定) 0 ~ 255m ESC[48;5;128m

0〜255の色の一覧は下記の通り。

もう1つはRGB指定する方法。

きもいオジサンのLINEメッセージ

先日(2019/05/31だったかな)ojichatというコマンドがシェル芸bot環境に追加された。 これはLINEやTwitterなどのSNSで女性に対して下心丸出しのおっさんが送るメッセージを再現するコマンド。 以下のように実行する。

ojichat

ヤエ、そっちも雨なのかな( ̄ー ̄?)⁉出張で広島に行ってきたよ😍💕😄観光でも、行きたいなぁ💗💗モチロン、ヤエとネ

ojichat 花子

花子チャン、お疲れ様〜😃(^o^)😃✋今日は長崎28度だよ😱💦^^;💔暑いよ^^;💔ヤケドしないように気をつけないトネ(^_^)😃☀ (笑)

文章は完全にランダムに生成されるので、特定のパターンがほしいときはheadする。 以下は10回ojichatを実行する。

yes ojichat | head | bash

これを利用して特定の文言が出現するパターンを取得して文章を書き換えて独自の文章を作ったり。

メンヘラおじさん。

m=$(seq 100 | xargs -I@ printf "なんで")
yes ojichat \
| head -n 100 \
| bash \
| grep こうよ \
| head -n 1 \
| sed -E 's/こうよ.*/こうよ・・・。行かないの?なんで・・・'$m'/g'

#シェル芸
マキちゃん、オハヨウ〜😚😃😃♥ ちょっと電話できるカナ✋❓⁉⁉そろそろご飯行こうよ・・・。行かないの?なんで・・・なんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなん

別の文章と合わせたりして色々遊べる。

きもいオジサンのメッセージをうんこに埋め込む

殺意のうんこシェル芸と同じようなことをやっている。

bigunko.showに含まれる文字を埋め込むには少し工夫が必要。 bigunko.showの全角スペースの前後にはエスケープシーケンスがついているため 「全角文字の連続」みたいな正規表現はかけない。

今回の僕のアプローチは「sedの置換式文字列をループで回し、bigunko.showが書かれた テキストを都度上書きして1文字ずつ全角空白を置換する」というようにした。

前述のシェルを分解すると以下のような3つのシェルに分解できる。

bigunko.show \
| tr '\n' 'Q' > a.txt

for __ in {1..20} do
  for cmd in $(ojichat | grep -o . | sed -E "s,.,s/ /&/ ,g" | tr -d "\n"); do
    sed -i "$cmd" a.txt
  done
done

cat a.txt|tr Q \\n \
| sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\
| textimg -s

#シェル芸

まずbigunko.showをテキストファイルに出力する。 このとき改行文字を元の文章に出現しない何でも良い文字に置換して1行のデータに変換している。 sedのループで置換するときに1行のデータのほうが処理しやすいため。

bigunko.show \
| tr '\n' 'Q' > a.txt

sedで文字を置換する処理。 sed -E "s,.,s/ /&/ ,g"の箇所でsedの式を生成している。

for __ in {1..20} do
  for cmd in $(ojichat | grep -o . | sed -E "s,.,s/ /&/ ,g" | tr -d "\n"); do
    sed -i "$cmd" a.txt
  done
done

sedの結果を一旦出力すると次のようになる。

% ojichat | grep -o . | sed -E 's,.,s/ /&/ ,g' | tr -d \\n
s/ /は/ s/ /つ/ s/ /か/ s/ /チ/ s/ /ャ/ s/ /ン/ s/ /、/ s/ /ヤ/ s/ /ッ/ s/ /ホ/ s/ /ー/ s/ /(/ s/ /^/ s/ /o/ s/ /^/ s/ /)/ s/ /😃/ s/ /😃/ s/ /♥/ s/ / / s/ /😄/ s/ /何/ s/ /し/ s/ /て/ s/ /る/ s/ /の/ s/ /か/ s/ /い/ s/ /❓/ s/ /早/ s/ /く/ s/ /会/ s/ /い/ s/ /た/ s/ /イ/ s/ /ナ/ s/ /😄/ s/ /💕/

sed&はマッチした文字列をそのまま埋め込む特殊な値。 後方参照みたいなもの。よく使う。

この結果をcmdにセットし、sed -iの式として利用している。 -iオプションでファイルを置換した結果で上書きできる。 分解すると以下のようなsedとして実行されている。

sed -i "s/ /は/" a.txt
sed -i "s/ /つ/" a.txt

最後に結合。最初の処理で1行のデータにしていたものをもとに戻す。 sedの式では、bigunko.showの背景色を赤色のエスケープシーケンスに置換している。

cat a.txt|tr Q \\n \
| sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\
| textimg -s

あとこのシェルだけでなくシェル芸botではよくやってしまうが、 sedで複数の式を一気に書いている。

sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\

圧縮されていて読みにくいが、複数のsコマンドをここでは実行している。 ;が式の区切り文字である。 実はあまり知らない人多いと思うが、sedは複数の式を同時にかけるし、何なら複数行に書くこともできる。

echo あいう | sed 's/い/i/; s/i/Z/'

あzう

これも結局は以下のシェルと同じである。

% echo あいう | sed 's/い/i/      
s/i/Z/
'

あZう

割とあまり一般的でない特殊なシェルを書いたような気がする。(シェル芸では一般的 なテクニックかもしれないが、普通のサーバオペレーションではまずやらないようなシ ェルの使い方であることは間違いない)

以上です。