トップページに戻る

shやBashの代わりにPythonを使う

目次

  • はじめに
  • pwd: 作業ディレクトリのパスを表示する
  • ls: ファイルをリストする
  • cd: 作業ディレクトリを変更する
  • cp: ファイルをコピーする
  • mv: ファイルを移動する
  • rm: ファイルを消去する
  • mkdir: ディレクトリを作る
  • ファイル操作その他
  • プロセスの実行

はじめに

Pythonの標準ライブラリを用いると、シェルスクリプト(shやbash)と同等の操作がPython上で行えます。
Pythonを用いることには次のような利点があります。

  • Pythonの強力な文字列操作メソッドを用いられる
  • シェルスクリプトの働きとPythonのその他のライブラリ(NumPyなど)とをシームレスにつなぐことができる

特に後者は、Pythonの強みである「言語自体の汎用性とライブラリによる専門性」を表していると言えるでしょう。

本稿では、Pythonを用いてファイル操作を行う方法と、プロセスを実行する方法について、シェルのコマンドと対応させて簡単に紹介します。

pwd: 作業ディレクトリのパスを表示する

pwdやlsなど、多くのファイル操作コマンドは、標準ライブラリosモジュールを通して実現されています。
現在の作業ディレクトリのパスはos.getcwd()によって得られ、文字列型として返されます。

In [1]:
import os
working_dir = os.getcwd()

ls: ファイルをリストする

ls path は os.listdir(path) で、pathにおけるファイルなどを、文字列のリストとして返してくれます。

In [2]:
os.listdir('.')
Out[2]:
['c02.csv',
 'Sample.gif',
 'c01.csv',
 'Pandas\u3000―\u3000データ分析.ipynb',
 'Pandas―データ分析(3)GroupBy.ipynb',
 'Seaborn ― matplotlib をより美しく、使いやすく.ipynb',
 'libsobol.c',
 'Fortran, C言語 との連携.ipynb',
 'RK4.f90',
 'Pandas―データ分析(5)応用:都道府県別人口推移.ipynb',
 'RK4_f2py.f90',
 'Pandas―データ分析(4)ファイル入出力.ipynb',
 'double_pendulum.gif',
 'SymPyー代数演算(2)応用:二重振り子.ipynb',
 'Pandas―データ分析(1)Series.ipynb',
 '.ipynb_checkpoints',
 'SymPyー代数演算(1)使い方.ipynb',
 'shやBashの代わりにPythonを使う.ipynb',
 'use_RK4.py',
 'SymPyー代数演算.ipynb',
 'Pandas―データ分析(2)DataFrame.ipynb',
 'Animation.ipynb']

os.listdirでは、任意の1文字を表す ? や任意の文字 * などのワイルドカード、または正規表現を使うことができません。
ワイルドカードや正規表現を用いて特定のファイル名のみをリストしたいときは、標準ライブラリのglobを用います。
glob.glob(path)で、pathに一致するファイルをリストとして抽出できます。ただし、os.listdirと異なり、ファイル名でなくそのパスを返していることに気をつけましょう。

In [3]:
import glob
ls2 = glob.glob("./*.ipynb")
for f in ls2:
    print(f)
./Pandas ― データ分析.ipynb
./Pandas―データ分析(3)GroupBy.ipynb
./Seaborn ― matplotlib をより美しく、使いやすく.ipynb
./Fortran, C言語 との連携.ipynb
./Pandas―データ分析(5)応用:都道府県別人口推移.ipynb
./Pandas―データ分析(4)ファイル入出力.ipynb
./SymPyー代数演算(2)応用:二重振り子.ipynb
./Pandas―データ分析(1)Series.ipynb
./SymPyー代数演算(1)使い方.ipynb
./shやBashの代わりにPythonを使う.ipynb
./SymPyー代数演算.ipynb
./Pandas―データ分析(2)DataFrame.ipynb
./Animation.ipynb

再帰的なファイルリスト化

glob.glob() 関数に recursive=True を与えてやると再帰的にファイル検索をします。

In [4]:
glob.glob("../**/*.ipynb", recursive=True)[:10]
Out[4]:
['../スライド/1日目スライド.ipynb',
 '../応用編/Pandas\u3000―\u3000データ分析.ipynb',
 '../応用編/Pandas―データ分析(3)GroupBy.ipynb',
 '../応用編/Seaborn ― matplotlib をより美しく、使いやすく.ipynb',
 '../応用編/Fortran, C言語 との連携.ipynb',
 '../応用編/Pandas―データ分析(5)応用:都道府県別人口推移.ipynb',
 '../応用編/Pandas―データ分析(4)ファイル入出力.ipynb',
 '../応用編/SymPyー代数演算(2)応用:二重振り子.ipynb',
 '../応用編/Pandas―データ分析(1)Series.ipynb',
 '../応用編/SymPyー代数演算(1)使い方.ipynb']

pathlib: 再帰的なファイル一覧

さらに高機能なPath操作をしたい場合は、pathlibライブラリが便利です。
Pathオブジェクトでディレクトリを指定し、glob関数をワイルドカードとともに呼び出すと、サブディレクトリまで再帰的にファイルを検索します。Pathオブジェクトはイテレータになっているため、最初から全部のファイルを探すのではなく、forループごとに次のファイルへ進んでいきます。普通にリストが欲しい場合は、list(path.glob('xxxx'))を呼び出してください。

In [5]:
from pathlib import Path

# Pathオブジェクト(ここでは、一つ上のディレクトリを基準に作る)
path = Path('..')

#  再帰的にサブディレクトリ内の '.ipynb'ファイルを全て列挙する
for p in path.glob('**/*.ipynb'):
    print(p)
../スライド/1日目スライド.ipynb
../応用編/Pandas ― データ分析.ipynb
../応用編/Pandas―データ分析(3)GroupBy.ipynb
../応用編/Seaborn ― matplotlib をより美しく、使いやすく.ipynb
../応用編/Fortran, C言語 との連携.ipynb
../応用編/Pandas―データ分析(5)応用:都道府県別人口推移.ipynb
../応用編/Pandas―データ分析(4)ファイル入出力.ipynb
../応用編/SymPyー代数演算(2)応用:二重振り子.ipynb
../応用編/Pandas―データ分析(1)Series.ipynb
../応用編/SymPyー代数演算(1)使い方.ipynb
../応用編/shやBashの代わりにPythonを使う.ipynb
../応用編/SymPyー代数演算.ipynb
../応用編/Pandas―データ分析(2)DataFrame.ipynb
../応用編/Animation.ipynb
../応用編/.ipynb_checkpoints/shやBashの代わりにPythonを使う-checkpoint.ipynb
../ライブラリ集/Sympy―代数演算.ipynb
../基礎編/Numpyを使う際の注意点.ipynb
../基礎編/Numpyの基礎(1)導入.ipynb
../基礎編/Numpyの基礎(7)便利なライブラリ群.ipynb
../基礎編/Matplotlib.ipynb
../基礎編/Numpyの基礎(4)データ型.ipynb
../基礎編/Numpyの基礎(6)ブロードキャスト.ipynb
../基礎編/Pythonの基礎2.ipynb
../基礎編/文字列操作(1)str.format.ipynb
../基礎編/Numpyの基礎(8)ファイル入出力.ipynb
../基礎編/Numpyの基礎(5)要素の参照.ipynb
../基礎編/numpy(基礎編).ipynb
../基礎編/文字列操作(2)strやファイル読み込み.ipynb
../基礎編/Pythonの基礎1.ipynb
../基礎編/Numpyの基礎(3)ufunc.ipynb
../基礎編/ファイル読み込み.ipynb
../基礎編/Numpyの基礎(2)生成関数.ipynb

より詳細な情報は、pathlib公式ドキュメントを参照してください。

cd: 作業ディレクトリを変更する

cd path は os.chdir(path) です。
Python上での作業ディレクトリの変更は、Pythonのセッションが終了するまで(IPythonなどを閉じるまで)有効です。

In [6]:
os.chdir("../基礎編")

mkdir: ディレクトリを作る

mkdir dirname は os.makedirs(dirname) です。

os.mkdir もありますが、再帰的にディレクトリを作る時に少し面倒なので、 os.makedirs を使うと良いです。

参照: Pythonでディレクトリ(フォルダ)を作成するmkdir, makedirs

In [7]:
os.mkdir('./new-dir')
In [8]:
os.makedirs('./new-dir/dep1/dep2/dep3')

# os.mkdir('./new-dir/dep1/dep2/dep3') はエラー

ファイル・フォルダが存在しているか確かめる

os.path.exists 関数を使います。

In [9]:
os.path.exists('new-dir')
Out[9]:
True

cp: ファイルをコピーする

cpやmvなどのコマンドはshutilモジュールによって提供されています。
cp src dst は shutil.copy(src, dst) です。

In [10]:
import shutil
print(glob.glob("./*.f90"))
shutil.copy("write_binary.f90", "copied_write_binary.f90")
print(glob.glob("./*.f90"))
['./write_binary.f90']
['./copied_write_binary.f90', './write_binary.f90']

また、ディレクトリまるごとのコピー(cp -r)は、 shutil.copytree(src, dst) です。

mv: ファイルを移動する

mv src dst は shutil.move(src,dst) です。

In [11]:
print(glob.glob("./*.f90"))
shutil.move("copied_write_binary.f90", "moved_write_binary.f90")
print(glob.glob("./*.f90"))
['./copied_write_binary.f90', './write_binary.f90']
['./moved_write_binary.f90', './write_binary.f90']

rm: ファイルを消去する

rm path は os.remove(path) です。

In [12]:
print(glob.glob("./*.f90"))
os.remove("moved_write_binary.f90")
print(glob.glob("./*.f90"))
['./moved_write_binary.f90', './write_binary.f90']
['./write_binary.f90']

フォルダを削除する

os.rmdir() 関数を使います。空でないとエラーが出ます。

フォルダ内のファイルも丸ごと削除したいときは、 shutil.rmtree() 関数を使います。

In [13]:
os.rmdir('new-dir/dep1/dep2/dep3')
os.path.exists('new-dir/dep1/dep2/dep3')
Out[13]:
False
In [14]:
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を返す。

In [15]:
for file in glob.glob('../**', recursive=True)[:10]:
    print(f'is_dir = {os.path.isdir(file)} :  is_path = {os.path.isfile(file)}')
is_dir = True :  is_path = False
is_dir = True :  is_path = False
is_dir = False :  is_path = True
is_dir = False :  is_path = True
is_dir = True :  is_path = False
is_dir = False :  is_path = True
is_dir = False :  is_path = True
is_dir = False :  is_path = True
is_dir = False :  is_path = True
is_dir = True :  is_path = False

次の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 というプログラムを実行してみます。

In [16]:
# 表示例
! tree -L 1 ../..
../..
├── Makefile
├── README.md
├── docs
├── resource
├── scripts
├── src
└── template

5 directories, 2 files

これを subprocess モジュールを使って実行してみましょう。

subprocess.run の引数には、空白で区切ったコマンドをリストで渡してあげます。

In [17]:
import subprocess
subprocess.run(['tree', '../..', '-L', '1'])
Out[17]:
CompletedProcess(args=['tree', '../..', '-L', '1'], returncode=0)

CompletedProcess というオブジェクトが返っており、 returncode=0 つまりコマンドが正常終了したことがわかります。

標準出力や標準エラーを受け取りたいときは、 capture_output=True をセットしてあげます。

In [18]:
proc = subprocess.run(['tree', '../..', '-L', '1'], capture_output=True)
proc
Out[18]:
CompletedProcess(args=['tree', '../..', '-L', '1'], returncode=0, stdout=b'../..\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Makefile\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 README.md\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 docs\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 resource\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 scripts\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 template\n\n5 directories, 2 files\n', stderr=b'')
In [19]:
# proc.stdoutはバイナリなので decode してやると文字列に変換できる
print(proc.stdout.decode('utf-8'))
../..
├── Makefile
├── README.md
├── docs
├── resource
├── scripts
├── src
└── template

5 directories, 2 files

In [20]:
# あえてエラーを起こしてみる (tree に存在しないオプション -hoge を与えてみる)
proc_failed = subprocess.run(['tree', '-hoge'], capture_output=True)
print(proc_failed.stderr.decode('utf-8'))
tree: missing argument to -o option.

コマンド実行結果の標準エラーもまとめて標準出力にする

stdout=PIPEstderr=STDOUTsubprocess.run に渡すと、エラーも標準出力と一緒に出力されます。

In [21]:
proc = subprocess.run(['tree', '-hoge'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
print(proc.stdout.decode('utf-8'))
tree: missing argument to -o option.

コマンド実行結果をファイルに保存する

In [22]:
# file objectをstdoutに渡す
with open('result.txt', 'w') as fp:
    proc = subprocess.run(['tree', '../..', '-L', '1'], stdout=fp)

! cat result.txt
../..
├── Makefile
├── README.md
├── docs
├── resource
├── scripts
├── src
└── template

5 directories, 2 files

IPythonマジックコマンド

Jupyter notebookやIPythonコンソールのみで使える非常に便利なマジックコマンドと呼ばれるものがあります。
%から始まるコマンド、例えば%pwd%cdなどです。実は、有名なコマンドはだいたい%がなくても実行できちゃいます。しかも、変数への代入もできるのでコマンドの結果を使ったプログラムを書くことができます。

In [23]:
my_wd = %pwd

# 他には %cd や %mkdir などがある

Jupyterからシェルのコマンドそのものを呼ぶ

Jupyterからシェルのコマンドそのものを呼ぶこともできます。
! から始まる行は、!以降の文字列はJupyterが起動しているOSのシェルで実行されます。
コマンド実行によって標準出力に吐かれるログを変数に代入ができます。

In [24]:
!which python
/Users/zawawahoge/.pyenv/versions/3.8.0/bin/python

ちなみにwhichコマンドはプログラムバイナリの位置を教えてくれるコマンドです。

In [25]:
python_location = !which python
python_location
Out[25]:
['/Users/zawawahoge/.pyenv/versions/3.8.0/bin/python']

「!」コマンドにPython変数を埋め込む

シェルコマンドにPythonの変数を埋め込みたい時ありませんか?実はこれもできます。Python変数の前に$をつけることで、シェルの変数のように扱えます。

In [26]:
a = '基礎編'
In [27]:
!ls ../$a
Matplotlib.ipynb
NINO.3.csv
Numpyの基礎(3)ufunc.ipynb
Numpyの基礎(1)導入.html
Numpyの基礎(1)導入.ipynb
Numpyを使う際の注意点.ipynb
Numpyの基礎(2)生成関数.ipynb
Numpyの基礎(4)データ型.ipynb
Numpyの基礎(5)要素の参照.ipynb
Numpyの基礎(8)ファイル入出力.ipynb
Numpyの基礎(6)ブロードキャスト.ipynb
Numpyの基礎(7)便利なライブラリ群.ipynb
Pythonの基礎1.ipynb
Pythonの基礎2.ipynb
axis.png
c01.csv
ersst.v4.201702.nc
my_module.py
numpy(基礎編).ipynb
redirected.txt
result.txt
temp
write_binary.f90
ファイル読み込み.ipynb
文字列操作(1)str.format.ipynb
文字列操作(2)strやファイル読み込み.ipynb

このコマンドはforループの中でも使えます。

In [28]:
langs = ['python', 'ruby', 'java']
for lang in langs:
    path_bin = !which $lang
    print(path_bin)
['/Users/zawawahoge/.pyenv/versions/3.8.0/bin/python']
['/usr/bin/ruby']
['/usr/bin/java']

ただし、!を使ったシェルの呼び出しはあなたの環境でしか動かない可能性があるので、他の人にプログラムを渡す場合は使ってはいけません。
osなどの標準ライブラリは環境依存しないように作られているので、こちらを使うようにしましょう。

誤字やおかしい点などがあったら @zawawahoge (Twitter) にお気軽にご連絡ください。

トップページに戻る