この記事はBashスクリプトをPythonスクリプトで置き換えるためのノウハウをまとめていますが、今後加筆修正を行っていく予定です。
文字列の表示
print関数を使用します。これは可変長引数で、複数与えるとデフォルトではスペースで区切って出力します。
echo Hello, world!
↓
print("Hello,", "world!")
標準出力以外に出力したい場合はfile=○○の形で指定します。例えば標準エラーの場合は次のようになります。
echo Error >&2
↓
import sys
print("Error", file=sys.stderr)
ファイルに上書き・追記する場合は次のようになります。
echo foo > file1.txt
echo bar >> file1.txt
echo hoge >> file2.txt
echo fuga >> file2.txt
↓
with open("file1.txt", "w") as f:
print("foo", file=f)
print("bar", file=f)
with open("file2.txt", "a") as f:
print("hoge", file=f)
print("fuga", file=f)
なお、表示する引数間のスペース、及び行末の改行文字は別の文字に変更できます。例えば、区切り文字をタブ、行末の文字をなしにする場合は次のようになります。
print("foo", "bar", sep="\t", end="")
オプションの解析
Bashなどでは色々大変なオプションの解析ですが、Pythonではargparseモジュールで簡単に解析できます。基本的には次のように使います。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", "-v", help="Verbose", action="store_true")
parser.add_argument("--quiet", "-q", help="Suppress messages", action="store_true")
parser.add_argument("target", help="target")
parser.add_argument("source", nargs="+", help="source")
args = parser.parse_args()
function(args.option)
各オプションにはargs.verbose・args.quietなどでアクセスできます。
詳しくは公式の解説を見てください。
https://docs.python.jp/3/library/argparse.html
コマンドの実行
Python3.5からsubprocess.runが使えます。過去バージョンとの互換性を考えないならこれを使います。
ただ実行するだけ
g++ sample.cpp -o sample -std=gnu++14 -march=native -O3
↓
import subprocess
subprocess.run(["g++", "sample.cpp", "-o", "sample", "-std=gnu++14", "-march=native", "-O3"])
なお、リスト以外でも、iterableならOKです。(タプルなど)
コマンドが0以外で終了したら例外
check=Trueを指定します。例外はsubprocess.CalledProcessErrorです。
import subprocess
subprocess.run(["git", "push"], check=True)
標準出力・エラーを捕捉
stdout=subprocess.PIPE・stderr=subprocess.PIPEを指定します。それぞれ返り値のstdout・stderr属性で取り出せます。byte型なので注意してください。
import subprocess
result = subprocess.run(["apt", "list", "--upgradable"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, check=True)
process(result.stdout)
標準出力・エラーを捨てる
subprocess.PIPEの代わりにsubprocess.DEVNULLを指定します。
標準入力を指定
subprocess.runの引数にinput=を加えます。byte型で指定してください。
import subprocess
assert subprocess.run(["cat"], input=b"aaa\n", stdout=subprocess.PIPE).stdout == b"aaa\n"
リダイレクト
subprocess.runの引数stdin(inputではない)・stdout・stderrの各引数にopen関数などで開いたファイルオブジェクトを指定します。入出力がテキストデータである限りopenの第2引数はbなしでも大丈夫ですが、あった方が確実です。
ディレクトリ関連
追記: pathlib を使った方が楽です。
from pathlib import Path
python_path = Path("/usr/bin/python3")
以下ではpathlibを使わないレガシーな方法を紹介しています。
カレントディレクトリの取得
os.getcwd()で取得できます。または、pathlibを使う場合はPath()(相対パス)・Path.cwd()またはPath().resolve()(絶対パス)で取得できます。
ディレクトリの変更
os.chdirを使用します。
cd directory
↓
import os
os.chdir("directory")
ディレクトリの中身を表示
os.listdirを使用します。カレントディレクトリの中身を表示する場合には引数は不要です。
foo=$(ls)
↓
import os
foo = os.listdir()
特定のディレクトリの中身を表示したい時にはそのパスを与えます。
foo=$(ls bar)
↓
import os
foo = os.listdir("bar")
pathlib.Pathを使う場合にはメソッドiterdir()を使います。返ってくるのはジェネレータなので、forループを使うなり、list()でリスト化するなりしてください。
from pathlib import Path
foo = Path().iterdir() # foo=$(ls)
bar = Path("baz").iterdir() # bar=$(ls baz)
# ls qux
for path in Path("qux").iterdir():
print(path)
パターンマッチングを使用したい場合は、代わりにglob.glob、またはglob.iglobを使用します。前者はリスト、後者はイテレータを返すので、後者はfor文やmap・filterなどに渡すために使うといいでしょう。
audio_files=$(ls *.wav)
ls *.tmp | xargs rm -f
↓
import glob
import os
audio_files = glob.glob("*.wav")
for tmp_file in glob.iglob("*.tmp"):
os.remove(tmp_file)
pathlib.Pathを使う場合はメソッドglobを使用します。
from pathlib import Path
for tmp_file in Path().glob("*.tmp"):
tmp_file.unlink()
ディレクトリの作成
単一のディレクトリを作成する場合、os.mkdirを使用します。
mkdir test_dir
↓
import os
os.mkdir("test_dir")
pathlib.Pathの場合はメソッドmkdirを使用します。
from pathlib import Path
Path("test_dir").mkdir()
パーミッションをデフォルト値(777 & umask値)より制限したい場合は、modeを使用します。
mkdir -m 700 secret_dir
↓
import os
os.mkdir("secret_dir", mode=0o700)
なお、この関数でディレクトリを作成しようとしたもののすでに存在した場合には、例外FileExitsErrorが投げられます。また、この引数はpathlib.Path.mkdirメソッドでも使用できます。
ディレクトリを再帰的に作成したい場合はos.makedirsを使用します。ディレクトリがすでに存在しても構わない場合には、exist_okオプションを使用します。また、os.mkdirと同様modeオプションを使用することもできます。
mkdir -p /usr/local/my_software
↓
import os
os.makedirs("/usr/local/my_software", exist_ok=True)
なお、exist_ok=Trueを指定しない場合にディレクトリがすでに存在した場合には、例外OSErrorが投げられます。pathlibの場合はmkdirメソッドのparentsオプションを使用します。
from pathlib import Path
Path("/usr/local/my_software").mkdir(parents=True, exist_ok=True)
ディレクトリ内部の探索
find . -type d -exec chmod 700 {} \;
find . -type f -exec chmod 600 {} \;
↓
import os
for root, dirs, files in os.walk("."):
for dir in dirs:
os.chmod(0o700, os.path.join(root,dir))
for file in files:
os.chmod(0o600, os.path.join(root, file))
引数にPathクラスのインスタンスを渡す場合、Python3.6以降ならそのまま渡して構いません。3.5の場合はstrを利用して一旦文字列化します。
パーミッション・所有権・UID・GID
パーミッションの変更
os.chmodを使用します。
chmod 700 directory
↓
import os
os.chmod("directory", 0o700)
Pathクラスにも同名のメソッドがあります。
from pathlib import Path
Path("directory").chmod(0o700)
また、実行ビットを付与する場合など、元のパーミッション+αの形式で指定する場合には、os.stat・ビット演算と併用してください。
chmod +x script.sh
↓
import os
os.chmod("script.sh", os.stat("script.sh").st_mode | 0o111)
Pathクラスの場合はos.statの代わりにstatメソッドを使用します。
from pathlib import Path
script_path = Path("script.sh")
script_path.chmod(script_path.stat().st_mode | 0o111)
UID・GIDの取得
現在のユーザのUID・GIDを取得するにはそれぞれos.getuid()・os.getgid()を使用します。
import os, sys
if os.getuid() != 0:
print("このスクリプトはrootで実行してください", file=sys.stderr)
exit(1)
また、指定された名前のユーザのUID・グループのGIDを取得するにはそれぞれpwd.getpwnam・grp.getgrnamを使用します。それぞれの返り値のpw_uid・gr_gid属性がそれぞれUID・GIDとなります。なお、指定された名前のユーザ・グループがない場合には例外KeyErrorが返ってきますので、適宜捕捉してください。
import grp
import pwd
assert pwd.getpwnam("root").pw_uid == 0
assert grp.getgrnam("root").gr_gid == 0
所有権の変更
shutil.chownコマンドを使用します。
chown root:www /var/www/html
↓
import shutil
shutil.chown("/var/www/html", "root", "www")
注: これらの関数はWindowsには実装されていません。また、pwd・grpモジュールもWindowsからは利用できません。
ファイル・ディレクトリのコピー・移動・削除
コピー
ファイルを普通にコピーする場合はshutil.copyを使用します。cpコマンドと同じ感覚で使用します。
import shutil
shutil.copy("old.txt", "new.txt")
shutil.copy("old.txt", "directory")
メタデータもコピーしたい場合はshutil.copy2を使用します。使い方はcopyと同じです。ファイルオブジェクト同士でコピーしたい場合はshutil.copyfileobjを使用します。
import shutil
with open("old.txt", "rb") as old:
with open("new.txt", "wb") as new:
shutil.copyfileobj(old, new)
ディレクトリをコピーしたい場合はshutil.copytreeを使用します。
移動
shutil.moveを使用します。os.renameもありますが、ファイル専用です。
削除
ファイルを削除するにはos.removeを使用します。
rm file
↓
import os
from pathlib import Path
os.remove("file") # or Path("file").unlink()
空のディレクトリを削除する場合には、os.rmdirまたはPathクラスのメソッドunlinkを使用します。空でないディレクトリ全体を再帰的に削除する場合にはshutil.rmtreeを使用します。
rm -rf /
↓
import shutil
shutil.rmtree("/")
シンボリックリンクの作成
os.symlinkまたはPathクラスのsymlink_toメソッドで作成できます。
ln -s foo bar
↓
import os
os.symlink("foo", "bar") # or Path("bar").symlink_to("foo")
ファイルのダウンロード
requestsパッケージを使用するといいでしょう。
ファイルの内容を取得して後に直接Pythonで解析する場合は次のように書きます。
import requests
req = requests.get("http://server/page.html")
html = req.text
大きなファイルをダウンロードしたい場合は次のように書くとよいでしょう。
import requests
import posixpath
import os
import shutil
req = requests.get("http://server/largefile.deb", stream=True)
with open(os.path.join("~/ダウンロード",posixpath.basename(req.url)), "wb") as f:
shutil.copyfileobj(req.raw, f)
万が一同パッケージが使えない場合には、urllib.requestモジュール(https://docs.python.jp/3/library/urllib.request.html)で代用します。
testコマンドの代替
パスの存在チェック
[[ -e "file.txt" ]] && echo "Exist" || echo "Not exist"
↓
import os
print("Exist" if os.path.exists("file.txt") else "Not exist")
from pathlib import Path
print("Exist" if Path("file.txt").exists() else "Not exist")
ファイル/ディレクトリのチェック
[[ -f "file.txt" ]] && echo "File" || echo "Not file"
[[ -d "directory" ]] && echo "Directory" || echo "Not directory"
↓
import os
print("File" if os.path.isfile("file.txt") else "Not file")
print("Directory" if os.path.isdir("directory") else "Not directory")
from pathlib import Path
print("File" if Path("file.txt").is_file() else "Not file")
print("Directory" if Path("directory").is_dir() else "Not directory")
権限のチェック
現在のユーザに対する権限をチェックする場合にはos.accessを利用します。
[[ -r 'file.txt' ]] && echo 'True' || echo 'False'
[[ -r 'file.txt' -a -w 'file.txt' ]] && echo 'True' || echo 'False'
[[ -r 'directory' -a -x 'directory' ]] && echo 'True' || echo 'False'
↓
import os
print(os.access("file.txt", os.R_OK))
print(os.access("file2.txt", os.R_OK | os.W_OK))
print(os.access("directory", os.R_OK | os.X_OK))
更新日のチェック
更新日のタイムスタンプはos.statまたはPathクラスのメソッドstatで取得できる名前付きタプルのst_mtime属性で取得できます。
ファイルサイズのチェック
ファイルサイズは同じくos.statまたはPathクラスのメソッドstatの返り値のst_size属性で取得できます。ディレクトリの場合、中に入っているファイルの総サイズという訳ではないので注意してください。
アーカイブの圧縮・解凍
shutilの利用
解凍にはshutil.unpack_archiveを利用します。各種tarball・zipならばこれが一番楽です。
tar xf foo.tar.xz
↓
import shutil
shutil.unpack_archive("foo.tar.xz")
なお、この関数の第2引数で解凍先を指定できます。
tar xf foo.tar.gz -C bar
↓
import shutil
shutil.unpack_archive("foo.tar.gz", "bar")
あるディレクトリを丸ごと圧縮したい場合にはshutil.make_archiveを使用します。例えばbarという名前のディレクトリを圧縮してfoo.tar.xzという名前のアーカイブを作りたい場合は次のように書きます。
tar cf foo.tar.xz bar
↓
import shutil
shutil.make_archive("foo", "xztar", base_dir="bar")
gz・bz2・xz
これらのファイルはそれぞれgzip・bz2・lzmaモジュールを利用すると普通のファイルのように読み書きできます。
import gzip
import bz2
import lzma
import shutil
with gzip.open("file.gz", "rb") as gz:
with bz2.open("file.bz2", "wb") as bzip2:
shutil.copyfileobj(gz, bzip2)
with lzma.open("file.xz", "wb") as xz:
shutil.copyfileobj(gz, xz)
tar
tarfileモジュールを使います。解凍はextractall()を使用します。例えば、次のように利用します。
import tarfile
with tarfile.open("tarfile.tar.xz") as f:
f.extractall()
なお、第1引数にディレクトリを指定すると、そこに展開します。tarファイルのファイル一覧は、members()で取得します。(リストが返ってきます)
tarファイルを作成する場合はadd()を使用しますが、open()で指定するファイルモードに注意が必要です。wだけだと非圧縮とみなされます。例えばtar.xz形式のアーカイブを作成したい場合は次のように書きます。
import tarfile
with tarfile.open("archive.tar.xz", "w:xz") as f:
f.add("directory")
このようにwの後に:(圧縮形式名)を追加します。
zip
zipfile.ZipFileを使用します。(http://docs.python.jp/3.6/library/zipfile.html#zipfile-objects)
例えばZIPファイルの中身をカレントディレクトリ内に全て展開したい場合は次のように書きます。
import zipfile
with zipfile.ZipFile("foo.zip") as ar:
ar.extractall()
zipファイルにはファイル名のエンコーディング問題があるので、OSのzip・unzipコマンドをsubprocess.runで呼び出すといいかもしれません。(Windows・Ubuntu日本語Remix)
一時ファイル・ディレクトリ
一時ファイル・ディレクトリはtempfileモジュールを利用します。
一時ファイル
ファイルを外部から使用しない場合はtempfile.TemporaryFileを使用します。
import tempfile
with tempfile.TemporaryFile() as temp_file_handler:
temp_file_handler.write(some_byte_array)
# ...
some_bytes = temp_file_handler.read(length)
# ...
外部から使用する場合はtempfile.NamedTemporaryFileを使用します。name属性があり、これでファイル名を取得できます。
import tempfile
import subprocess
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(some_byte_array)
# ...
subprocess.run(("external-process", temp_file.name), check=True)
# ...
拡張子・プレフィックスを指定したい場合はsuffix・prefixを使用します。例えば、次のコード
import tempfile
with tempfile.NamedTemporaryFile(prefix="tempfile-", suffix=".deb") as temp_file:
print(temp_file.name)
は、/tmp/tempfile-hz1u73cr.debのような文字列を出力して終了します。
なお、どちらもwith文を抜けるとファイルは削除されます。また、ファイルの規定のオープンモードはw+b、すなわちバイナリモードです。
一時ディレクトリ
一時ディレクトリはtempfile.TemporaryDirectoryで作成します。通常は次のようにwith文を使用します。
import tempfile
import os
import subprocess
with tempfile.TemporaryDirectory() as temp_dir_name:
temp_conf_name = os.path.join(temp_dir_name, "hoge.conf")
with open(temp_conf_name, "w") as temp_conf:
print("hoge=hogehoge", file=temp_conf)
subprocess.run(("sudo", "hoge", temp_conf_name), check=True)
この場合、with文を抜けると一時ディレクトリ及びその中のファイルはは削除されます。また、この例の場合temp_dir_nameの型はstrです。
環境変数
os.environという名前の辞書(のようなもの)で取得・設定できます。
import os
print("256色" if "TERM" in os.environ and "256color" in os.environ["TERM"] else "16色")
import os
import pip
if "CUDA_ROOT" not in os.environ:
os.environ["CUDA_ROOT"] = "/usr/local/cuda"
pip.main(["install", "-U", "chainer"])
文字列の中に環境変数を埋め込みたい場合はos.path.expandvarsを利用します。
import os
WORKING_DIR = os.path.join(COMMON_ROOT, os.path.expandvars("work-$USER"))
Pathクラスのメソッドによるパスの文字列操作
パス連結
from pathlib import Path
# 全て Path("/bin/bash")を返す
Path("/bin","bash")
Path("/bin") / "bash"
Path("/bin") / Path("bash")
Path("/bin").joinpath("bash")
Path("/bin").joinpath(Path("bash"))
パス分割
from pathlib import Path
p = Path("dir/dir2/file.tar.gz")
print(p.name) # file.tar.gz
print(p.parent) # dir/dir2
print([str(dir) for dir in p.parents]) # ['dir/dir2', 'dir', '.']
print(p.stem) # file.tar
print(p.suffix) # .gz
print(p.suffixes) # ['.tar', '.gz']
親ディレクトリはそのままで名前・拡張子の変更
from pathlib import Path
ico = Path("img/favicon.ico")
gitignore = ico.with_name(".gitignore") # img/.gitignore
png = ico.with_suffix(".png") # img/favicon.png
文字列操作
パス操作 (非推奨; Pathクラスの使用を推奨)
各種区切り文字はos.sep(ディレクトリ)・os.extsep(拡張子)・os.pathsep(環境変数PATH)で取得できます。
import os
print(os.sep, os.extsep, os.pathsep)
# Windows: \ . ;
# それ以外: / . :
パスの連結にはos.path.joinを使用します。
import os
os.makedirs(os.path.join(PROJECT_ROOT, "build"), exist_ok=True)
パスをbasename・dirnameに分割するにはos.path.split・os.path.basename・os.path.dirnameを使用します。
import os
dirname, basename = os.path.split(FILE_PATH)
assert dirname == os.path.dirname(FILE_PATH)
assert basename == os.path.basename(FILE_PATH)
パスの拡張子・拡張子以外の部分を取り出す場合はos.path.splitextを使います。
import os
root, ext = os.path.splitext("/etc/nginx/nginx.conf")
print(root, ext)
# /etc/nginx/nginx .conf
ホームディレクトリを表す~を利用したパスを取り扱うにはos.path.expanduserを使用します。
echo "Your home directory is: $HOME"
↓
import os
print("Your home directory is:", os.path.expanduser("~"))
Windowsとの互換性を意識する場合にはこのようなホームディレクトリを取得する使い方のみをし、os.path.joinを併用してください。
また、環境変数を含んだパスを扱うにはos.path.expandvarsを使用します。
import os
os.makedirs(os.path.join(PROJECT_ROOT, "build", os.path.expandvars("python-$PYTHON_VERSION")), exist_ok=True)
pathlibを使う方法もありますが、現在のところは解説しません。
https://docs.python.jp/3/library/pathlib.html
その他
他にはsed・awk・grep相当のことを書く予定です
参考