たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

うんこがSL走らせるらしい

夜はさっむい

一年中こんな気候ならいいのに

なんかメールをのぞいたらはてなブログ

そろそろ更新しませんかだってさ
なので最近アツいシェル芸で遊んだお話を書きます

難読化シェル芸という闇

raintrees.net

シェル芸界隈に少し前に現れた究極のジャンル 難読化シェル芸
ここ最近はシェル芸で遊ぶときはこの難読化でわくわくしてます

難読化シェル芸ちょっと研究した

難読化を発見したkanata氏に感銘を受けたのでぼくも少しやってみました

その結果 日本語base64難読化シェル芸 とかいう闇を一つ深めたものを生んでしまいました

これをLT大会で話しました
その時のスライドがこれ~~
www.slideshare.net

詳細はスライドに書いてあるんですがまぁここでも

日本語base64難読化シェル芸とは

基本

日本語をbase64エンコードすることで目的のコマンドを得る手法
例えば「あ」をbase64エンコードすると 44GCCg== が得られる

$ echo "" | base64
44GCCg==

またこれを

$ echo "" | base64 | sed -E 's@(44|g==)@@g'|awk '{print tolower($1)}'
gcc

こうすることでgccコマンドが得られる うーんこれはなかなか

これは実はある法則がある
例えば「ら」「り」「る」をbase64に通すとそれぞれ 44KJ44KK44KL が得られる
つまり4文字目がアルファベット順になってる(実際は3文字目もだけど)

これを使ってコマンド列をレッツメイク

$ echo "そずねぞ" | base64 | grep -o '[a-z]' | xargs | sed 's@ @@g'
date

やったー!

他にもありまぁす

base64を複数通すことで目的のコマンド列をcutコマンドを使って取り出す手法
これはkanata師のls --helpからdateを取り出すやつを元にやってみた(上記サイト参照)

それがスライド13ページにあるやつ

$ echo "うんこ"|base64|base64|base64|cut -c 4,27|awk '{print tolower($1)}'
sl

うんこがSLを走らせる
まさに 産業革命

ちなみに目grepがつらい人たちのために

$ echo "もじれつ"|fold -w1|cat -n|grep ほしいやつ|awk '{print $1}'

これで何文字目かわかるのでcutしまくろう

これからの展望

なんかうんこを使うのが通例でトレンドなようで、様々なコマンドがうんこから錬成されてるみたいですね・・・ここまでくるとうんこに無限の可能性があるように感じてきます
どうやらシーザー暗号とかopensslを利用する手もあるようなのでそっちも試してみたいですね

日本語base64難読化シェル芸の方はさらなる隠蔽を目指してコマンド全体を覆いつくせるように模索しているところです

何かわかったらシェル芸勉強会のLTで話してみたいですね

競プロとかのテストケースを自動化したい

蜘蛛ってこわい

家蜘蛛っていいやつなのは知ってるけどデカいと怖い

テストケースを自動化したい

いまさら競プロを始めた隊長です
いまはABCを中心にやっています

基本的に
問題読む

コード書く(長考)
↓  ↑
問題のページに書いてあるテストケースを試す

提出

って流れですけどこの テストケースを試すとこが正直ダルい
そう思いませんか!?

なのでVisualStudioの単体テスト機能を使ってこれを自動化したいんですけど、ggってもあんまり出てこないので書きました。

自動化する

これをテストしたぃょ・・・

public class Calc{
    public void Solve(){
        var item=int.Parse(Console.ReadLine());
        Console.WriteLine(item);
        return;
    }
}

適当なテキストファイルにテストケースの入力と出力を一行開けて並べるだけで全部チェックしてほしぃょ・・・

123

123

456

456

↑のテキストファイルをtestcase.txtという名前でプロジェクトに追加して、以下の単体テストを実行すると・・・????

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Text;
using System.IO;

namespace CS_AtCoder.Tests {
    [TestClass()]
    public class CalcTests {
        private static bool[] active = new bool[4] { false, false, false, false };
        private static StringBuilder[] expected = new StringBuilder[4];
        private static StringBuilder[] Stdin = new StringBuilder[4];
        private const string testcasePath = @"..\..\test_case.txt";
        Calc Target = new Calc();
        [ClassInitialize()]
        public static void ClassInit(TestContext tc) {
            int cnt = 0;
            string s;
            StreamReader sr = new StreamReader(testcasePath);
            while (sr.Peek() >= 0 && cnt < 4) {
                var sb = new StringBuilder();
                while ((s = sr.ReadLine()) != "" && sr.Peek() >= 0) {
                    sb.Append(s);
                }
                Stdin[cnt] = sb;
                StringBuilder expect = new StringBuilder();
                if (sr.Peek()<0) break;
                while (sr.Peek() >= 0 && (s = sr.ReadLine()) != "") {
                    expect.Append(s);
                }
                expected[cnt] = expect;
                active[cnt] = true;
                cnt++;
            }
            sr.Close();
        }

        private Tuple<StringWriter, StringReader, StringBuilder> MakeRedirect(int index) {
            var sb = new StringBuilder();
            var sr = new StringReader(Stdin[index].ToString());
            var sw = new StringWriter(sb);
            Console.SetIn(sr);
            Console.SetOut(sw);
            return new Tuple<StringWriter, StringReader, StringBuilder>(sw, sr, sb);
        }

        //↓と同じようなのを好きなだけ並べる
        [TestMethod]
        public void Case0() {
            int index = 0;
            if (!active[index]) { Assert.IsTrue(true);return; }
            var item = MakeRedirect(index);
            Target.Solve();
            item.Item1.Close();
            item.Item2.Close();
            var actual = item.Item3.ToString().Replace("\r\n", "");
            Assert.AreEqual(expected[index].ToString(), actual);
        }
    }
}

unittest1

やったあぁぁぁ

やってること

やってることはただ単にStringBuilderに入力と出力を格納し、入力の方をStringReaderとしてstdinに割り当てる。
もう一つStringBuilderを用意し、それをStringWriterとしてstdoutに割り当てる。これをほしい出力を格納したStringBuilderAssert.AreEqualしてあげるだけです。

ちなみにテストケースは5つ以上できません。
まあでもだいたい5つもないしな~みたいなw
余ったテストメソッドはIsTrueでOKだします

もう少しいい書き方もあるだろうけど、これで動くのでこれでいいです。 テストケースをコピペするのもなんか自動化できないかな~とか思いますけどまぁこれでいいです。

よーし!めっちゃはかどりそうです!

AAの画像作ってくれるSlackBotつくった

Pythonむずい

勉強が足りない

AAは面白いが崩れる

AAって面白いですよね。

dic.nicovideo.jp

でも2chの専ブラとかでないと本来の見た目でなかったり、そもそも崩壊していたり・・・。

slackbot1
ブーム君のAA

Slackでもそれは例外でないです。
この間某Slackで「いよいよ明日は~」というコピペをもじった告知がありました。
slackbot2
↑こういうやつです。

画像の通り、返信には「今日と明日だよ・・・」と返すのがそれっぽいと思うんですが・・・(押しつけ)
これをそのままSlackに張り付けると
slackbot3
こんなふうにちょっとずれます。
これはこのままでも見れないこともないですけど、大きなAAになるとめちゃめちゃになります。
特にスマホとかだと確定でめちゃめちゃになります。
slackbot4
めちゃめちゃになったAAの例

AAは モナーフォント で見せるようになっている(?)ので普通のフォントでは崩れるのは当然なのですが・・・

IPAモナーフォント

崩れてしまうと何がなんだか分からずに白けてしまうので何とかしたい・・・
しかし全員にこのフォントにするように言うのはなんか違いますよね。

文字列がダメなら画像にしちゃう

画像にAAを張り付けてUpすれば全員が同じ崩れていないAAを見ることができます。
でも普通の人はそんな手間なことしませんし、そんなことしてたら会話の流れが過ぎ去っていきます。オタクの会話はスピードがはやいですから。

ここはSlackBot AAGenBot とでも名付けて、コイツにAAを画像化するのをやってもらいましょう。
slackbot5
AAGenBotのイメージ1

こうすればAAをコピペするだけでAAがずれることなくみんな見れます (実際はテキストが張り付けられるのでタイムラインがガーッと流れたりしますが・・・)

他にも特定の発言に対してそれっぽいAAを張り付ける機能も欲しいです。
slackbot6
AAGenBotのイメージ2

こうすればいちいちAAをコピペせずともBotが勝手にAAを貼ってくれます(邪魔だという意見もありそうです)

つくってワクワク

使った言語はPythonBashシェルスクリプトです。
SlackAPIを使ってメッセージの送受信と画像の生成をするのがPythonのお仕事で、それっぽいAAの画像を探したり、AA用のテキストファイルを用意したりするのをBashシェルスクリプトでやります。
Pythonはやったことなかったのでワクワクしてます

だいたいの流れはこうです
slackbot7

SlackAPIの利用にはpython-slackclientを使いました
github.com
Slackのサイトからテキトーにクリックしたら見つかったのでこれにしました(頭わるい感)
Pythonで画像を生成するにはPillowを使いました
https://pillow.readthedocs.io/en/4.1.x/pillow.readthedocs.io
なんかPILとか呼ばれてたやつ?っていうかそれのforkらしい?(詳しく調べてない)

slackbot8

つくります

できました

github.com
色々説明は面倒なのでこれをみてね

py/SlackMessageGetter.pyでメッセージを受信(送信もするけどね)して機能分け
py/aagen.pyでAAを生成 sh/search.shsh/aaImageSearcher.shで発言に合いそうな画像をさがします。二つに分けたのはsh/search.shの方をパイプに挟みたかったからです。 このsh/search.sh形態素解析とか結構謎深い検索方法をしているので興味があれば見てみてください。
sh/FileUploader.shcurl使って画像をアップロードします。なぜか前述のpython-slackclientではうpできませんでした・・・

またsh/FileUploader.shではある程度Botの画像うpを自重するようにしてます。

  1. 30秒以内に画像をうpしてたら自重
  2. 候補の画像を30分以内にうpしてたら自重

これで自重できてるのかは・・・わからん・・・な

インストールのしかた

必要なものをapt-getとかpip3します。python3がなければそれもいれてね

# mecab  ファイルサーチにつかっちゃったw
sudo apt-get install mecab
# もとPIL
sudo pip3 install Pillow
# Slackのライブラリ
sudo pip3 slackclient
  • メモ:普段あんまり使わなさそうなコマンドとか
  • jpegtopnm
  • pnmquant
  • ppmtosixel
  • これらは生成した画像をターミナルに表示するのに使っています。Sixelに対応しているターミナルでないとダメなのでいらない場合はsh/aaTextRedirector.shの25行目をコメントアウトしてOKです

普通にgit cloneします

git clone https://github.com/xztaityozx/AAGenBot
cd AAGenBot

AAGenBotはシェル変数からTokenやユーザ名、フォントへのパスを読み取るのでexportします

export SLACK_API_TOKEN="xxxx-xxxxxxxxxxxxxxxxxxxxxxxx" #APIトークン
export AAGENBOT_NAME="<@Uxxxxxxxxxx>" #Uから始まるBotのID?名前?みたいなやつ、<@ >で囲むこと
export AAGENBOT_MASTER_NAME="Uyyyyyyyyyy" #Uから始まる自分の名前?ID?。Botを管理する人の名前?ID?
export monafontPath="/home/user/.fonts/ipagp-mona.ttf" #AA作るときに使うフォント 絶対パスで書かないと見つからない?

~/.bashrcとかに書いておくと楽ちんです

シェルスクリプトに実行権限がなかったらchmodしておきます。

chmod +x sh/*

これだけだよー(^o^)/

起動方法

AAGenBotディレクトリ以下で

sh/Starter.sh

とするだけだよー(^o^)/

つかいかた

使いたいチャンネルにAAGenBotを参加させます。

生成

AAGenBotに対して生成してほしいAAの名前とAA本体を発言します
フォーマットは

@aagenbot [AAの名前]
[AA本体]

とします
slackbot9

こんなふうに結果がうpされます

ちなみにAAの名前はそのままファイル名に使われるので*/が入ると癇癪というかエラーを起こします。

呼び出し

AAGenBotに対して呼び出したい画像の名前をcallキーワードの後に付けて言うとうpしてくれます
slackbot10
こんな感じですね。
名前が間違ってるとうんともすんとも言いません(不親切)

発言に反応

これはAAGenBotに特に何か言うということはないです。
ユーザー同士の会話の中を勝手に読み取って適度にAAを張り付けます。
slackbot11
こういった感じですね

slackbot14
こんなふうに複雑な文章でもそれっぽいのを見つけてきます。
これはなんかアルゴリズムを使ったとかじゃないです。シェル芸を使ってます。(sh/search.shを見ればわかると思います。)
前述のとおり 自重システム がが備わっているので会話の流れが汚染されることは無いと思います()

終了

AAGenBotのMASTER(Botを管理するやつ)限定でSlackからAAGenBotを終了できます。
デバッグ用だったんですけど残しました。
slackbot12
こうです。煽ってるわけではないです

まとめ

Pythonはじめてでしたけどなかなか楽しかったです。またなんか作りたいですね・・・
最初は全部Pythonでやるつもりだったんですが
シェルスクリプトのが楽やわ!w
ってなったところはシェルスクリプトにしました。その結果Pythonシェルスクリプトが交互する意味の解らない構造になりましたがまあいいでしょう

まだ某Slack内で使ってないのでどれほど実用に堪えるのかわからないのですが、書く記事もちょうどなかったので書きました。
まだ生成失敗したときとか呼び出し失敗とか、エラーをユーザー達に伝えるような部分できてないのでその辺をもうちょっとだけメンテしてから某Slack内で公開したいです。どきどきする・・・

ちなみにこのAAGenBot副作用があります。
slackbot13
ALLFilesのところがめちゃめちゃになる

それでは良い AAライフ を!

参考

既知のバグ

  • 画像をUpしたあともう一度sh/FileUploader.shが呼ばれる。1行目からエラーが出て終わる
  • 下の方がはみ出る。謎

果物も食えないこんな世の中じゃ

ポイズン

最近はコタツを付けると熱いが消すと寒いので、雨が降ってほしいと願うばかりである。花粉も落ちるし。

更新をサボってたわけじゃないですよ。記事を推敲しまくってたんです(嘘)

果物を食うとだいたいのどが痒くなる

これわかる人いますか。他にもモヤシや火の通ってない豆腐などでも喉がかゆ~くなるやつです。
(僕の場合ですが)具体的に言うと のどちんこ のすぐ手前のあたり 軟口蓋 周辺がモーレツに痒くなります。(いまモーレツ宇宙海賊が予測変換に出てきて懐かしくなりました)

モヤシの場合は少しややこしくて、太めのモヤシ(緑豆や大豆モヤシ)はダメですが細めのモヤシ(黒豆モヤシ)はOKなんです。
他には高野豆腐もダメですね。
生の果物だけかと思ったら実は結構いろはすももでも来てるんですよね・・・
irohasu

だいたい花粉症のせい

僕は白樺の花粉症持ちなんですが、どうやらコイツがダメっぽいですね。
www.newsdigest.de

さらにブタクサもOUTなのでポピュラーな果物はほぼ死滅して、食えるのはブドウ、ミカンぐらいです。(バナナはなんかOK)
火を通せばだいたいのモノが食えるようになりますが・・・

わざわざ火なんか通してられるか!

という話です

かといって

かまわん!食ってやる!!

と食ってしまえば当然痒くなるので泣く泣く我慢しています。

しかし果物がダメなのはわかりましたが、大豆系がダメなのがよくわからなかったのでggりました
allabout.co.jp

どうやらここによると僕は クラス2の食物アレルギニスト のようですね  そしてその2ページ目

花粉症との関係についてですが、シラカンバ、ハンノキなどのカバノキ科の植物の花粉には、PR-10というアレルゲンタンパクが含まれています。このPR-10 と似たアレルゲンが大豆にも含まれているため(大豆の アレルゲンタンパクは「Glym4」)に、カバノキ科の花粉症患者の中には、豆乳等を飲んだ時にアレルギーを発症するケースが見られるのです。

Ah…
なるほど納得です(納豆は大丈夫)

モモが食べたい

僕の場合は 食べられへん!!! というほどではないので食べることはできますが
無理に食うことで 、これが悪化してしまうと今度はまだ発症していない食べ物にも反応してしまうかもしれない ので控えています。ジャガイモなんかが発症したらえらいことですからね

花粉症がある人は一度ggってこういうのがないか確認してみた方がいいかと思います。

!!????!??????! !!?????!??!!?? !!?????!?!?!!! !!????!????!!! !!?????!!!!!?? !!?????!?!?!!! !!????!????!!! !!?????!!!!!?? !!?????!?!!??! !!????!???!?!!

!!????!!?!!!?? !!????!?!??!?? !!????!?!!!??! !!????!!!?!!?! !!????!?!??!?? !!????!!??!??! !!?????!??!!?? !!?????!!!!?!! !!?????!?!?!!! !!?????!???!??

!???!?!????!!??? !??!!!?!???!?!! !!?????!!?!??? !!?????!??!?!! !???!?!?!?!?!!?! !!?????!!!!!!? !!?????!?!!?!! !!?????!?!!!!! !!?????!???!??

!!?????!?!??!! !!?????!!?!!!? !!??!?!!????!!! !!!!?!?!!!????? !!?????!??!!?? !???!?!?!?!?!!?! !!????!??????! !!?????!!??!!? !!?????!???!?? !!????!???!?!! !!?????!!?!??? !!?????!???!?? !!?????!???!!? !!?????!?!??!! !!?????!!?!??? !!?????!!?!!!!

!!?????!?!??!! !!?????!!?!!!? !!??!?!!????!!! !!!!?!?!!!????? !!?????!??!!?? !???!?!?!?!?!!?! !!????!??????! !!?????!!??!!? !!?????!???!?? !!????!???!?!! !!?????!!?!??? !!?????!???!?? !!?????!???!!? !!?????!?!??!! !!?????!!?!??? !!?????!!?!!!! !!???????????! !?!?!?????!!?!! !!?????!!?!!!! !!?????!!?!??? !!?????!!???!! !!?????!!??!!? !!????!?????!? !!??!!?!????!!! !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!!?!!?! !!!!!!!!???!!!!!

!!????!?????!? !!?????!!????! !!????!???!!?! !!????!??!??!! !!???????????! !!?????!???!?? !!?????!!!!!!? !!?????!?!??!! !!????!???!!?? !!????!??!??!? !!??!!?!!!!!??? !!?????!???!?? !!?????!!??!!? !!?????!???!?? !!????!???!?!! !?!????!!?!?!?! !!????!?????!? !!??!!?!????!!! !??!!!?!?!!!?!? !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!?!???! !!?????!!?!??! !!?????!!?!!?!

!!?????!!!?!!? !!?????!!???!! !!?????!!????! !!????!?????!! !!?????!?!???! !??!!!!?!?!?!?! !!!?!?!?!!?!?!? !!!???!?!??!!!? !!?????!?!!??? !!?????!!????? !!????!???!??? !!?????!!???!! !!?????!!??!!? !!????!!??!!?! !!????!?!!!!!! !!?????!!??!!! !!?????!?!!??! !!?????!??!!?? !!?????!!?!!?!

!!?????!?!??!! !!????!??!??!! !!?????!!?!?!? !!?????!?!??!! !!?????!!?!??? !!????!??!??!? !!?????!?!??!! !!?????!?!??!! !!?????!!?!?!! !!??!!?!!!!!??? !!?????!??!!!! !!?????!!?!!!? !!?????!!?!!!! !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!?!???! !!?????!!?!??! !!???????????! !!?????!?!??!! !!?????!!?!!!? !???!?!????!!??? !??!!!?!???!?!! !!????!??!??!? !???!?!?!?!?!!?! !!????!??????? !!?????!!?!?!! !!?????!!?!!!! !??!!!?!!!??!?! !??!!!?????!?!! !!?????!!?!!!? !!????!?!!?!!! !!????!?!??!!! !!????!!!?!?!! !?????!?!?!!!??? !!?????!??!!?? !!??!!!????!??! !?!??!?!?!!!??! !!?????!!????? !!????!???!???

!!??!?! !!???!! !!?!??? !!?!!!! !????? !???!? !!????!?????!? !!?????!?!!??? !!????!???!!?? !!?????!!??!?? !???!? !!!!!?? !!!??!! !!??!?! !!??!?? !????? !??!!! !!!??!! !?!!!! !!???! !?!!!! !????! !?!!!! !!??!!! !!!?!! !!!??!! !?!!!! !!???? !?!!!! !!!!!! !?!!!! !!??!!! !!!?!! !!!??!! !?!!!! !????? !?!!!! !?!!!?? !!?!!!? !?!!!! !!??!!! !??!!! !!!!!?? !!????! !!!?!!! !!?!?!! !????? !??!!! !!!!?!! !!!???? !!!??!? !!?!??! !!?!!!? !!!?!?? !????? !???!? !!?!??! !!???!? !!????! !!!??!! !!??!?! !!!!?! !!??!? !!!?!! !???!? !??!?? !!???! !!!!!?! !??!!! !!!!!?? !!???!? !!???!! !!!!!?? !!????! !!!?!!! !!?!?!! !????? !??!!! !!!!!?? !!!???? !!!??!? !!?!??! !!?!!!? !!!?!?? !!??!!? !????? !???!? !??!?! !!???!! !???!? !?!!?? !??!?? !!???! !!!?!! !!??!?! !!?!??? !!???!! !!?!!!!

!!?????!????!? !!?????!!?!??? !!?????!!?!!!! !?????! !?!??!! !????!! !??!??! !??!??! !!?????!!?!?!! !?!!??!????!??! !!???!!!!?!!?!! !!?????!?!!??! !!????!???!?!! !!?????!!????? !!?????!?!???! !!?????!!??!!! !!?????!?!!??! !!?????!!?!!?! !!!!!!!!???????!

!??!!!!?!?!?!?! !!?????!!?!!!? !!?????!?!!!!! !!????!??????! !!?????!!?!!!? !???!?!????!!??? !??!!!?!???!?!! !!?????!!?!?!? !!?????!!?!!!? !!!!!!!!???!!!!!

!!????!?????!? !!?????!??!!!! !!?????!!??!!? !!?????!??!!?! !!?????!!?!!!! !!?????!!?!??? !!?????!??!!!! !!?????!!?!?!! !!?????!!?!?!? !!?????!???!?? !!?????!!??!!! !!?????!?!!??! !!????!???!??? !!????!!!!!?!! !!????!!!!!?!! !!????!!!!!?!! !!!!!!!!???????! !!?????!?!?!!! !!?????!???!?? !!?????!!??!!? !???!?!????????? !!?????!???!!? !!?????!!?!?!? !!????!???!??! !!?????!!!???! !!?????!!???!! !!?????!!?!??? !???!??!!???!?!! !!?????!!??!!! !?!!!!!???!?!?! !!?????!???!?? !!?????!!??!!? !!?????!!!!?!! !!?????!?!?!!! !!?????!??!?!! !!?????!!???!! !!?????!?!!!!! !!?????!!????? !!?????!?!???! !!?????!!??!!! !!?????!?!!??! !!?????!!?!!?! !!!!!!!!???????!

!!?????!?!!!?! !!????!???!!?! !!?????!?!!!?! !!????!???!!?! !??????!!!!?!?!? !?!??!??????!!? !!?????!!??!!! !!????!?????!? !??!!!!?!?!?!?! !!?????!??!?!! !!?????!???!?? !!?????!!??!!? !!????!???!?!! !!?????!??!?!! !!????!???!!!! !!?????!??!?!! !!????!???!??! !!?????!!?!?!? !!?????!??!!!! !!?????!!?!?!? !!?????!!???!! !!?????!!??!!? !!?????!??!!?! !!?????!?!!!!! !!?????!!?!!!? !!?????!!??!!! !!?????!?!??!! !!?????!!?!!!? !???!!!!!?!!!?!? !!?????!!??!!! !!!!!?!?!????!? !!????!???!!!! !!????!???!?!? !!?????!!!!!!? !!?????!?!!??!

!!?????!?!!!?! !!????!???!!?? !!?????!!??!!! !!?????!!?!!!! !?????!??!!?!!!! !!?????!???!?? !??!!!?!???!!?? !??!??????!!??!? !!??!?!?!!!???? !!????!!!?!??! !!????!?!??!?? !!????!!?!?!?! !!????!??!??!? !!!!!!!!???????!

!?!??!!!!????!? !?????????????!!

!?????! !?!??!! !????!! !??!??! !??!??! !!????!?!!??!! !!????!!!!!!?? !!????!!??!??! !?!!??!????!??! !!???!!!!?!!?!! !!?!?!??!?!!!!!

世界一わかりやすい絶対パス

世界一は盛り過ぎた

みなさんcdx使ってますか。ぼくは使ってます。

xztaityozx.hatenablog.com

パスって見づらい

Linux上で作業をしていてたまに思うことがありません?僕は思います。
例えば
no1path1
こういうの

これってまだ見やすい方ですけど、長くなってくると意味わかんなくなるよね
それに同じ色だからどこが/なのか分かりにくいし正直長いパスでも後ろいくつかでわかるし
no1path2

お得意のシェルスクリプト

こういうのってcdxのときみたくggったら出てきそうなんですけど・・・
自分で作ればシェル芸人パワー上がるしいいよね

まずはパスの一番最後の要素 つまり

/home/taityo/testdir/dir
                     ↑これとか
/home/taityo/testdir/item.txt
                     ↑これ

これに対して色を付けてやります。
こいつは目的の場所、もしくは目的のファイルであるので目立つ方がいいですね。

#!/bin/bash

path=$1

bpath=${path##*/} #最後尾
fpath=${path%/*}  #その前

out_path=""

bpath="\e[1;33m$bpath\e[0;39m"

out_path="$fpath/$bpath"
echo -e  $out_path

\e[1;33m$fpath\e[0;39mは端末の出力に色を付けるものです。この場合だと黄色の太文字になりますね。echo 色付けとかでggるとでてきまする。
本当はsedとかawkとか使って一撃でやりたかったんですけど・・・
出力はこんなかんじ
no1path3
いいねいいね。

次は最後の要素から上二つまでのディレクトリに別の色をつけます。まぁ同じようにやってもいいんですが ダセー ので配列に展開してしまいましょう

#!/bin/bash

path=$1
arr=(`echo $path|tr -s '/' '  '`) #配列に展開
path_range=${#arr[@]}#要素をカウント

out_path=""

bpath="\e[1;33m${arr[`echo $path_range - 1 | bc`]}\e[0;39m"#最後の要素
bbpath="\e[1;36m${arr[`echo $path_range - 3|bc`]}\e[0;39m/\e[1;36m${arr[`echo $path_range - 2|bc`]}\e[0;39m"
echo -e $bbpath/$bpath

こっちのがいいですね~
ちょっとインデックス計算のところが変なことになってますが・・・
bcコマンドは文字列を計算式として計算してくれる(日本語が下手)コマンドです
exprより使い勝手がいいのでよく使います。

出力は
no1path4

今度は/home/から下3つまでの要素を省略しましょう。
パスが長くなるとターミナルを占領してしまって目がおかしくなるのでね
no1path5

省略する場合パスの要素が6個以上必要ですね~。

#!/bin/bash

path=$1
arr=(`echo $path|tr -s '/' '  '`)
path_range=${#arr[@]}

if [ $path_range -le 2 ]; then #パスが短かった時
    echo -e "/${arr[0]}/\e[1;33m${arr[1]}\e[0;39m"
    exit;
fi

out_path=""

bpath="\e[1;33m${arr[`echo $path_range - 1 | bc`]}\e[0;39m"
bbpath="\e[1;36m${arr[`echo $path_range - 3|bc`]}\e[0;39m/\e[1;36m${arr[`echo $path_range - 2|bc`]}\e[0;39m"
if [ $path_range -ge 6 ]; then
        bbpath="home\e[0;39m/.../$bbpath"
else
        path_range=`echo $path_range - 4|bc`
        while [ $path_range -ge 0 ]; do
                bbpath="${arr[$path_range]}/$bbpath"
                path_range=`echo $path_range - 1|bc`
        done
fi
echo -e /$bbpath/$bpath

こんなかんじになるよ
no1path6
例が悪いから良くなった感があんまないな・・・

で?

これどこでつかうんだ?

一応これを~/.bashrcとかに書いておけばパスを呼び出すときとかに通してやれば綺麗になりますよ!
no1path7

快適な パスライフ を!

最強のcdコマンドを目指して

cdで無限の可能性を

集え!OITer!!! OIT Advent Calender 2016!!!!

この記事は、 OIT Advent Calendar 2016の5日目の記事です。

5日目の今日も隊長がお送りします。


皆さんcdコマンド使ってますか。
cmdにもシェルにもふつーにありますよね。
lsとかとならんでめちゃ使うコマンドの一つだと思います。

よく使うのでcdは快適であってほしいです。
lsコマンドはオプションが沢山あり、よく使うls -aとかls -lなんかはエイリアス登録して使ってる人も多いんじゃないですか?
しかしcdmanを見ると・・・

http://ss64.com/bash/cd.htmlss64.com

オプションが二つしかありません。(そのうち一つはデフォルトでON)

これってこれ以上快適にできないってことじゃん!!!!

そうです。

ぼくのかんがえたさいきょうのcdこまんど その名は cdx

  • 履歴がある
  • 表示がカッコいい
  • ブックマークがある
  • ディレクトリが無かったら作るか訊く
  • ひとつ前にいたディレクトリにすぐもどれる
  • 探し物してるときに自動でlsしてくれる

_人人人人人人人人人人人人_
> そんなんありません! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

(たぶん)

なら自分でつくろうか

cdコマンドだけでも全く問題はないんですが
僕みたいに車輪の再発明をした後、その車輪にスパイクとか発光するやつとかをつけちゃうような奴はこういうことが大好きです。
まぁcdxみたいなのはないと思うので車輪の再発明にあたるかは微妙ですが・・・


(2016/12/05 13:55 追記)cdxみたいなのありました・・・!こっちのがすごそうです! qiita.com (追記終わり)


車輪の再発明(しゃりんのさいはつめい、英: reinventing the wheel)は、車輪を題材にした慣用句であり、世界中で使われている。「広く受け入れられ確立されている技術や解決法を知らずに(または意図的に無視して)、同様のものを再び一から作ること」を意味する。 -車輪の再発明 Wikipedia

使う言語はbashです。

履歴機能

まずは cdx に履歴を付けます。
履歴はいつでも参照できて、番号指定でそこへ移動できるようにします。
パスをどこかファイルへechoしておいてそれを読み出すようにします。

if [ $cdx_option == "-h" ]; then
    line=$2
    if [ "$line" == "" ]; then
            line=`cat ~/.cdx_history|wc -l`
            lineStart=`echo $line - 10|bc`
        if [ $lineStart -lt 1 ]; then
            lineStart=1
        fi
        cat -n ~/.cdx_history|sed -n "$lineStart,${line}p"
        cd_flag=0
    elif [ $line == "clear" ]; then
        echo "" > ~/.cdx_history
    else
        change_to=`cat ~/.cdx_history | sed -n "$line,${line}p"`
    fi
fi
###中略###
#cdするまえに
echo `pwd` >> ~/.cdx_history

実はC#Jsonを使って書いたんですけど思いのほか長くなってしまった・・・のでやめました・・・。
これで~/.cdx_historyにどんどん履歴がたまっていきます。

cdx.sh -h 1

とすると履歴ファイルの1行目へ移動します。

cdx.sh -h

とだけ打ち込むと最新の履歴10件を表示して終了します。
無限に履歴をため込むのでときどきcdx -h clearとしてやるといいですね。
cdx5
(エンコードの調子が悪い)

表示をかっこよくする

ぼくはまだ中二病を卒業できてないところがあるので表示がカッコいいとテンションが上がります。
なのでcdした後に単に移動先のディレクトリをpwdするだけじゃなくて色々表示しちゃいます。

if [ $cd_flag = 1 ]; then
    bef_dir=`pwd`
    cd $change_to > /dev/null
    if [ $? = 1 ]; then
        echo -e -n "${change_to}\nが見つかりませんでした。\n作ろうか?(y/n):"
        read ans
        if [ $ans == 'y' ]; then
            mkdir $change_to
            cd $change_to > /dev/null
        fi
    else
        echo -e "  ${clr_Black}--> ${clr_main}cdx ${clr_Black}: ${clr_green}$bef_dir${clr_Black} ->>>${clr_green} `pwd`${clr_reset} "
    fi
fi

さっきの部分のelse側はcdに成功した場合に通りますのでそこでカッコイイ表示を出してやります。
これがカッコよく感じるかは個人差があるのであれですが・・・

ブックマーク機能

次はブックマークを作ります。
いつもアクセスするディレクトリとかにはできるだけ短いキーストロークで移動したいもんです。
たとえば

cdx 0 #ブックマークの0番目に移動

とこんな具合に

まずブックマークは ~/.bashrcに記述するようにします。
こんな感じで

#~/.bashrc 
#cdx bookmark
export cdx_bookmark=("ブックマーク0" "ブックマーク1")

環境変数cdx_bookmarkを作成してそこにブックマークしておきたいディレクトリを記述します。

次にcdx.shにこれを読み出してインデックス通りに移動するように追記します。

#!/bin/bash

## cdx is hyper cd command

##color setting
clr_error="\e[1;31m"
clr_main="\e[1;35m"

cdx_bm_arg=$1
cd_flag=1
change_to=$HOME

if expr "$cdx_bm_arg" : "[0-9]+" > /dev/null  ; then
        if [ $cdx_bm_arg -ge ${#cdx_bookmark[@]} ] || [ $cdx_bm_arg -lt 0 ]; then
                echo -e "${clr_error}[ブックマークの範囲外です]"
                cd_flag=0
        fi
        #読み出し
        change_to=${cdx_bookmark[$cdx_bm_arg]}
else
        #引数のところへcd
        change_to=$cdx_bm_arg
fi

if [ $cd_flag = 1 ]; then
        cd $change_to > /dev/null
fi

これで. ./cdx.sh 0とかするとcdx_bookmarkの0番目を読み出してcdできるようになります。
でもこれだけだとなんか寂しいのでもう少し拡張します。

#!/bin/bash

#cdxの補完
_cdx_complete(){
    case $2 in
    [0-9]* )
        COMPREPLY=( `echo "${cdx_bookmark[$2]}"` )
        ;;
    *)
        ;;
    esac
}
complete -d -F _cdx_complete cdx

これは cdx の補完を行う記述です。Tabキー押したら発動するあれです。
cdx 0 としたとき 0 の部分に実際のパスを展開してくれます。
cdx1
こんな感じで
※書いただけでは使えません。

さらに 一時ブックマーク みたいなのも作りましょう。

$cdx_option=$1
if [ $cdx_option == "bm" ]; then
        export CDX_TMP_BM=`pwd`
        echo -e "${clr_green}Temporary BookMark${clr_Black} <-- ${clr_green}`pwd`"
        cd_flag=0
fi
if [ $cdx_option == "b" ]; then
        change_to=$CDX_TMP_BM
        cd_flag=1
fi

こうすると「きょうの作業ディレクトリはね~ここなの!」とcdx bmしてやれば
どこかに移動しててもcdx bですぐに戻れます。
cdx3

移動先のディレクトリが無かったら作るか訊く

これできたら結構うれしいですよね?
cdコマンドが「そのようなファイルやディレクトリはありません(冷酷)」とか言ってくると
無いんじゃなくてつくるんだよ!って感じます。
それでは

if [ $cd_flag = 1 ]; then
    cd $change_to > /dev/null
    if [ $? = 1 ]; then
        echo -e -n "${change_to}\nが見つかりませんでした。\n作ろうか?(y/n):"
        read ans
        if [ $ans == 'y' ]; then
            mkdir $change_to
            cd $change_to > /dev/null
        fi
    fi
fi

cdx.shの最後のif文の中をこういう風に変えます。
読んだら分かりますがcdに失敗したら訊いて作って移動します。great
cdx4

ひとつ前にいたディレクトリにすぐもどれる

pushdpopdというコマンドがあるのを知ってますか?
これpush,popってところから想像つくと思うんですけど、移動するときにスタックにパスを残しておけるんです。
なのでpopdを使えば一瞬でひとつ前のディレクトリに移動できるわけです。

しかし
みなさんここでお手元のキーボードをご覧ください。
無い方のために画像用意しました。
keyboard
試しにpopdって打ち込んでみてください。

やりました?

結構キーストローク長くないですか??え?そうでもない?
ならpushdはどうですか?

うん長いね?
それにディレクトリ変えたいだけなのにいくつもコマンドあるとか無くない?

なのでcdxに含んでしまいましょう。
といってもさっきまでのスクリプト内のcdpushdに変えて、popdcdx -pという形で呼び出せるようにするだけです。

if [ $cdx_option == "-p" ]; then
    bef_dir=`pwd`
    popd
    if [ $? = 1 ]; then
        echo -e "  ${clr_Black}--> ${clr_main}cdx ${clr_Black}: ${clr_green}$bef_dir${clr_Black} ->>>${clr_green} `pwd`${clr_reset} "
    fi
    cd_flag=0
fi

正直cdx -pってpopdよりなげーじゃんって思ってますけどいいんです。
ひとつにまとまっている。これが重要です。

そしてこれpopdってコマンドラインから打ち込んでもちゃんとひとつ前のディレクトリに移動できます。(当然だけど)

探し物してるときに自動でlsしてくれる

皆さんLinux上で探し物するときcdlscdlscdls→・・・ってしません?。僕はします。
探し物をしているときっていうのはcdlsというステップを踏みます。踏め!
なので何回目か以降はcdxが自動的にlsしてくれるようにします。
さて、探し物しているかどうかをcdxは判断しないとダメなんですが・・・それには~/.bash_historyを使います。

history -a
line=`cat ~/.bash_history|wc -l`
lineStart=`echo $line - 6|bc`
if [ $lineStart -lt 1 ]; then
    lineStart=1
fi
if [ `cat ~/.bash_history|sed -n "$lineStart,${line}p"|grep "ls"|wc -l` -ge 2 ]; then
    echo -e "${clr_main}探し物ですか?${clr_reset}"
    ls -la
fi

pushdに成功した後のelseにこれを突っ込みました。
まず、~/.bash_historyを更新します。その次に~/.bash_historyの下から6行を見てlsが2つ以上あればls -laを実行します。
なんだかハイテクですよね。
cdx6
(エンコードの調子わる)

作ったコマンドを使えるようにする

このままでは呼び出すときに

. [cdx.shまでのパス] [もろもろの引数]

としないと使えません。
cd系のコマンドをスクリプト内で使ってもスクリプト内しかディレクトリが変わらないのでsource(略系は .)コマンドで反映するようにします。
でもこれ 明らかに面倒

なので~/.bashrcをごにゃごにゃします。

#~/.bashrc

cdx()
{
    . [cdx.shまでのパス] $@
}
. [complete.shまでのパス]

関数として定義してやればいいのです。
さらにcomplete.shも同時にsourceしてやれば補完ができるように!

うーんgreat

最後に

いかがでしたか?cdx、魅力的なコマンドになったと思いませんか?
今回ぼくはcdxをこの記事に合わせて作り直しました。(作り直す前のはここ
結構気に入っててふつーに使ってます。みんなも使ってくれたらうれしいです。

一応オプションのこととかまとめておきます。

オプション 引数 説明
-p なし popdが呼ばれる
-h なし 最新の移動履歴10件が表示される
-h 番号 番号の履歴へpushdする
なし なし $HOMEへpushdする
なし パス パスへpushdする
b なし 一時ブックマークへ飛ぶ
bm なし カレントディレクトリを一時ブックマークする
なし 番号 ブックマークを読みだしてpushdする

シェルスクリプトはかなり自由なのでホントいろいろできます。既存のコマンドもこうすればよりよくなります。
それに書き方も単純です。型がないですし楽です。Cが書けてggれる人なら簡単に書けますYO!

今回作ったcdxコマンドはここにあります。ぷるりくもお待ちしてる。
github.com
適当にgit cloneしたりzipとかでもってきて↑のように~/.bashrcをごにゃごにゃすれば使えるようになります。

それでは良いcdxライフを!