Pythonの標準ライブラリを用いると、シェルスクリプト(shやbash)と同等の操作がPython上で行えます。
Pythonを用いることには次のような利点があります。
特に後者は、Pythonの強みである「言語自体の汎用性とライブラリによる専門性」を表していると言えるでしょう。
本稿では、Pythonを用いてファイル操作を行う方法と、プロセスを実行する方法について、シェルのコマンドと対応させて簡単に紹介します。
pwdやlsなど、多くのファイル操作コマンドは、標準ライブラリosモジュールを通して実現されています。
現在の作業ディレクトリのパスはos.getcwd()によって得られ、文字列型として返されます。
import os
working_dir = os.getcwd()
cd path は os.chdir(path) です。
Python上での作業ディレクトリの変更は、Pythonのセッションが終了するまで(IPythonなどを閉じるまで)有効です。
os.chdir("./my-dir")
ls path は os.listdir(path) で、pathにおけるファイルなどを、文字列のリストとして返してくれます。
os.listdir('.')
os.listdirでは、任意の1文字を表す ? や任意の文字 * などのワイルドカード、または正規表現を使うことができません。
ワイルドカードや正規表現を用いて特定のファイル名のみをリストしたいときは、標準ライブラリのglobを用います。
glob.glob(path)で、pathに一致するファイルをリストとして抽出できます。ただし、os.listdirと異なり、ファイル名でなくそのパスを返していることに気をつけましょう。
import glob
ls2 = glob.glob("./*.txt")
for f in ls2:
print(f)
glob.glob()
関数に recursive=True
を与えてやると再帰的にファイル検索をします。
glob.glob("./**/*.txt", recursive=True)[:10]
さらに高機能なPath操作をしたい場合は、pathlibライブラリが便利です。
Pathオブジェクトでディレクトリを指定し、glob関数をワイルドカードとともに呼び出すと、サブディレクトリまで再帰的にファイルを検索します。Pathオブジェクトはイテレータになっているため、最初から全部のファイルを探すのではなく、forループごとに次のファイルへ進んでいきます。普通にリストが欲しい場合は、list(path.glob('xxxx'))
を呼び出してください。
from pathlib import Path
# Pathオブジェクト(ここでは、今いるディレクトリを基準に作る)
path = Path('.')
# 再帰的にサブディレクトリ内の '.txt'ファイルを全て列挙する
for p in path.glob('./**/*.txt'):
print(p)
より詳細な情報は、pathlib公式ドキュメントを参照してください。
mkdir dirname は os.makedirs(dirname) です。
os.mkdir
もありますが、再帰的にディレクトリを作る時に少し面倒なので、 os.makedirs
を使うと良いです。
os.mkdir('./new-dir')
os.makedirs('./new-dir/dep1/dep2/dep3')
# os.mkdir('./new-dir/dep1/dep2/dep3') はエラー
os.path.exists
関数を使います。
os.path.exists('new-dir')
cpやmvなどのコマンドはshutilモジュールによって提供されています。
cp src dst は shutil.copy(src, dst) です。
import shutil
print(glob.glob("./*.txt"))
shutil.copy("mytext1.txt", "copied_mytext1.txt")
print(glob.glob("./*.txt"))
また、ディレクトリまるごとのコピー(cp -r)は、 shutil.copytree(src, dst) です。
mv src dst は shutil.move(src,dst) です。rename もできます。
print(glob.glob("./*.txt"))
shutil.move("copied_mytext1.txt", "moved_mytext1.txt")
print(glob.glob("./*.txt"))
rm path は os.remove(path) です。
print(glob.glob("./*.txt"))
os.remove("moved_mytext1.txt")
print(glob.glob("./*.txt"))
os.rmdir('new-dir/dep1/dep2/dep3')
os.path.exists('new-dir/dep1/dep2/dep3')
shutil.rmtree('new-dir')
osのサブモジュールpathは、パスの操作に便利な関数を提供しています。
os.path.exists(path) pathで表されるファイル・ディレクトリが存在していればTrue、存在していなければFalseを返す。
os.path.isdir(path) pathがディレクトリならばTrue、ファイルやシンボリックリンクならばFalseを返す。
os.path.isfile(path) pathがファイルならばTrue、ディレクトリやシンボリックリンクならばFalseを返す。
for file in glob.glob('./**', recursive=True)[:10]:
print(f'is_dir = {os.path.isdir(file)} : is_path = {os.path.isfile(file)}')
次の2つは、パスの文字列を場所+ファイル名に分割したり、拡張子を取り出すのに使えます。
dir, file = os.path.split(path) pathのファイル等の場所をdirに、ファイル名をfileに文字列として返す。
root, ext = os.path.splitext(path) pathのファイル等の拡張子をextに、拡張子の手前までをrootに文字列として返す。
上記のファイル操作コマンドだけでなく、Pythonからシェルを呼ぶことで任意のコマンドを実行することができます。
subprocess
モジュールを使いましょう( os.system
は非推奨)。
subprocess.run - 公式ドキュメント によると、基本的にPythonから子プロセスを実行する場合は、 subprocess.run
のみを使えば良いように作られているらしいです。
試しに、 ディレクトリ構成をみやすく表示してくれる tree
というプログラムを実行してみます。
# 表示例
! tree -L 2 .
これを subprocess
モジュールを使って実行してみましょう。
subprocess.run
の引数には、空白で区切ったコマンドをリストで渡してあげます。
import subprocess
subprocess.run(['tree', '.', '-L', '2'])
CompletedProcess
というオブジェクトが返っており、 returncode=0
つまりコマンドが正常終了したことがわかります。
標準出力や標準エラーを受け取りたいときは、 capture_output=True
をセットしてあげます。
proc = subprocess.run(['tree', '.', '-L', '2'], capture_output=True)
proc
# proc.stdoutはバイナリなので decode してやると文字列に変換できる
print(proc.stdout.decode('utf-8'))
# あえてエラーを起こしてみる (tree に存在しないオプション -hoge を与えてみる)
proc_failed = subprocess.run(['tree', '-hoge'], capture_output=True)
print(proc_failed.stderr.decode('utf-8'))
stdout=PIPE
と stderr=STDOUT
を subprocess.run
に渡すと、エラーも標準出力と一緒に出力されます。
proc = subprocess.run(['tree', '-hoge'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
print(proc.stdout.decode('utf-8'))
# file objectをstdoutに渡す
with open('result.txt', 'w') as fp:
proc = subprocess.run(['tree', '.', '-L', '2'], stdout=fp)
! cat result.txt
! rm result.txt
Jupyter notebookやIPythonコンソールのみで使える非常に便利なマジックコマンドと呼ばれるものがあります。
%
から始まるコマンド、例えば%pwd
や%cd
などです。実は、有名なコマンドはだいたい%
がなくても実行できちゃいます。しかも、変数への代入もできるのでコマンドの結果を使ったプログラムを書くことができます。
my_wd = %pwd
# 他には %cd や %mkdir などがある
Jupyterからシェルのコマンドそのものを呼ぶこともできます。
!
から始まる行は、!
以降の文字列はJupyterが起動しているOSのシェルで実行されます。
コマンド実行によって標準出力に吐かれるログを変数に代入ができます。
!which python
ちなみにwhich
コマンドはプログラムバイナリの位置を教えてくれるコマンドです。
python_location = !which python
python_location
シェルコマンドにPythonの変数を埋め込みたい時ありませんか?実はこれもできます。Python変数の前に$
をつけることで、シェルの変数のように扱えます。
!ls
a = 'child-dir'
!ls ./$a
このコマンドはforループの中でも使えます。
langs = ['python', 'ruby', 'java']
for lang in langs:
path_bin = !which $lang
print(path_bin)
ただし、!
を使ったシェルの呼び出しはあなたの環境でしか動かない可能性があるので、他の人にプログラムを渡す場合は使ってはいけません。
osなどの標準ライブラリは環境依存しないように作られているので、こちらを使うようにしましょう。