Pythonでオリジナルのコマンドを実装してみる【前編】

# はじめに

こんにちは。

たまに

```
pip install hoge
```

でインストールすると、**コマンドになるやつらいますよね？**

……要するに。
例えば、scikit-learnの場合は、

```
$ pip install scikit-learn
```

でscikit-learnをインストールした後、Pythonで参照するためには

```:hoge.py
from sklearn import ……
```
と書けばいいのですが、これは言い換えると**「Pythonで使える新しいライブラリをインストールした」**ということです。

一方で、例えば……
Jupyter Notebookを使いたい場合。

```
$ pip install jupyter
```
で使えるようになるわけだけど、こちらはfrom jupyter……みたいな使い方をするわけじゃなく、コマンドラインで

```
$ jupyter notebook
```
と打ち込むと、Jupyter Notebookというアプリがブラウザに立ち上がる。
これは言い換えると、**「pipを使うと新しいコマンドが使えるようになる！」**と解釈することもできる。

これって結構高度なことやってる……
と思ってたけど、案外簡単だったので共有します。

## 目標
macまたはLinuxのコマンドラインで

```
$ MyCommand
```

と打ち込むと

```
ジャンプで連載中のチェンソーマンが好き
```

と出力されるようになる。

```
$ MyCommand --help
```

と打ち込むと、

```
ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド
```
と、MyCommandの説明が出力される。

……今回Windowsは対象外です。ごめんね。
ただ、Windowsでanaconda promptとか使ってる方なら参考になると思います。

# 事前知識
そもそも

```
$ jupyter notebook
```
でなぜJupyter Notebookが立ち上がるのか？という仕組みをご説明します。
さっそく場所を確認。

```
$ which jupyter
/anaconda3/bin/jupyter
```
出力結果は環境構築方法によって違うはず。
僕の場合はanaconda3フォルダ直下のbinの中でした。
ってことで中身を観てみましょう。

```
$ vi /anaconda3/bin/jupyter
```
中身は、実は(……当然？)Pythonです。

```python:jupyter
#!/anaconda3/bin/python
  
# -*- coding: utf-8 -*-
import re
import sys

from jupyter_core.command import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())
```
なんとこれだけです。

ちょっと余談入ります。
面倒な方は読み飛ばしてください

【余談】
この書き方……つまり、「コマンドの本体は余計な要素を削るだけで、そのまま必要な処理のメインモジュールを呼び出す」という書き方は他のコマンドもちまちま見る限り「お約束」のようです。
それもそのはず、コイツはpip install時に自動生成されています。詳細は後編で。

ちなみにjupyter_coreはどこにいるかっていうと、これも環境ごとに違うはずですが、実はこんな場所にいます。

```場所を確認してみよう_1.py
import jupyter_core
print(jupyter_core.__file__)
# /anaconda3/lib/python3.6/site-packages/jupyter_core
```

この場所は、実はpipでinstallしたライブラリが入ってる場所と同じです。

```場所を確認してみよう_2.py
import sklearn
print(sklearn.__file__)
# /anaconda3/lib/python3.6/site-packages/sklearn/__init__.py
```
要するに、Jupyterも「サードパーティ製ライブラリの格納庫であるsite-packagesに『Jupyterそのものにとって必要な』ライブラリがインストールされる」という意味合いでは、scikit-learnとそこまで変わらないって話でした。
【余談ここまで】

今回は面倒なことは抜きにして、「とにかく雑に」Pythonでコマンドを作ってみましょう。

さっきの

```python:jupyter
#!/anaconda3/bin/python
  
# -*- coding: utf-8 -*-
import re
import sys

from jupyter_core.command import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())
```

で重要なのはこの部分。

```pythonn:jupyter
#!/anaconda3/bin/python
```

さて、これはなんでしょうか？
……そう、ただのコメントですね。


<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>


嘘です。
シェル(スクリプト)書いたことある方には常識かもしれませんが……

試しに#!を#に書き換えてJupyterを実行してみましょう。（意味が分かる方以外は真似しないでくださいね！）

```
$ jupyter notebook
```
結果はこうなります。

```
/anaconda3/bin/jupyter: line 4: import: command not found
/anaconda3/bin/jupyter: line 5: import: command not found
from: can't read /var/mail/jupyter_core.command
/anaconda3/bin/jupyter: line 10: syntax error near unexpected token `('
/anaconda3/bin/jupyter: line 10: `    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])'
```

line4-5では「そんなコマンド無いっすよ」って言われてて、line10では「文法エラーだよ」って言われてます。
つまり、最初の1行がなければ「こいつは.pyファイルだよ」って認識してくれないわけですね。
(厳密に言えば「こいつはPythonコマンドで実行するファイルだよ」と認識してくれない)

この#!から始まるコメントっぽい部分、**"シバン"**と言います。
そう言えば版画のこするやつが似たような名前だったなぁと思って調べ直してみましたが、あれは"バレン"でした。あまり似てなかったです。

閑話休題。

"シバン"はシェル(スクリプト)書いたことがある方ならだいたい知っていると思います。
詳しくはこちらで。

**\#!/bin/sh は ただのコメントじゃないよ！ Shebangだよ！**
(https://qiita.com/mohira/items/566ca75d704072bcb26f)

ここまで分かればあとはPythonで処理を書くだけです。
実装してみましょう。

# 実装してみよう

環境変数いじるの面倒なので、jupyterコマンドがあるディレクトリと同じディレクトリにそのまま突っ込んじゃいましょう。
もちろん、ローカルで作ったあとアップロードしても良いです。

僕はこんな感じで作りました。

```
$ cp /anaconda3/bin/jupyter /anaconda3/bin/MyCommand
$ vi /anaconda3/bin/MyCommand
```

```python:MyCommand
#!/anaconda3/bin/python
  
# -*- coding: utf-8 -*-
print('ジャンプで連載中のチェンソーマンが好き')
```

これだけで、

```
$ MyCommand
```

と打ち込むと

```
ジャンプで連載中のチェンソーマンが好き
```
がクリアできました。

# オプションを実装してみよう
次は

```
$ MyCommand --help
```

と打ち込むと、

```
ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド
```
と、MyCommandの説明が読めるように実装してみましょう。
標準ライブラリのsysを使えば一瞬です。
コマンドラインで呼び出した場合、半角スペース区切りでリスト：sys.argvに格納されます。

```python:MyCommand
#!/anaconda3/bin/python
  
# -*- coding: utf-8 -*-
import sys

if(len(sys.argv) < 2):
    print('ジャンプで連載中のチェンソーマンが好き')
elif(sys.argv[1] == '--help'):
    print('ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド')
else:
    print('そこまでは実装してないんだ、ごめん')
```

なかなかに頭が悪いコードですが、とにもかくにも実装できました。

ちなみに、sys.argv[0]は

```0.py
print(sys.argv[0])
#/anaconda3/bin/MyCommand
```
コマンド本体のフルパスが格納されてます。

……と、ここまで書いたのですが。
実はもっといい方法があります。コマンドラインパーサーのclickを使う方法です。
こちらが詳しいです。
https://blog.amedama.jp/entry/2015/10/14/232045
こちらも後編で記載いたしますね。

# 何が嬉しいの？

まず、「Pythonはできるけどシェルスクリプト書けないよ！」って人がシェルスクリプトを書く必要がなくなります。複雑なシェルスクリプトって難しいですよね……
もちろん速さやパフォーマンス等が問われる面では考慮が必要ですが、「シェルスクリプト使えないけどパパっと定時ジョブ作りたい」って場合にこの方法使うのは一つの選択でしょう。

それともう一つ、ちょっと応用になりますが、**「コマンドの配布」が可能になります**。

PyPI(Python Package Index)にパッケージを登録すると、pip install (パッケージ名) でPythonのパッケージがインストールできます。
PyPIには"Pythonで使えるライブラリが登録されている"と思いがちですが、Jupyterのことや当記事のことを前提に考えると、**「PyPIに登録すると、Pythonが入っている環境でさえあれば、自動的にオリジナルコマンドを配布できる」**ことも成立します。aptやyumでインストールできるようにパッケージを配布するより（Python環境が使える人にとっては）簡単でカスタマイズしやすいコマンドが配布できるってわけです。夢が広がりますね。

PyPIデビューは以下の記事を参考にしましょう。
pip installでコマンドを作る方法については後編に続きます。

**PyPIデビューしたい人の為のPyPI登録の手順**
(https://qiita.com/kinpira/items/0a4e7c78fc5dd28bd695)

お読みいただきありがとうございました。

こちら、後編です。
**Pythonでオリジナルのコマンドを実装してみる【後編】**
https://qiita.com/K_Takata/items/d062929e93ae43164c41