12 コマンドラインプログラム

これまでのところは、もっぱらMu(あるいは別のコードエディタ)からプログラムを実行してきました。本章では、コマンドラインターミナルからプログラムを実行する方法を説明します。コマンドラインは恐ろしく感じられるかもしれません。暗号のようなコマンドとユーザーに優しくない見た目から、ほとんどのユーザーはコマンドラインを避けています。しかしコマンドラインに慣れることは本当に有益であり、これまでに行ってきたプログラミングより難しいということもありません。

何らかの作業を自動化するPythonプログラムを書いたなら、そのプログラムを実行するたびにMuを開かなければならないのは負担に感じられるでしょう。コマンドラインを使えばもっと簡便にPythonのスクリプトを実行できるようになります(特に自分が書いたプログラムを、Muを持っておらず、ひょっとしたらPythonもインストールしていない友人や同僚に共有する場合はそうです)。ソフトウェア開発では、ソフトウェアをコードエディタ以外から実行できるようにする手順をデプロイと呼びます。

プログラムの別の呼び方

この章では(そしてプログラミングの世界一般でも)、プログラムを意味する用語がいろいろあります。以下の用語を全部プログラムと読んでも間違いではありませんが、意味合いに微妙な違いがあります。

プログラム 大きさはいろいろですが、コンピュータが実行できる指示から成る完全なソフトウェアを指します。

スクリプト マシンコードにコンパイルするのではなく、インタープリタがソースコードを実行するプログラムを指します。この語には曖昧な面があります。Pythonのコードは他の言語と同じようにコンパイルできるのですけれども(「PyInstallerでPythonプログラムをコンパイルする」で説明します)、それでもPythonのプログラムはしばしばスクリプトと呼ばれます。

コマンド テキストベースのターミナルから実行され、GUIを持たないプログラムを指します。コマンド実行時にコマンドライン引数を指定することにより設定を変更します(“Are you sure? Y/N”のような質問を実行中に表示する対話型コマンドもあります)。「cd、pwd、dir、lsコマンド」で紹介するdirやlsはコマンドの例です。

シェルスクリプト 一度に複数のターミナルコマンドを実行できるようにまとめられた一つのテキストファイルを指します。一つのシェルスクリプトを実行すれば、手動で複数のコマンドを個別に実行せずにすみます。macOSとLinuxでは、シェルスクリプトには拡張子.shをつけるか拡張子なしにします。Windowsでは拡張子.batでシェルスクリプト用のバッチファイルを作成します。

アプリケーション GUIを持ち複数の機能があるプログラムを指します。ExcelやFirefoxがアプリケーションの例です。アプリケーションは、通常、コンピュータにコピーできる一つの実行ファイルではなく、インストーラープログラムがそれぞれのコンピュータにセットアップする(アンインストーラープログラムで削除する)複数のファイルから成り立ちます。

アプリ 主にスマホやタブレット用のアプリケーションを想起させますが、デスクトップアプリケーションを指すこともあります。

ウェブアプリ ウェブサーバーで実行されるプログラムです。ユーザーはウェブブラウザを通してインターネット越しに利用します。

これらの用語の正確な定義について細かく言い出すときりがありません。ここでの説明はあくまでも一般的な使われ方を示しています。用語についてもっと知りたければ、Beyond the Basic Stuff with Python (No Starch Press, 2020)の“Programming Jargon”の章で詳しく説明しています。

ターミナルを使う

AppleとMicrosoftが複数のプログラムを同時に実行できるGUIを備えたコンピュータを普及させた1990年代まで、プログラムはコマンドラインインターフェイス(CLI)から起動し、入出力はほぼテキストに限られていました。CLIは、コマンドプロンプト、ターミナル、シェル、コンソールとも呼ばれます。ソフトウェア開発者は今でもCLIを使い、常に複数のターミナルウィンドウを開いていることも珍しくありません。テキストベースのターミナルにはアイコンやボタンなどGUIのグラフィックスはありませんが、コマンドの使い方を知っていればコンピュータを効果的に活用できます。

以下の手順でターミナルウィンドウを開けます。

  • Windowsでは、スタートボタンをクリックし(あるいはWindowsキーを押し)、コマンドプロンプト (インストールされているならPowerShellやTerminal)と入力します。
  • macOSでは、画面右上のSpotlightアイコンをクリックし(あるいは-spacebarを押し)、Terminalと入力します。
  • Ubuntu Linuxでは、Windowsキーを押してDashを開き、Terminalと入力します。CTRL-ALT-Tのキーボードショートカットでもターミナルウィンドウが開きます。

対話型シェルには>>>プロンプトがあるように、ターミナルにもプロンプトが表示されます。Windowsでは、現在のフォルダのフルパスに続けて大なり記号(>)が表示されます。

C:\Users\al>your commands go here

macOSでは、ユーザー名とコンピュータ名と現在の作業ディレクトリ(ホームフォルダは~と表されています)に続けてパーセント記号(%)が表示されます。

al@Als-MacBook-Pro ~ % your commands go here

Ubuntu Linuxは、macOSと似ています。

al@al-VirtualBox:~$ your commands go here

Windowsならスタートメニューから、macOSならSpotlightから、プログラムを実行できますが、ターミナルから実行することもできます。Pythonインタープリタはターミナルから実行されることも多いです。

本章では、実行するPythonのプログラムがyourScript.pyという名前で、ホームフォルダのScriptsフォルダ内にあると仮定します。Pythonの対話型シェルにアクセスするのにMuを開く必要はありません。ターミナルウィンドウから、Windowsではpython、macOSとLinuxではpython3と入力すれば、Pythonの対話型シェルが開きます(おなじみの>>>プロンプトが見えるはずです)。ターミナルからPythonファイル.pyを実行するには、pythonないしpython3のあとにそのファイルのパスを入力します。現在の作業ディレクトリがC:\Users\al\Scriptsであれば、python C:\Users\al\Scripts\yourScript.pyのような絶対パスでも、yourScript.pyのような相対パスでも構いません。

cd、pwd、dir、lsコマンド

すべての実行中のプログラムに相対パスの起点となる現在の作業ディレクトリ(CWD)があるように、ターミナルにも現在の作業ディレクトリがあります。このCWDはターミナルプロンプトの一部として見えていますし、macOSとLinuxではpwd(print working directory)、Windowsではコマンドライン引数なしでcdを実行して確かめることもできます。

Pythonプログラムではos.chdir()関数を呼び出すとCWDを変更できます。ターミナルでは、cdコマンドのあとに変更先のフォルダを入力すると(相対パスでも絶対パスでも構いません)、同じことができます。

C:\Users\al>cd Desktop
C:\Users\al\Desktop>cd ..
C:\Users\al>cd C:\Windows\System32
C:\Windows\System32>

Windowsでは、ドライブ文字を変更するにはさらに別の手順が必要になります。cdコマンドではドライブを変更することはできません。ドライブ文字とコロンを入力してから、cdでそのドライブのディレクトリに変更します。

C:\Windows\System32>D:
D:\>cd backup
D:\backup>

Windowsのdirコマンド、macOSとLinuxのlsコマンドは、CWDのファイルとサブフォルダを一覧表示します。

C:\Users\al>dir
--snip--
08/26/2036  06:42 PM           171,304 _recursive-centaur.png
08/18/2035  11:25 AM             1,278 _viminfo
08/13/2035  12:58 AM    <DIR>          __pycache__
              77 File(s)     83,805,114 bytes
             108 Dir(s)  149,225,267,200 bytes free

ターミナルでファイルシステムを動き回る際には、cdでディレクトリを変更してdir/lsでそのディレクトリの中身を確認するという作業をしばしば繰り返します。Windowsではdir *.exe、macOSとLinuxではfile * | grep executableを実行すれば、CWD内のすべての実行可能ファイルを一覧表示できます。プログラムを含むフォルダにいるなら、以下の方法でそのプログラムを実行できます。

  • Windowsでは、プログラム名を入力します。example.exeのように拡張子.exeをつけても、つけなくても構いません。
  • macOSとLinuxでは、./exampleのように./に続けてプログラム名を入力します。

C:\full\path\to\example.exeや/full/path/to/exampleのように、プログラムのフルパス(絶対パス)を入力しても実行できます。

example.txtという名前のテキストファイルなど、プログラムファイル以外のファイルを開く場合は、Windowsならexample.txt、macOSとLinuxならopen example.txtと入力すれば、関連づけられたアプリケーションでそのファイルを開きます。これはGUIのexample.txtファイルのアイコンをダブルクリックするのと同じことをターミナルからしています。.txtに関連づけられたアプリケーションが存在しなければ、ユーザーはそのファイルを開くアプリケーションを選ぶことを求められ、そのアプリケーションが将来のために記憶されます。

PATH環境変数

すべてのプログラムには、どのような言語で書かれていても、環境変数と呼ばれる文字列変数があります。その中の一つにPATH環境変数があります。プログラム名を実行したときにターミナルが確認するフォルダの一覧です。例えば、Windowsでpython、あるいはmacOSとLinuxでpython3と入力すると、ターミナルはその名前のプログラムをPATHに含まれるフォルダの中から探します。OSによってPATHのルールが微妙に異なります。

  • Windowsはその名前のプログラムをまずCWDで探し、次にPATHのフォルダを探します。
  • LinuxとmacOSでは、PATHのフォルダだけを探し、CWDは探しません。CWDのexampleという名前のプログラムを実行したければ、exampleではなく./exampleと入力する必要があります。

Windowsではecho %PATH%、macOSとLinuxではecho $PATHを実行すると、PATH環境変数の中身を確認できます。PATHに格納されている値はフォルダ名をセミコロン(Windows)またはコロン(macOSとLinux)で区切った長い文字列です。例えば、Ubuntu Linuxでは、PATH環境変数は以下のようになっています。

al@al-virtual-machine:~$ echo $PATH
/home/al/.local/bin:/home/al/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/
usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

このPATHのLinuxのターミナルでpython3と入力すると、まず/home/al/.local/binフォルダ、次に/home/al/binフォルダと順にpython3という名前のプログラムを探します。/usr/binでpython3を見つけてそれを実行します。PATH環境変数のフォルダ内にプログラムを置いておくと、プログラムを実行するたびごとにそのプログラムのあるフォルダにcdで移動しなくてもすむので、便利です。

ターミナルはPATHフォルダのサブフォルダは探さないことに注意してください。C:\Users\al\ScriptsがPATHに含まれているとしたら、spam.exeを実行すると、C:\Users\al\Scripts\spam.exeファイルを実行できますが、C:\Users\al\Scripts\eggs\spam.exeファイルは実行できません。

PATHの編集

これまで.pyファイルを、Muエディタがデフォルトで用いるmu_codeフォルダに保存してきました。しかし、ホームフォルダ以下にScriptsフォルダを作成することをおすすめします。ユーザー名がalだとしたら、次のようなフォルダです。

  • WindowsではC:\Users\al\Scripts
  • macOSでは/Users/al/Scripts
  • Ubuntu Linuxでは/home/al/Scripts

このフォルダをPATHに追加しましょう。

Windows

Windowsには2種類の環境変数があります。すべてのユーザーに適用されるシステム環境変数と、システム環境変数をユーザーごとに上書きするユーザー環境変数です。これらの環境変数を編集するには、スタートメニューから環境変数を編集と入力し、環境変数ウィンドウを開きます。

(画面下部のシステム変数一覧ではなく)画面上部のユーザー変数一覧からPathを選び、編集をクリックして、新しいフォルダ名C:\Users\al\Scriptsをセミコロンで区切られたテキストフィールドに追加し、OKをクリックします。

macOSとLinux

PATH環境変数にフォルダを追加するには、ターミナル起動スクリプトを編集します。macOSでは.zshrcファイル、Linuxでは.bashrcファイルです。これらのファイルはホームフォルダにあり、新しいターミナルウィンドウが開くたびに実行されるコマンドが書かれています。macOSでは、.zshrcファイルの最後に以下の行を追加してください。

export PATH=/Users/al/Scripts:$PATH

Linuxでは、.bashrcファイルの最後に以下の行を追加してください。

export PATH=/home/al/Scripts:$PATH

この行を追加すると、それ以後に開くターミナルウィンドウでPATHを変更します。現在開いているターミナルウィンドウには変更が及びません。

whichコマンドとwhereコマンド

プログラムがPATH環境変数のどのフォルダにあるのかを知りたければ、macOSとLinuxではwhichを、Windowsではwhereを実行します。例えば、macOSで以下のwhichコマンドを入力してください。

al@Als-MacBook-Pro ~ % which python3
/Library/Frameworks/Python.framework/Versions/3.13/bin/python3

Windowsでは、以下のwhereコマンドを入力してください。

C:\Users\al>where python
C:\Users\al\AppData\Local\Programs\Python\Python313\python.exe
C:\Users\al\AppData\Local\Programs\Python\Python312\python.exe

whereコマンドはpythonという名前のプログラムが存在するPATHのフォルダを表示します。一番上のフォルダがpythonと入力したときに使われるものです。whichコマンドとwhereコマンドは、PATHがどのような設定になっているか不確かで、特定のプログラムの場所を知りたい場合に役立ちます。

仮想環境

2つのPythonプログラムがあり、1つはあるパッケージのバージョン1.0を使っていて、もう1つは同じパッケージのバージョン2.0を使っているとします。Pythonは同じパッケージの2つのバージョンを同時にインストールすることはできません。バージョン2.0にバージョン1.0との後方互換性がなければ、実行するプログラムを切り替えるたびにあるバージョンをアンインストールして別のバージョンをインストールしなければなりません。

この問題は、仮想環境でPythonを別々にインストールしてサードパーティのパッケージをそれぞれにインストールすれば解決できます。アプリケーションごとに仮想環境を用意するのが一般的です。しかし、プログラムの学習中に作成する小さなスクリプトであれば、全部を一つの仮想環境で作成しても差し支えないでしょう。Pythonに組み込まれているvenvモジュールで仮想環境を作成します。cdでScriptsフォルダに移動して、python –m venv .venvを実行します(macOSとLinuxではpythonではなくpython3としてください)。

C:\Users\al>
C:\Users\al>cd Scripts
C:\Users\al\Scripts>python -m venv .venv

これにより.venvという名前の新しいフォルダ内に仮想環境のファイルを作成します。フォルダ名は何でもいいですが、.venvという名前にする慣習があります。ドットで始まる名前のフォルダは隠しフォルダです。隠しフォルダと隠しファイルを表示するようにOSの設定を調整してください。

ターミナルからpythonないしpython3を実行すると、Pythonをインストールしたときのインタープリタを起動します。仮想環境のPythonを使うには、仮想環境を有効化します。WindowsではC:\Users\al\Scripts\.venv\Scripts\activate.batを実行します。

C:\Users\al\Scripts>cd .venv\Scripts
C:\Users\al\Scripts\.venv\Scripts>activate.bat
(.venv) C:\Users\al\Scripts\.venv\Scripts>where python.exe
C:\Users\Al\Scripts\.venv\Scripts\python.exe
C:\Users\Al\AppData\Local\Programs\Python\Python313\python.exe
C:\Users\Al\AppData\Local\Programs\Python\Python312\python.exe

仮想環境を有効化してからwhere python.exeを実行すると、ターミナルから実行するpythonが、システムのPythonではなく、.venv\ScriptsフォルダのPythonインタープリタであることが示されます。

macOSとLinuxでは~/Scripts/.venv/bin/activateを実行するのですが、セキュリティのパーミッションにより直接実行することはできません。source activateコマンドを実行します。

al@al-virtual-machine:~/Scripts$ cd .venv/bin
al@al-virtual-machine:~/Scripts/.venv/bin$ source activate
(.venv) al@al-virtual-machine:~/Scripts/.venv/bin$ which python3
/home/al/Scripts/.venv/bin/python3

仮想環境を有効化するとPATH環境変数が変更され、pythonないしpython3が、インストールしたシステムのPythonインタープリタではなく、.venvフォルダ内のPythonインタープリタを実行します。ターミナルプロンプトにも(.venv)と表示されますから、仮想環境が有効化されていることがわかります。仮想環境を有効化してwhich python3を実行すると、python3が新しく作成した.venv/binフォルダのPythonインタープリタを実行することがわかります。この変更は現在のターミナルウィンドウだけに適用され、別のターミナルウィンドウでは環境変数もプロンプトも変更されません。この新しいPython環境にはデフォルトのパッケージしか入っておらず、これまでにシステムのPythonにインストールしたパッケージは含まれていません。python –m pip listを実行すると、インストールされたパッケージを一覧表示するので、そのことを確かめられます。

(.venv) C:\Users\al\Scripts\.venv\Scripts>python -m pip list
Package    Version
---------- -------
pip        23.0
setuptools 65.5.0

プロジェクトごとにパッケージの依存関係が異なりますから、プロジェクトごとに仮想環境を作成するのが標準的です。しかし、Windowsでは、もう少し緩く考え、Scriptsフォルダで作成する小さなスクリプトをすべて同じ仮想環境で実行してもよいでしょう。

macOSとLinuxでは、OSにPythonが付属していてそれに依存しているプログラムがあります。このOSに付属しているPython(システムのPython)のパッケージをインストールしたりアップデートしたりすると、プログラムに悪影響を及ぼす可能性があります。自分で書いたスクリプトをシステムのPythonで実行しても何の問題もありません。システムのPythonでサードパーティーのパッケージをインストールするのは少しリスクがありますが、Scriptsフォルダで仮想環境を作成すれば安心です。

Muにも固有の仮想環境があります。MuでF5を押してスクリプトを実行すると、Scripts\.venvフォルダの仮想環境にインストールしたパッケージを利用できません。プログラミングに習熟してくると、Muのウィンドウでコードを編集し、ターミナルウィンドウでそのコードを実行したくなるかもしれません。WindowsとLinuxではALT-TAB、macOSでは-TABを押すと、ウィンドウを切り替えられます。

仮想環境を無効化するには、仮想環境を有効化したフォルダで、Windowsではdeactivate.bat、macOSとLinuxではdeactivateを実行します。ターミナルウィンドウを閉じて新しく開いてもよいでしょう。仮想環境とその環境にインストールしたパッケージを完全に削除したければ、.venvフォルダをその中身ごと削除します。

以下の節では仮想環境を有効化してPATHにScriptsフォルダを追加した状態でスクリプトを開発します。

pipでPythonのパッケージをインストールする

Pythonにはpipと呼ばれるコマンドラインパッケージ管理プログラムが付属しています。pipはpip installs packageという再帰的頭字語です。Pythonにはsys、random、osなどの標準ライブラリが付属していますが、https://pypi.orgのPyPI(パイピーアイ)には何十万ものサードパーティーのパッケージが登録されています。パッケージは、Pythonのコードを集めたもので、PyPIからインストールできます。moduleは、Pythonのコードが書かれた個々の.pyファイルです。モジュールを含むパッケージをPyPIからインストールして、import文でモジュールをインポートします。

pipは独立したプログラムで、Windowsならpip、macOSとLinuxではpip3で直接実行できますが、Windowsではpython –m pip、macOSとLinuxではpython3 –m pipで、Pythonインタープリタを通じて実行することもできます。インタープリタを通じて実行すれば、Pythonが複数インストールされていてPATHの設定がまずくpip/pip3が別のPythonインタープリタにインストールするというエラーを防げます。

Anacondaでpipを使わないでください

https://python.orgから通常のディストリビューションをインストールせずにAnacondaをインストールした場合は、conda環境でpipを使わずにcondaコマンドでパッケージ管理をしてください。

以下のコマンドでPyPIからパッケージをインストールします。

C:\Users\al>python –m pip install package_name

この種のコマンドに関して、macOSとLinuxではpythonの代わりにpython3としてください。また、このコマンドはPythonの対話型シェルではなく、ターミナルウィンドウから実行します。

python –m pip listを実行すると、インストールしたパッケージとそのバージョン番号を一覧表示します。

C:\Users\al>python -m pip list
Package                   Version     Editable project location
------------------------- ----------- -------------------------
altgraph                  0.17.3
argon2-cffi               21.3.0
argon2-cffi-bindings      21.2.0
async-generator           1.10
--snip--
wsproto                   1.2.0

python –m pip install –U package_nameを実行するとPyPIからパッケージを最新のバージョンにアップグレードしますし、python –m pip install package_name==1.17.4のように実行すれば指定したバージョンのパッケージをインストールします。

python –m pip uninstall package_nameを実行するとパッケージをアンインストールします。python –m pip --helpを実行するとヘルプ情報を見ることができます。

automateboringstuff3パッケージのインストール

本書ではサードパーティーのパッケージをいくつか利用しています。時間が経つとパッケージの作者がアップデートを行い、本書で利用しているバージョンと後方互換性が保たれなくなる可能性があります。本書に記載している情報の正確性を保つために、付録Aに記載されているバージョンをインストールしてください。最新のバージョンをインストールしても問題ない場合もありますが、新しい変更点についてそのパッケージのオンラインドキュメントを確認してください。

仮想環境でautomateboringstuff3パッケージをインストールすると、本書で利用しているすべてのパッケージを指定したバージョンでインストールできます。このパッケージは、本書で利用しているバージョンを揃えたすべてのパッケージを含む容器のようなものです。Windowsでは、仮想環境を有効化してから、次のコマンドを実行してください。

python –m pip install automateboringstuff3

macOSとLinuxでは、仮想環境を有効化してから、次のコマンドを実行してください。

python3 –m pip install automateboringstuff3

実行するPythonプログラムについての情報

プログラムに感覚を持たせるPythonの標準モジュールは(今のところ)ありません。しかし、実行するPythonプログラムやOSやインタープリタなどについて有用な情報を提供する組み込み変数はあります。Pythonのインタープリタが自動的にそれらの変数を設定します。

変数__file__には.pyファイルのパスが文字列として格納されています。例えば、ホームフォルダでyourScript.pyを実行したとすると、'C:\Users\al\yourScript.py'に評価されます。from pathlib import PathとインポートしてからPath(__file__)を呼び出すと、このファイルのパスオブジェクトが返されます。Pythonプログラムフォルダに存在するファイルを知りたければ、この情報を使うことができます。Pythonの対話型シェルでは変数__file__は存在しません。

変数sys.executableにはPythonインタープリタプログラム自体のフルパスとファイルが格納されており、変数sys.versionにはPythonインタープリタのバージョン情報など対話型シェルの上部に現れる文字列が格納されています。

変数sys.version_info.majorとsys.version_info.minorにはPythonインタープリタのメジャーバージョン番号とマイナーバージョン番号の整数が格納されています。筆者の環境ではPythonバージョン3.13.1を使っているので、それぞれ3と13になります。sys.version_info をlist()関数に渡すともっと詳細な情報を得られます。筆者の環境では、list(sys.version_info) が[3, 13, 1 'final', 0]を返しました。バージョン情報をこのように取得すると、sys.versionの文字列から抽出しようとするよりも簡単です。

変数os.nameには、Windowsで実行しているなら'nt'が、macOSとLinuxで実行しているなら'posix'が格納されます。Pythonのスクリプトが、実行しているOSに応じて別のコードを実行する必要がある場合に便利です。

OSに関するさらに詳細な情報は、変数sys.platformに格納されています。Windowsなら'win32'、macOSなら'darwin'、Linuxなら'linux'といった具合です。

OSのバージョンやCPUのタイプについて細かい情報が必要でしたら、組み込みのplatformモジュールで情報を取得できます。このモジュールについてはhttps://docs.python.org/3/library/platform.htmlで説明されています。

あるモジュールがインストールされているかどうかをチェックする場合は、import文をtryブロックに入れてModuleNotFoundError例外を捕捉します。

try:
    import nonexistentModule
except ModuleNotFoundError:
    print('This code runs if nonexistentModule was not found.')

プログラムの動作に特定のモジュールが必要な場合に、ここで詳細なエラーメッセージを用意して、sys.exit()でプログラムを終了できます。一般的なエラーメッセージとトレースバックを表示するよりもそのほうが親切でしょう。

テキストベースのプログラム設計

GUIをサポートするOSが一般的になるまでは、プログラムはすべてテキストでユーザーとやり取りしていました。本書で取り上げるプログラムはプロのソフトウェアアプリケーションではなく小規模な便利プログラムですから、ウィンドウやボタンがあるGUIのグラフィックはなく、(昔のプログラムと同じように)print()とinput()でやり取りするコマンドラインインターフェイスです。

テキストに限定されていた時代でも、現代的なGUIに似たユーザーインターフェイスを備えたソフトウェアアプリケーションがありました。図12-1はNorton Commanderというファイルシステム閲覧アプリケーションを示しています。こうしたアプリケーションは遡及的にTUI(text-based user interface)と呼ばれます。

図 12-1:テキストベースのNorton Commander(上側)と、現代的なGUIアプリケーション(下側)

テキストベースのユーザーインターフェイスはシンプルだという長所があります。この節では、ユーザーインターフェイスをどう設計するかを考えます。

短いコマンド名

テキストベースのプログラムは、デスクトップやスタートメニューのアイコンをクリックするのではなく、コマンドラインから実行されることが多いです。こうしたコマンドの中には理解しにくいものもあります。私は、Linuxの学習を始めたときに、よく知っているWindowsのcopyコマンドがLinuxではcpであることを知って驚きました。cpよりもcopyのほうがずっとわかりやすいです。簡潔で暗号のような名前で2文字の入力を節約する価値があるのかと疑問に思いました。

コマンドラインの経験を積むと、短いコマンドに価値があるとわかりました。ソースコードを書くよりも読むほうが多いですから、変数や関数の名前に長い名前をつけるのは役立ちます。しかし、コマンドラインでコマンドを読むよりも打ち込むことのほうが多いですから、反対に短いコマンド名のほうが使いやすく、腕への負担が減ります。

一日に何十回も打ち込むようなコマンドのプログラムを作成するなら、短い名前にすることを検討してください。whichないしwhereコマンドを実行すれば、別のプログラムで使われている名前かどうか確認できます。インターネットでコマンドの名前を調べることもできます。短い名前にすると使いやすいです。

コマンドライン引数

コマンドラインからプログラムを実行するには、その名前を入力します。.pyのPythonのソースコードファイルを実行する場合は、python yourScript.pyのようにpython(Windows)ないしpython3(macOSとLinux)プログラムのあとに.pyのファイル名をつけます。

コマンドのあとにつけるテキストをコマンドライン引数と呼びます。コマンドライン引数は、関数呼び出しに引数が渡されるのと同じ要領で、コマンドに渡されます。例えば、lsコマンドを単独で実行するとCWD内のファイルを一覧表示します。exampleFolderというコマンドライン引数をつけてls exampleFolderを実行すると、lsはexampleFolderフォルダ内のファイルを一覧表示します。コマンドライン引数を利用するとコマンドの動作を設定できます。

Pythonスクリプトでは、リストsys.argvにPythonインタープリタが受け取ったコマンドライン引数が入っています。例えば、python3 yourScript.py hello worldと入力したら、python3プログラムがコマンドライン引数を受け取り、Pythonスクリプトの変数sys.argvに転送します。変数sys.argvには['yourScript.py', 'hello', 'world']が格納されます。

sys.argvの最初の要素はPythonスクリプトのファイル名です。残りの引数はスペースで分割されます。コマンドライン引数にスペースを含めたければ、ダブルクォートで囲んでください。例えば、python3 yourScript.py "hello world"だとsys.argvに['yourScript.py', 'hello world']が格納されます。

コマンドライン引数を使う利点は、プログラムを開始する前にさまざまな設定ができることです。設定メニューや多段階プログラムは不要です。このアプローチでは、コマンドライン引数が複雑で読みにくくなる可能性があるという欠点があります。Windowsのコマンドなら/?を、macOSとLinuxのコマンドなら--helpを、コマンドのあとにつけると、何ページにもわたってコマンドライン引数を説明するドキュメントを見ることになるでしょう。

プログラムが受け入れるコマンドライン引数が単純であれば、sys.argvのリストを直接読み取るのが一番簡単です。しかし、コマンドライン引数が増えてきたら、その組み合わせを管理するのが重荷になり始めます。python yourScript.py spam eggsとpython yourScript.py eggs spamは同じであるのか、cheeseとbaconを選べるのであれば、両方を指定した場合にはどうなるのか、などを考えなければなりません。こうした複雑性に対処するには、限界事例を処理するたくさんのコードを書かなければなりません。そうなると、Python組み込みのargparseモジュールを使って処理したほうがよいでしょう。argparseモジュールは本書の範囲外ですが、https://docs.python.org/3/library/argparse.htmlで説明を読めます。

クリップボードの入出力

テキストを受け取るのにinput()を使わなければならないという決まりはありません。クリップボードを通じてPythonのプログラムとテキストのやり取りをすることもできます。pyperclipモジュールはクロスプラットフォームであり、(OSを問わず)copy()関数でクリップボードにテキストを貼り付け、paste()関数でクリップボードのテキストを文字列として取得します。Pyperclipはターミナルからpipでpython –m pip install pyperclipを実行してインストールするサードパーティーのパッケージです。Linuxでは、sudo apt install xclipも実行する必要があります。詳細は付録Aをご参照ください。

クリップボードで入出力を行うプログラムは、以下の基本設計になります。

  1. pyperclipモジュールをインポートする

  2. pyperclip.paste()でクリップボードからテキストを取得する

  3. そのテキストを使って処理を行う

  4. pyperclip.copy()で処理済みのテキストをクリップボードに貼り付ける

第8章の「ウィキマークアップに箇条書き記号を追加する」プロジェクトがその一例です。このようにプログラムを設計して、「Pythonプログラムのデプロイ」の指示通りにデプロイすると、使いやすいです。入力テキストを反転させ、CTRL-Cでコピーし、プログラムを実行します。そうすると処理済みのテキストがクリップボードに保存されますから、必要に応じて貼り付けて使えます。

本章の最後で、クリップボードを活用するccwdとクリップボードレコーダーの2つのプロジェクトに取り組みます。

Bextでテキストに色をつける

Jonathan HartleyのColoramaパッケージをもとに作られたサードパーティーのBextパッケージを利用すると、テキストに色をつけて出力できます。付録Aの指示に従ってpipでBextをインストールしてください。Bextはターミナルウィンドウから実行するプログラムでのみ機能し、Muその他のエディタから実行するプログラムでは機能しません。fg()関数とbg()関数を'black'、'red'、'green'、'yellow'、'blue'、'magenta'、'purple'、'cyan'、'white'のような引数で呼び出して前面のテキスト色と背景色を変更すれば、print()で色のついたテキストを出力できます。'reset'を渡すとターミナルウィンドウのデフォルトの色に戻ります。対話型シェルで次のように入力してみてください。

>>> import bext
>>> bext.fg('red')
>>> print('This text is red.')
This text is red.
>>> bext.bg('blue')
>>> print('Red text on blue background is an ugly color scheme.')
Red text on blue background is an ugly color scheme.
>>> bext.fg('reset')
>>> bext.bg('reset')
>>> print('The text is normal again. Ah, much better.')
The text is normal again. Ah, much better.

ユーザーがターミナルウィンドウをライトモードに設定しているかダークモードに設定しているかはわかりませんので、ターミナルのデフォルトの見た目が白地に黒色のテキストなのか黒地に白色のテキストなのかはわかりません。また、あまりにたくさんの色を使わないほうがよいでしょう。色を使いすぎると悪趣味で読みづらくなります。

Bextには以下のようなちょっとしたTUI機能もあります。

bext.clear() 画面をクリアします。

bext.width()とbext.height() ターミナルウィンドウの幅と高さをそれぞれ返します。

bext.hide()とbext.show() カーソルの非表示/表示を切り替えます。

bext.title(text) ターミナルウィンドウのタイトルバーに表示されるテキストを変更します。

bext.goto(x, y) カーソルを座標(x, y)に移動させます。左上が(0, 0)です。

bext.get_key() ユーザーのキー入力を待ち受け、キーが押されるとそのキーを表す文字列を返します。

bext.get_key()関数はinput()の1キーバージョンだと考えてください。返り値は'a'、'z'、'5'といったものもあれば、'left'、'f1'、'esc'といったものもあります。TABキーは'\t'、ENTERキーは'\n'を返します。対話型シェルでbext.get_key()を呼び出すとキーの返り値を試せます。

https://inventwithpython.com/projects/fishtank.pyのアスキーアート水槽プログラムのコードを実行すると、Bextで何ができるかをつかめます。このプログラムでは最初にbext.clear()でターミナルウィンドウのテキストをすべてクリアしています。次に、bext.goto()を呼び出してカーソルを移動させ、bext.fg()でテキストの色を変更し、><)))*>のようなテキストで魚を表示しています。このプログラムは筆者の別の本のThe Big Book of Small Python Projects (No Starch Press, 2021)で取り上げています。

ターミナルのクリア

プログラムを実行する前にターミナルのテキストを消したいときに、bext.clear()関数は便利です。パラパラ漫画のアニメーションにも使えます。clear()を呼び出してターミナルをクリアして、print()呼び出しでテキストを表示し、time.sleep()で停止して…を繰り返します。以下の自作のclear()関数は、画面をクリアするPythonのワンライナー(1行のコード)です。

import os
def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

このコードを実行すると、Bext パッケージをインストールしなくてもターミナルをクリアできます。Muその他のコードエディタからの実行では動作せず、ターミナルからスクリプトを実行する必要があります。os.system()呼び出しは、Windowsならclsプログラムを、macOSとLinuxならclearプログラムを実行します。この奇妙な構文はPythonの条件式(他の言語では三項演算子と呼ばれることもあります)の一例です。value1 if condition else value2という構文は、condition がTrueならvalue1と評価され、conditionがFalseならvalue2と評価されます。この例では、os.name == 'nt'がTrueなら'cls'と評価され、os.name == 'nt'がFalseなら'clear'と評価されます。条件式(とワンライナー一般)は、読みづらく避けたほうがいいですが、これくらい単純な例なら使ってもよいでしょう。

音とテキストによる通知

ターミナルプログラムは、今日のコンピュータで利用できるオーディオ機能よりも前から存在していました。今日では、テキストベースのプログラムが音を出してはいけない理由はありません。とはいえ、音を出すのは最小限にとどめたほうがよいでしょう。ユーザーが別のウィンドウを見ていても、作業が完了したり問題が発生したときに音を出すと通知できます。しかし、色のついたテキストと同様に、音を使いすぎてうっとうしく感じることがあります。ユーザーは音声を再生しているかもしれませんし、オンラインミーティングに参加しているかもしれず、音を出すとその邪魔をしてしまいます。そもそも、ユーザーのコンピュータはミュートになっていて音を出して通知をしようとしても聞こえないかもしれません。

単純なオーディオファイルを再生するには、playsound3というサードパーティーのパッケージを利用できます。これをインストールすればplaysound3モジュールのplaysound()関数を呼び出してMP3またはWAVのオーディオファイルのパスを渡せばそのオーディオファイルを再生できます。https://autbor.com/hello.mp3からhello.mp3 fileファイルをダウンロードするか自分でファイルを用意して、対話型シェルで以下のコードを入力してください。

>>> import playsound3
>>> playsound3.playsound('hello.mp3')

playsound()関数はオーディオファイルの再生が終わるまで値を返しません。つまり、この関数はオーディオが終わるまでプログラム実行をブロックします。長いオーディオファイルを再生するとプログラムが停止してしまうことに注意してください。(ファイル名にイコール記号などが含まれるなどで)playsound()が例外を送出する場合には、文字列ではなくPathオブジェクトを渡してみてください。

プログラムが出力するテキストにも制限を設けたほうがよいかもしれません。Unix哲学のコマンド設計では、あるコマンドのテキスト出力をパイプでつないで別のコマンドに渡します。コマンドが重要な情報だけを出力すれば、それがやりやすくなります。重要ではない出力がある場合はフィルターにかけなければなりません。多くのコマンドは出力を最小限にとどめているか、そもそも出力をせずに終了コード(exit code)でコマンドの成功/不成功を伝えます(終了コードについては第19章で説明します)。出力をパイプで別のコマンドにつなぐつもりはなく、人間のユーザーが見るためにたくさんの情報がほしければ、-vないし--verboseコマンドライン引数で冗長モードにできるコマンドが多いです。逆のアプローチを採用しているコマンドもあります。つまり、デフォルトではたくさんの出力をして、-qないし--quietコマンドライン引数を指定すると静寂モードで出力をしないコマンドです。(音による通知でも同じような考え方ができます。)無音モードをデフォルトにして、--verboseないし--beepで音を出すようにするのがよいでしょう。

プログラムをそこまで洗練させる必要がなければ、この節の内容は忘れてください。しかし、自分が書いたプログラムを他の人と共有し始めると、自分が思ってもみなかった使い方をするユーザーがいるので、こうしたオプションを提供すると使いやすいプログラムになります。

短いプログラム:吹雪

テキストベースの吹雪アニメーションを作りましょう。半分、下半分、全体のブロックテキストを使います。これらのブロックテキストは、chr(9600)、chr(9604)、chr(9608)で返されます。TOP、BOTTOM、FULLという定数に格納し、コードを読みやすくします。

以下のコードをsnowstorm.pyという名前で保存してください。

import os, random, time, sys

TOP    = chr(9600)  # Character 9600は'▀'
BOTTOM = chr(9604)  # Character 9604は'▄'
FULL   = chr(9608)  # Character 9608は'█'

# コマンドライン引数で吹雪の密度を指定
DENSITY = 4  # デフォルトの吹雪の密度は4%
if len(sys.argv) > 1:
    DENSITY = int(sys.argv[1])

def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

while True:
    clear()  # ターミナルウィンドウのクリア

    # 各行と各列をループ
    for y in range(20):
        for x in range(40):
            if random.randint(0, 99) < DENSITY:
                # 雪の表示
                print(random.choice([TOP, BOTTOM]), end='')
            else:
                # スペースの表示
                print(' ', end='')
        print()  # 改行の出力

    # 雪で覆われた地面を表示
    print(FULL * 40 + '\n' + FULL * 40)
    print('(Ctrl-C to stop.)')

    time.sleep(0.2)  # 少し停止

まず、os、random、sys、timeのモジュールをインポートします。これらのモジュールはPythonの標準ライブラリに含まれていますから、サードパーティーパッケージをインストールする必要はありません。次に、定数TOP、BOTTOM、FULLにchr()の返り値を設定します。9600、9604、9608という数字よりも定数の名前のほうが理解しやすいです。

ユーザーはコマンドライン引数で吹雪の密度を指定できます。コマンドライン引数が指定されなければ、sys.argvは['snowstorm.py']になりますので、DENSITYは4のままです。ユーザーが例えばpython snowstorm.py 20のようにプログラムを実行すると、sys.argvは['snowstorm.py', '20']になり、DENSITYをint(sys.argv[1])、すなわち20に更新します。ユーザーはソースコードを変更せずに吹雪プログラムの動作を調整できます。

while無限ループ内では、まずcls/clearワンライナーで画面をクリアします。次に入れ子のforループでターミナルの40×20のスペースの行と列を反復処理します。(この数字を増やしたり減らしたりすると吹雪のサイズを変更できます。)各行及び各列について一つの文字を出力します。雪を表すTOPまたはBOTTOMか、何もないスペースのどちらかをランダムに選びます。(4パーセントだけ雪を表す文字が選ばれます。)ここでのprint()呼び出しではend=''キーワード引数を指定しているので、print()は自動的に改行文字を出力しません。行が終わったあとにprint()を引数なしで呼び出して改行を出力します。

入れ子のforループのあとで、地面を表す2行のFULLと、CTRL-Cを押せばプログラムが停止する案内を出力します。このコードは吹雪アニメーションの1フレームを出力してからtime.sleep(0.2)で少し静止し、ターミナルをクリアして…という実行を繰り返します。

実用的ではありませんが、ターミナルによるスノーグローブのようで楽しいからこのプログラムを紹介しました。このテクニックを活用した役に立つアプリケーションとして、ダッシュボードアプリを作成できます。ターミナルウィンドウで実行して開いたままにしておき、ひと目で情報がわかるようにするプログラムです。このプログラムは重要な情報を表示し、毎秒、毎分、毎時など適時情報を更新します。

PyMsgBoxによるポップアップメッセージボックス

プログラムで完全なGUIをデザインするには、Tkinter、wxPython、PyQtなどのライブラリを十分に学習する必要がありますが、PyMsgBoxパッケージで簡単なGUIメッセージボックスをプログラムで作成できます。ターミナルからpip install pymsgboxを実行してインストールできるサードパーティーのパッケージです。PyMsgBoxではTkinterを使用したダイアログを作成します。WindowsとmacOSではTkinterが付属していますが、Ubuntu Linuxではターミナルでsudo apt install python3-tkを実行してTkinterをインストールしなければなりません。付録Aで詳しく説明しています。

PyMsgBoxにはJavaScriptのメッセージボックス関数と対応する関数があります。

pymsgbox.alert(text) テキストメッセージを表示し、ユーザーがOKをクリックしたら文字列'OK'を返します。

pymsgbox.confirm(text) テキストメッセージを表示し、ユーザーがOKまたはCancelをクリックしたら文字列'OK'または'Cancel'を返します。

pymsgbox.prompt(text) テキストフィールドがあるメッセージを表示し、ユーザーが入力したテキスト文字列またはNone(キャンセルをクリックした場合)を返します。

pymsgbox.password(text) pymsgbox.prompt()と同じですが、ユーザーの入力がアスタリスクでマスクされます。

これらの関数はユーザーがOK、Cancel、X(閉じる)をクリックするまで値を返しません。たまに通知をしたいだけなら、PyMsgBoxのダイアログをprint()やinput()の代わりに利用できます。

Pythonプログラムのデプロイ

Pythonプログラムが完成したら、毎回Muを実行して.pyファイルを読み込み実行ボタンを押すのは面倒に感じます。この節では、自分が書いたPythonプログラムをデプロイして、できるだけ少ない手数で実行できるようにデプロイする方法を説明します。

「PATH環境変数」の手順に従ってScriptsフォルダをPATH環境変数に追加してください。私のユーザー名はalですので、そのフォルダのパスはWindowsならC:\Users\al\Scripts、macOSなら/Users/al/Scripts、Linuxなら/home/al/Scriptsになります。また、次に説明するように、Pythonの仮想環境を設定する必要があります。

Windows

Windowsでは、WindowsキーとRキーを同時に押すと(あるいはスタートメニューボタンを右クリックして実行を選ぶと)実行ダイアログが立ち上がります。小さなウィンドウが開き、一つのコマンドを実行できるので、一回限りのターミナルのように使えます。ここからPythonスクリプトを実行するには、以下の手順で進めます。

  1. ScriptsフォルダにyourScript.pyPythonスクリプトを置く。

  2. ScriptsフォルダにPythonスクリプトを実行するためのyourScript.batバッチファイルを作成する。

バッチファイルを実行すると、そのファイルに書かれたコマンドが実行されます。ファイル拡張子は.batで、LinuxのシェルスクリプトやmacOSの.commandスクリプトに似ています。PATHフォルダの中にyourScript.batという名前のバッチファイルを置いたら、実行ダイアログにyourScriptと入力して実行できます。Windowsでは.batファイルや.exeファイルを実行するのにファイル拡張子を入力する必要はありません。

バッチファイルの中身は、.pyファイルと同じように、プレーンテキストですので、Muやメモ帳などのテキストエディタで作成できます。バッチファイルでは1行に1つのコマンドを書きます。C:\Users\al\Scriptsフォルダ内のyourScript.pyファイルを実行するには、以下の内容のyourScript.batファイルを作成します。

@call %HOMEDRIVE%%HOMEPATH%\Scripts\.venv\Scripts\activate.bat
@python %HOMEDRIVE%%HOMEPATH%\Scripts\yourScript.py %*
@pause
@deactivate

バッチファイルの名前は何でも構いませんが、Pythonスクリプトと同じ名前にしたほうが覚えやすいです。このバッチファイルは3つのコマンドを実行します。1つ目のコマンドは、Scriptsフォルダに作成した仮想環境を有効化します。冒頭の@記号はこのコマンド自体がターミナルウィンドウに表示されないようにするためのものです。環境変数%HOMEDRIVE%は'C:'で、%HOMEPATH%は'\Users\al'のようなホームフォルダのパスです。(Windowsではチルダ(~)がホームフォルダを表しません。)これらを合わせると、ユーザー名が何であっても、仮想環境を有効化するスクリプトの場所を指定できます。(これらのファイルを同僚に共有してその人のコンピュータで実行するときに便利です。)callは必要です。(yourScript.batのような)バッチファイルが別の(activate.batのような)バッチファイルを実行するときにcallがなければ、最初のバッチファイルの残りのコマンドが実行されません。

2つ目のコマンドは、バッチファイルがpython.exeを実行し、yourScript.pyを実行します。%*はバッチファイルにコマンドライン引数をPythonプログラムへと転送します。あとでPythonプログラムにコマンドライン引数を追加する場合に備えて、必ず%*をつけるのはいい考えです。

3つ目のコマンドは、pauseコマンドを実行し、Press any key to continueを表示して、ユーザーが何らかのキーを押すのを待ちます。こうすることで、Pythonプログラムが終了するとターミナルウィンドウがすぐに閉じてしまって出力が見られなくなる事態を防ぎます。プログラムからの出力がなければ、この行を書かなくても大丈夫です。最後に、@deactivateで仮想環境を無効化します。ターミナルからバッチファイルを実行したときに、Pythonプログラムが終了したあとでターミナルを開いたまま使う場合を考えてのことです。

バッチファイルを設定すると、WindowsキーとRキーを押して実行ダイアログを表示し、yourScript(とコマンドライン引数)と入力すればyourScript.batスクリプトを実行することによりPythonスクリプトを実行できます。あるいは、ターミナルウィンドウを開いているなら、どの場所からでもターミナルでyourScriptと入力することでもPythonスクリプトを実行できます。これはMuのようなエディタからコードを実行するよりも断然速いです。

別のPythonスクリプトを作成したら、このバッチファイルを再利用できます。新しい名前でファイルをコピーし、ファイル名のyourScript.pyを新しいPythonスクリプトの名前に変更します。あとはすべて同じです。

macOS

macOSでCOMMANDキーとスペースキーを同時に押すと、Spotlightが立ち上がり、実行するプログラム名を入力できます。Spotlightに自分が書いたPythonスクリプトを追加するには、以下の手順で進めます。

  1. ScriptsフォルダにyourScript.pyPythonスクリプトを置く。

  2. そのPythonスクリプトを実行するためのyourScript.commandという名前のテキストファイルを作成する。

  3. chmod u+x yourScript.commandを実行してyourScript.commandファイルに実行パーミッションを追加する。

/Users/al/ScriptsのようなScriptsフォルダに.pyPythonスクリプトを置いてから、以下の内容のyourScript.commandという名前のテキストファイルをScriptsフォルダに作成します。

source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/yourScript.py
deactivate

~は/Users/alのようなホームフォルダを表します。最初の行は仮想環境を有効化し、2行目は仮想環境のPythonを使ってPythonスクリプトを実行します。

最後に、ターミナルでcdにより~/Scriptsへ移動し、chmod u+x yourScript.commandコマンドを実行してください。これにより、Spotlightからスクリプトを実行するのに必要な実行パーミッションが追加されます。⌘とスペースキーを押してyourScript.commandと入力すれば素早くPythonスクリプトを実行できます。(Spotlightは数文字入力すると完全な名前を自動補完してくれるはずです。)ターミナルからyourScript.commandと入力することでもPythonスクリプトを実行できます。

SpotlightからyourScript.pyファイルを実行するにはyourScript.commandファイルが必要です。Spotlightは.pyファイル拡張子を見て、直接実行するのではなく、Muその他のコードエディタで開くのだと想定してしまいます。

macOSのテキストエディットでyourScript.commandファイルを作成するときには、SHIFT-⌘-Tを押して(あるいはフォーマットメニューから標準テキストをクリックして)プレーンテキストファイルで作成してください。テキストエディットはpython3をPython3へと自動的に大文字にするので(そうなるとSpotlightから実行しようとしたときにエラーが発生します)、その点にも気をつけてください。

残念ながら、Spotlightではユーザーがコマンドライン引数をPythonスクリプトに渡す手段がありません。コマンドライン引数は.commandファイルに予め書いておく必要があります。

Ubuntu Linux

Windowsキーを押せばUbuntu LinuxのDashが立ち上がり、プログラム名を入力すれば実行できます。自分が書いたPythonスクリプトをDashに追加するには、以下の手順で進めます。

  1. ScriptsフォルダにyourScript.pyPythonスクリプトを置く。

  2. 仮想環境を有効化してPythonスクリプトを実行するyourScriptという名前のシェルスクリプトを作成する。

  3. chmod u+x yourScriptを実行して、そのシェルスクリプトに実行パーミッションを追加する。

  4. ~/.local/share/applicationsフォルダにyourScript.desktopファイルを作成し、Dashからそのシェルスクリプトを実行できるようにする。

/home/al/ScriptsのようなScriptsフォルダに.pyのPythonスクリプトを置いてから、Scriptsフォルダに以下の内容のyourScriptという名前のテキストファイル(拡張子なし)を作成します。

#!/usr/bin/env bash
source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/yourScript.py
read -p "Press any key to continue..." -n1 –s
deactivate

~は/home/alのようなホームフォルダを表します。1行目はこのファイルがシェルスクリプトであることを示します。このファイルをファイル拡張子.shにする必要はありません。

2行目は仮想環境を有効化し、3行目は仮想環境のPythonを使ってPythonスクリプトを実行します。readコマンドでターミナルにPress any key to continueと表示し、ユーザーが何らかのキーを押すのを待ちます。こうすることで、Pythonプログラムが終了するとターミナルウィンドウがすぐに閉じてしまって出力が見られなくなる事態を防ぎます。プログラムからの出力がなければ、この行を書かなくても大丈夫です。

このシェルスクリプトを作成してから、cdで~/Scriptsに移動し、chmod u+x yourScriptコマンドを実行します。これにより実行パーミッションが追加されます。ターミナルからyourScriptと入力すればPythonスクリプトを実行できるようになります。DashからPythonスクリプトを実行するには、yourScript.desktopファイルを作成する必要があります。

Muやgeditなどのテキストエディタで以下の内容のyourScript.desktopファイルを作成します。

[Desktop Entry]
Name=yourScript
Exec=gnome-terminal -- /home/al/Scripts/yourScript
Type=Application

このファイルを/home/al/.local/share/applicationsフォルダに(alの部分は自身のユーザー名に置き換えてください)yourScript.desktopという名前で保存します。(このファイルではホームフォルダを表す~を使えないので、Execフィールドには/home/alと書かなければなりません。)テキストエディタで.localフォルダが表示されなければ(ドットで始まるフォルダは隠しフォルダなので)、ファイル保存ダイアログでCTRL-Hを押して隠しファイルを表示してください。

Windowsキーを押してDashを立ち上げ、yourScriptと入力してPythonスクリプトを素早く実行できるようになりました。Dashは完全な名前を自動補完してくれるはずです。yourScript.desktopのNameフィールド中のyourScriptというテキストがDashで表示されます。好きな名前にすることができますが、yourScript.pyと同じ名前にしておくとわかりやすいでしょう。

次はこの章で学んだ原則を活用し、2つのプログラムを作成して使いやすくデプロイしてみましょう。

短いプログラム:現在の作業ディレクトリをコピーする

macOSとLinuxでpwdコマンドを実行すると現在の作業ディレクトリが表示されますが、それをクリップボードにコピーして別の場所に貼り付けられれば便利なことがあります。例えば、私はWindowsでしょっちゅう現在の作業ディレクトリをターミナルからコピーして、ファイル保存ダイアログで貼り付けています。マウスでWindowsのプロンプトから現在の作業ディレクトリを選択してコピーすることもできますが(macOSとLinuxではpwdコマンドを実行して現在の作業ディレクトリを表示してそれをコピーすることもできますが)、この数ステップを1ステップにできるはずです。

私はccwd(copy current working directory)という名前のプログラムを書こうと考えました。まず、Windowsでwhere ccwdと入力し、macOSとLinuxでwhich ccwdと入力して、さらに少しインターネットで調べて、ccwdという名前のコマンドが存在しないことを確認しました。ccwdは短くかつユニークな名前です。

ターミナルの現在の作業ディレクトリがC:\Users\al\ScriptsであるときにC:\Users\alをクリップボードにコピーしたい、という場合に対応できる追加機能を作成したいです。cd ..コマンドを実行してからccwdを実行し、cd ScriptsでC:\Users\al\Scriptsに戻ることもできます。しかしccwdにコマンドライン引数として相対パスを渡せたほうが便利です。例えば、現在の作業ディレクトリがC:\Users\al\Scriptsであるときに、ccwd ..でC:\Users\alをクリップボードにコピーできるということです。このコマンドライン引数は必須ではありませんが(コマンドライン引数がなければデフォルトとして現在の作業ディレクトリをクリップボードにコピーします)、ユーザーが望めばこの機能を使えます。こうした小さい改良は些細なことのように思えるかもしれませんが、小さな改良が大きな効果をもたらします。だからECサイトでは「1クリックで購入」機能があります。

Pyperclipパッケージを利用してクリップボードを操作するので、Scriptsフォルダの仮想環境にこのパッケージをインストールします。Muで新しいファイルを作成して以下のコードを入力してください。

import pyperclip, os, sys
if len(sys.argv) > 1:
    os.chdir(sys.argv[1])
pyperclip.copy(os.getcwd())

このプログラムをccwd.pyという名前で、ホームフォルダ以下のScriptsフォルダの中に保存してください。

1行目ではこのプログラムに必要なモジュールをインポートしています。2行目ではこのプログラムにコマンドライン引数が渡されたかどうかをチェックしています。sys.argvはスクリプト名の'ccwd.py'という少なくとも一つの文字列を含んでいることを思い出してください。1つよりも多い(2つ以上の)文字列が含まれていれば、ユーザーがコマンドライン引数をプログラムに渡したのだとわかります。その場合は、3行目でプログラムの現在の作業ディレクトリを変更します。プログラムごとに現在の作業ディレクトリが設定されていますから、この設定をos.chdir()で変更したとしても、このプログラムを実行したターミナルの現在の作業ディレクトリは変更されません。最後に、4行目で、現在の作業ディレクトリをクリップボードにコピーします。

プログラムを書き終えても、ターミナルから起動するのにpython C:\Users\al\Scripts\ccwd.pyのようにフルパスを入力しなければなりません。これでは入力量が多く、スクリプトで素早く簡単に現在の作業ディレクトリをクリップボードにコピーしたいという目的を達成できません。この点を改善するために、このプログラムをデプロイする方法をOSごとに示します。

Windows

PythonファイルをC:\Users\al\Scripts\ccwd.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容のccwd.batファイルを作成します。

@call %HOMEDRIVE%%HOMEPATH%\Scripts\.venv\Scripts\activate.bat
@python %HOMEDRIVE%%HOMEPATH%\Scripts\ccwd.py %*
@deactivate

このバッチファイルには@pause行はありません。print()出力がないからです。これでどのフォルダにいてもこのプログラムをターミナルからccwdで実行できるようになりました。

C:\Users\al>ccwd
C:\Users\al>

'C:\Users\al'がクリップボードにコピーされています。

macOS

Pythonファイルを/Users/al/Scripts/ccwd.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容の ccwd.commandファイルを作成します。

source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/ccwd.py
deactivate

ターミナルからcdでScriptsフォルダに移動し、chmod u+x ccwd.commandを実行します。

al@Als-MacBook-Pro ~ % cd ~/Scripts
al@Als-MacBook-Pro Scripts % chmod u+x ccwd.command

これでどのフォルダにいてもこのプログラムをターミナルからccwd.commandで実行できるようになりました。

al@Als-MacBook-Pro ~ % ccwd.command
al@Als-MacBook-Pro ~ %

'/Users/al'がクリップボードにコピーされています。

Ubuntu Linux

Pythonファイルを/home/al/Scripts/ccwd.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容の ccwdファイルを作成します。

#!/usr/bin/env bash
source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/ccwd.py
deactivate

このccwdシェルスクリプトにread -p "Press any key to continue..." -n1 –sの行は必要ありません。ターミナルからのみ実行して、Dashからは実行しないからです。ターミナルウィンドウはこのPythonスクリプトを実行後も消えません。

ターミナルからcdでScriptsフォルダに移動し、chmod u+x ccwdを実行します。

al@al-VirtualBox:~$ cd ~/Scripts
al@al-VirtualBox:~/Scripts$ chmod u+x ccwd

これでどのフォルダにいてもこのプログラムをターミナルからccwdで実行できるようになりました。

al@al-VirtualBox:~$ ccwd
al@al-VirtualBox:~$

'/home/al'がクリップボードにコピーされています。

短いプログラム:クリップボードレコーダー

あるウェブページ中のリンクURLをコピーしてスプレッドシートに貼り付ける作業をしているとしましょう。(第13章ではページのHTMLソースからすべてのリンクをスクレイピングする方法を説明しますが、ここではそのうちのいくつかのページだけが必要で、人間が一つずつ判断するという状況を想定します。)以下のステップでこの作業を行うでしょう。

  1. ウェブブラウザでリンクを右クリックする。

  2. コンテキストメニューから「リンクのコピー」または「リンクアドレスのコピー」を選ぶ。

  3. スプレッドシートに切り替える。

  4. CTRL-Vを押してリンクを貼り付ける。

  5. ウェブブラウザに切り替える。

特にページに数十や数百のリンクがあれば、これは退屈な作業です。この作業を素早く行えるように、ちょっとしたクリップボードレコーダープログラムを作成しましょう。そのプログラムを自分が使っているコンピュータにデプロイし、必要なときに簡単に実行できるようにします。このプログラムは、新しいテキストがコピーされたかどうかクリップボードを監視して、コピーされたらその新しいテキストをターミナル画面に表示します。このようにして、5ステップの手順を2ステップに短縮できます。

  1. ウェブブラウザでリンクを右クリックする。

  2. コンテキストメニューから「リンクのコピー」または「リンクアドレスのコピー」を選ぶ。

それから、クリップボードレコーダーがターミナルウィンドウに出力したテキストをすべてコピーし、スプレッドシートに一度で貼り付けます。次のコードをcliprec.pyという名前で保存してください。

import pyperclip, time

print('Recording clipboard... (Ctrl-C to stop)')
previous_content = ''
try:
    while True:
        content = pyperclip.paste()  # クリップボードの内容を取得

        if content != previous_content:
            # 前の内容と異なれば出力
            print(content)
            previous_content = content

        time.sleep(0.01)  # CPUを専有してしまわないように停止
except KeyboardInterrupt:
    pass

冒頭からプログラムの各部を見ていきましょう。

import pyperclip, time

print('Recording clipboard... (Ctrl-C to stop)')
previous_content = ''

このプログラムは、クリップボードからテキストをコピーアンドペーストしますので、pyperclipモジュールをインポートする必要があります。sleep()関数を使うためにtimeモジュールもインポートします。プログラムが実行中であり、CTRL-Cで停止させられる旨のメッセージを表示します。変数previous_contentを追跡してクリップボードの内容の変化を検知します。最初は空文字列に設定します。

try:
    while True:
        content = pyperclip.paste()  # クリップボードの内容を取得

ブログラムの大部分は、tryブロック内のwhile無限ループです。ユーザーがCTRL-Cを押すと、KeyboardInterrupt例外が送出され、プログラム実行はソースコードの下のほうのexceptブロックに移動します。

このループではクリップボードの内容を常時監視し、新しいテキストがコピーされるたびに記録します。ループ内ではまずpyperclip.paste()を呼び出してクリップボードのテキストを取得します。

        if content != previous_content:
            # 前の内容と異なれば出力
            print(content)
            previous_content = content

クリップボードの現在の内容が前の内容と異なっていたら、現在の内容を出力し、previous_contentをcontentに更新します。ループを反復して、次にユーザーが新しいテキストをクリップボードにコピーするのに備えます。

        time.sleep(0.01)  # CPUを専有してしまわないように停止

クリップボードの内容が前の内容と同じなら、プログラムは何もしません。しかし、このプログラムはこのループを1秒で何万回もループしていまい、ユーザーがそんなに速くクリップボードを更新することはありそうにありません。(そんなに速くクリップボードを更新したらキーボードのCTRL キーとCキーがすり減ってしまいます。)無意味にループを高速で反復してこのプログラムがCPUを専有してしまわないように、ループがクリップボードの更新をチェックさせるのに0.01秒遅延させます。これなら1秒に100回の実行です。

except KeyboardInterrupt:
    pass

プログラムの最後の部分はexcept節で、pass文にしています。この文は何も行いませんが、Pythonではexcept文に続くブロックには1行以上が必要です。pass文が存在するのはそのためです。ユーザーがCTRL-Cを押すと、プログラム実行はこのexcept節に移動し、プログラムの最後まで進んで終了します。

このプログラムを実行すると、複数のアプリを行ったり来たりせずに複数の内容をコピーできます。これくらい小さいプログラムでも作業フローを随分簡単にできます。毎日行うような作業なら特に効果的です。このプログラムをデプロイする手順をOSごとに説明します。

Windows

PythonファイルをC:\Users\al\Scripts\cliprec.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容のcliprec.batファイルを作成します。

@call %HOMEDRIVE%%HOMEPATH%\Scripts\.venv\Scripts\activate.bat
@python %HOMEDRIVE%%HOMEPATH%\Scripts\cliprec.py %*
@pause
@deactivate

これで、ターミナルから、あるいはWindowsキーとRキーを同時に押すと表示される実行ダイアログから、cliprecと入力してこのプログラムを実行できるようになりました。

macOS

Pythonファイルを/Users/al/Scripts/cliprec.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容の cliprec.commandファイルを作成します。

source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/cliprec.py
deactivate

ターミナルからcdでScriptsフォルダに移動し、chmod u+x cliprec.commandを実行します。

al@Als-MacBook-Pro ~ % cd ~/Scripts
al@Als-MacBook-Pro Scripts % chmod u+x cliprec.command

これで⌘-スペースを押してSpotlightを立ち上げ、cliprec.commandと入力してこのプログラムを実行できるようになりました。

Ubuntu Linux

Pythonファイルを/home/al/Scripts/cliprec.py(alを自分のユーザー名に変更してください)に保存してください。同じScriptsフォルダに以下の内容の cliprecファイルを作成します。

#!/usr/bin/env bash
source ~/Scripts/.venv/bin/activate
python3 ~/Scripts/cliprec.py
read -p "Press any key to continue..." -n1 –s
deactivate

ターミナルからcdでScriptsフォルダに移動し、chmod u+x cliprecを実行します。

al@al-VirtualBox:~$ cd ~/Scripts
al@al-VirtualBox:~/Scripts$ chmod u+x cliprec

最後に、以下の内容の~/.local/share/applications/cliprec.desktopファイルを作成して保存します。

[Desktop Entry]
Name=Clipboard Recorder
Exec=gnome-terminal -- /home/al/cliprec
Type=Application

これで、Windowsキーを押してDashを立ち上げて、Clipboard Recorderと入力するか最初の数文字を入力して自動補完で選ぶかすれば、このプログラムを実行できるようになりました。

PyInstallerでPythonプログラムをコンパイルする

Pythonはインタープリタ型の言語であるとしばしば言われますが、プログラミング言語自体はインタープリタ型でもコンパイル型でもありません。どの言語でもインタープリタまたはコンパイラを作ることができます。Pythonで書かれたプログラムはたいていインタープリタによって実行されます。それでも、PyInstallerパッケージで、Pythonのコードから、コマンドラインから実行できる実行ファイルを作成することもできます。

PyInstallerはPythonプログラムをマシンコード(機械語)そのものにコンパイルするわけではありません。Pythonインタープリタとコードのコピーを含む実行ファイルを作成します。そのため、プログラムのサイズは大きくなってしまいがちです。単純な“Hello, world”プログラムでもPyInstallerでコンパイルすると約8MBのサイズになります。アセンブリ言語で書いたときよりも千倍ほど大きいサイズです。しかしながら、Pythonプログラムをコンパイルすると、Pythonをインストールしていない人と共有できるという利点があります。一つの実行ファイルを共有するだけです。

pip install pyinstallerを実行するとPyInstallerをインストールできます。実行したいOSでPyInstallerを実行する必要があります。つまり、WindowsでPyInstallerを実行したらWindowsの実行ファイルが作成され、macOSやLinuxでは実行できません。macOSやLinuxで実行した場合も、同様にそのOSでの実行ファイルが作成されます。

ターミナルから次のコマンドを実行して(macOSとLinuxではpythonをpython3にしてください)、yourScript.pyという名前のPythonスクリプトをコンパイルしてください。

C:\Users\al>python -m PyInstaller --onefile yourScript.py
378 INFO: PyInstaller: X.X.X
378 INFO: Python: 3.XX.XX
392 INFO: Platform: Windows-XX-XX.X.XXXX
393 INFO: wrote C:\Users\al\Desktop\hello-test\hello.spec
399 INFO: UPX is not available.
--snip--
11940 INFO: Appending PKG archive to EXE
11950 INFO: Fixing EXE headers
13622 INFO: Building EXE from EXE-00.toc completed successfully.

PyInstallerはPとIが大文字です。大文字と小文字を間違えると“No module named pyinstaller”エラーメッセージが表示されます。また、--onefile引数はダッシュが2つです。

PyInstallerを実行すると、buildフォルダ(削除しても構いません)とdistフォルダが作成されます。distフォルダの中に実行プログラムが入っています。そのための仮想環境を作成する必要はありません。このプログラムを他のコンピュータにコピーしたりメールに添付したりできます。もっとも、セキュリティ上の理由から、多くのメールプロバイダでは実行プログラムを含むメールはブロックされる可能性があります。

基本的なPythonプログラムであればこの方法で実行ファイルを作成できます。詳細はhttps://pyinstaller.orgのオンラインドキュメントで説明されています。

まとめ

本章では、プログラムを素早く便利に実行できるように、コードエディタの世界から抜け出して、プログラムをデプロイする方法を説明しました。現代的なグラフィックスを備えないテキストベースユーザーインターフェイスを持つプログラムを設計する方法も説明しました。GUIはユーザーにとって使いやすいですが、TUIはコードがシンプルです。自分の作業を自動化する場合、プロが作ったアプリケーションのような見た目にする必要はなく、そこに労力をかける価値はありません。動作すればよいのです。

とはいえ、プログラムを使いやすくする設計がいくつか考えられます。馴染みのない人が多いでしょうが、コマンドラインターミナルを使うと便利なことが多いです。cdやdir/lsでのファイルシステムの移動と確認、PATH環境変数、コマンドライン引数など、コマンドラインの概念に馴染むにはしばらく時間がかかります。しかし、ターミナルからだとコマンドを素早く発行してプログラムを実行できます。特にプログラムを書き終えてデプロイしたら素早くプログラムを実行できます。

本章ではサードパーティーパッケージをいくつか取り上げました。Bextパッケージを使うと、テキストに色をつけたり、カーソルの位置を指定したり、画面をクリアしたりできます。PyMsgBoxパッケージを使うと、ターミナルウィンドウではない、アラートや基本的な入力のためのGUIボックスを作れます。同じパッケージで互換性のない複数のバージョンを使わなければならない日がいつか来るので、別々の仮想環境でスクリプトを実行するのが望ましいです。Pythonに付属しているvenvモジュールで仮想環境を作成できます。仮想環境はターミナルから有効化し、パッケージをインストールする場所を分けられるので、既存のプログラムを壊してしまう心配から解放されます。

最後に、自分が書いた.pyファイルを、PyInstallerパッケージで実行プログラムにコンパイルする方法を説明しました。数メガバイトのサイズになりますが、Python(とプログラムで利用しているサードパーティーパッケージ)を持っていない人ともプログラムを共有できます。

本章ではプログラミング言語の概念を説明したわけではなく、プログラムを日常的に使いやすくて便利にする方法を説明しました。本書のここまでの部分で、基本的なプログラムを作成するのに十分なPythonの構文を学びました(まだまだ学ぶことはありますが)。次章から、サードパーティーのパッケージを使って、プログラムができることを拡張していきます。

練習問題

  1. フォルダの中身を一覧表示するコマンドは、Windowsでは何ですか? macOSとLinuxでは何ですか?

  2. PATH環境変数には何が入っていますか?

  3. 変数__file__には何が格納されていますか?

  4. ターミナルウィンドウのテキストを消すコマンドは、Windowsでは何ですか? macOSとLinuxでは何ですか?

  5. 新しい仮想環境をどうやって作成しますか?

  6. プログラムをコンパイルする際にPyInstallerに渡すコマンドライン引数は何ですか?

練習プログラム:自分が書いたプログラムのデプロイ

PATHフォルダ内にシェルスクリプトを作成して、あるいはPyInstallerでコンパイルして、既存のプログラムを実行しやすくしてください。以下のプロジェクトが対象になります。

  • 第11章の「フォルダをZIPファイルにバックアップする」
  • 第9章の「長い文書から連絡先情報を抽出する」
  • 第8章の「ウィキマークアップに箇条書き記号を追加する」
  • 第7章の「対話的なチェス盤シミュレーター」
  • 簡単に起動できるようにしたいその他のプログラム