この記事は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
相当のことを書く予定です
参考