読者のみなさんは、「シェルスクリプト」をご存知ですか。簡単にいってしまうと、「“cd”や“ls”といったターミナルで利用するOSのコマンド」とそれを制御する文法に従って書かれたプログラムのことです。今回と次回は、Pythonでそれ相当のことを実行する方法について紹介します。

「シェル」とは

シェルは以下の図のようにOS(Kernel)とユーザの間に存在するコマンドベースのインタフェースとなります。たとえば「cd」というコマンドは、ユーザがOSに対して「現在のディレクトリを変更して」と依頼し、OSがシェルを介してユーザの要求を受け取り応答を返すという流れで動きます。

シェルのイメージ

シェルスクリプトは、ユーザからシェルへの要求をプログラムに沿って実行するものです。たとえばファイルのバックアップを定期的に実行しようと思った場合、

  1. NFSでリモートのストレージをマウント
  2. ディレクトリA 配下を日付をつけて丸々コピー
  3. ディレクトリB 配下を日付をつけて丸々コピー
  4. ディレクトリC 配下を日付をつけて丸々コピー
  5. ストレージをアンマウント

という処理をシェルスクリプトで作成し、それをcronなどで定期的に呼び出すという利用例が考えられます。

これぐらいならシェルスクリプトで作成すれば十分なのですが、シェルスクリプトの文法はPythonほど強力ではなく、複雑なことをするのには向いていません。真偽のほどは定かではありませんが、「Perl」と呼ばれている古い有名なスクリプト言語は、Bashより前のシェルスクリプトが貧弱で互換性に乏しいことを理由に開発されたという話を聞いたことがあります。こういった話からもわかるように、スクリプト言語はシェルスクリプトとの親和性が非常に高いため、スクリプト言語をシェルスクリプトのように使うということが可能なのです。

Pythonをシェルスクリプトのように使う際は以下のイメージのような形で動いていると考えてもらうとわかりやすいかもしれません。

Pythonをシェルスクリプトのように使う際のイメージ

先ほどはユーザがシェルを操作していましたが、それをPythonにやらせているだけです(なお、今まではシェルを経由せずにPythonが直接OSとやりとりをしていました)。

シェルを使いこなせないとシェルスクリプトを書けないように、Pythonでシェルプログラミングのようなことをするには、ある程度シェルを知っていることが前提となります。なお、今回はLinux上での利用を前提に記事を書きます。Posix準拠のMacだとほぼ同じことができると思いますが、Windowsは利用するコマンドがまったく違うので気をつけてください。

OSのパス

毎度のことですが、今回も脱線からはじまります。シェルスクリプトはOSの機能を利用するため、どうしてもOSのディレクトリ構造を意識する必要があります。そのため、先にOSのパスの基本概念と、Pythonでパスを操作する方法について話してしまいます。

基本的なことですが、パスはOS上のファイルやディレクトリの所在地を示すために利用されています。

たとえば上記の図でいうと、右上にあるpython.pptxというファイルの所在地は“/Users/yuichi/Documents/python.pptx”と示せます。OSの階層構造は、たとえばルートを地球とすると、その下の階層が国、都道府県(State)、町……と続いていくように、エリアを区切っていくイメージです。ただ、階層の深さは / 直下のファイルから、資源が許す限り深くすることができるという点で住所と異なります。

先ほどの例ではOSのディレクトリ構造をトップから辿ることで位置を指し示しましたが、場所Aから場所Bを相対的に指定することでも位置を示すことができます。

たとえば上記の図でtest.txtを指し示す際、今自分がVMディレクトリーにいるのであれば、2つ上の階層まで戻り(Documents -> yuichi)、そこにあるtest.txtを指し示せばいいことがわかります。この際、上の階層を表現する必要がありますが、それは特別な記法“../”で示されます。2つ上であれば“../../”と繰り返します。必要であれば、その後ろにディレクトリ名やファイル名を続けていきます。そのため、VMディレクトリー上にいるとし、そこからtest.txtを示すのであれば“../../test.txt”と指定すれば問題ありません。

今自分がいるディレクトリ自体を示す特別な記法は“./”です。今自分がいるディレクトリのファイルなどは、特に“./”を使わずにも指定できるので、必ずしも使う必要はないのですが、「今いるディレクトリであるということをあえて明示する場合」や「シェルスクリプトが書かれたファイルを実行」という特別な場合に利用されることが多いです。

Pythonによるパスの操作

それでは実際にPythonでパスを操作する方法について扱います。といっても話すことはそれほど多くなく、現在のパスの取得と、パスの変更(今いるディレクトリの変更)、絶対パスの取得方法あたりだけです。当面はこれだけを知っていれば十分かと思います。

まず最初に現在のパスの取得です。これは以下のようにして行います。

>>> import os
>>> os.getcwd()
'/Users/yuichi'

まずosモジュールをimportし、getcwd()関数を使います。getcwd()関数は現在のパスを文字列として返します。

上記の例では'/Users/yuichi'が返されていますが、これはPythonコマンドが実行されたディレクトリが '/Users/yuichi' だったためです。試しに、

import os
print(os.getcwd())

というプログラムをデスクトップ上のtest.py (/Users/yuichi/Desktop/test.py)に記述し、これをさまざまなディレクトリで実行してみます。

YUIITO-M-64WZ% pwd
/Users/yuichi
YUIITO-M-64WZ% python Desktop/test.py
/Users/yuichi
YUIITO-M-64WZ% 
YUIITO-M-64WZ% python /Users/yuichi/Desktop/test.py
/Users/yuichi

この出力を見てもらうとわかるように、pwdで確認した「今自分がいるディレクトリ」でほかのディレクトリに存在するPythonプログラムが実行されています。相対パスの指定や絶対パスの指定は挙動に関与していません。そのため、自分が作成したモジュールが読み込めない、ファイルが読み込めないといった問題を防ぐためにも起点となるPythonプログラムのファイルが存在するディレクトリでプログラムの実行を行うのが無難かもしれません。

実は、私はつい最近もこのトラブルにあいました。私が開発を指揮しているプロジェクトで、開発エンジニアから「Pythonコマンドだと実行できるが、cronで呼び出すと動かない」という問題の報告をもらって調査したところ、まさにこの問題だったのです。よく遭遇する問題なので、心の片隅にでも留めておいてください。

話をもとに戻して、次にディレクトリの移動について説明します。ディレクトリの移動は以下のようにして行います。

>>> import os
>>> os.getcwd()
'/Users/yuichi'
>>> os.chdir('Desktop')
>>> os.getcwd()
'/Users/yuichi/Desktop'
>>> os.chdir('../../')
>>> os.getcwd()
'/Users'

OSのcdコマンドと同じですね。相対パスだけでなく絶対パスによる作業ディレクトリの変更も可能です。

次に絶対パスの取得です。相対パスはどこの作業ディレクトリにいるかに依存して、実際に指し示すディレクトリが変わってしまうので、時と場合によっては不便です。絶対パスは長くて記述するのが面倒なものの、どこからでも必ず一意にディレクトリやファイルを指定できるので便利です。

>>> import os
>>> os.path.abspath('./')
'/Users/yuichi/Desktop'

>>> os.path.abspath('test.py')
'/Users/yuichi/Desktop/test.py'
>>> os.path.abspath('test999.py')
'/Users/yuichi/Desktop/test999.py'

現在の作業ディレクトリの絶対パスを得るためには、前にお伝えした特別なキーワード“./”を指定すればよいです。ファイル名やディレクトリ名を記述した場合は「作業ディレクトリの絶対パス + 与えられた文字列」を表示します。存在しないファイル名を指定してもエラーにはなりません。

ファイルやディレクトリの操作

作業ディレクトリの変更や取得といったことだけでなく、実際にファイルやディレクトリを操作することも可能です。よく使うのがファイルの存在の有無の確認です。ファイルの読み込みや書き込みの際の存在確認によく利用されます。

>>> os.path.exists('/Users/yuichi/Desktop/test.py')
True
>>> os.path.exists('/Users/yuichi/Desktop/test999.py')
False

存在の確認だけでなく、それがディレクトリかファイルかといった判定もできます。OSによってはディレクトリもファイルの一種なのですが、Pythonではディレクトリはファイルではないものとして扱われます。

>>> os.path.isdir('/Users/yuichi/Desktop/test.py')
False
>>> os.path.isfile('/Users/yuichi/Desktop/test.py')
True
>>> os.path.isfile('/Users/yuichi/Desktop')
False

その次によく利用するのが、ある特定ディレクトリ配下のファイル一覧を取得する操作あたりでしょうか。Unixのlsコマンドに相当します。

>>> os.listdir('/Users/yuichi/Desktop/testdir')
['dir1', 'file1', 'file2']

名前を見てもらうと想像できると思いますが、ディレクトリもファイルも返してきます。ただ、子ディレクトリの中身については返してきません。

最後にディレクトリの作成と、ファイルとディレクトリの削除について説明します。ファイルの作成は通常は「書き込み」で実現します。ディレクトリの作成はmkdir関数で行います。

>>> os.mkdir('/Users/yuichi/Desktop/testdir/dir2')
>>> os.listdir('/Users/yuichi/Desktop/testdir')
['dir1', 'dir2', 'file1', 'file2']

消去についてはremove関数とrmdir関数を使います。

>>> os.remove('/Users/yuichi/Desktop/testdir/file1')
>>>
>>> os.remove('/Users/yuichi/Desktop/testdir/dir2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 1] Operation not permitted: '/Users/yuichi/Desktop/testdir/dir2'
>>>
>>> os.rmdir('/Users/yuichi/Desktop/testdir/dir2')
>>> 
>>> os.listdir('/Users/yuichi/Desktop/testdir')
['dir1', 'file2']

表示を見てもらうとわかるように、removeはファイルでrmdirはディレクトリに対する関数です。ディレクトリを再帰的に削除する関数os.removedirsという関数もありますが、これはディレクトリの中にファイルが入っていると失敗します。

パスやファイル操作の詳細については以下のドキュメント参照ください。


演習1

あるディレクトリの「直下」に存在するすべてのファイル名(ディレクトリは不要)を書き出すプログラムを作ってください。

演習2

演習1のプログラムを改造し、あるディレクトリの「配下すべて」を対象としてください。

※解答はこちらをご覧ください。


パスの説明だけで長くなってしまったので、つづきは後編で紹介することにします。次回はOSのコマンドの実行方法と、シェルスクリプトの実用例について取り扱います。

執筆者紹介

伊藤裕一(ITO Yuichi)

シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。

もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択

詳細(英語)はこちら