Python more basics - シェル環境の拡充としての Python

[テキストプロセッサとしての Python] [Perl正規表現]
[2014/07/10新規] [2014/07/10更新]

Contents

主な形式

python script_file [file...]

外部コマンドとしての利用の説明

Python はテキストフィルタとしての利用だけでなく、汎用なシステム操作記述言語としての利用に十分な組み込み関数や拡張モジュールが充実しており、シェル環境で足りない機能を外部コマンドとして拡充するだけの能力が備わっている。

Python スクリプトを外部コマンドとして利用する為には、Unix 環境では以下のような行頭から始めればよい。

#!/usr/bin/python
	:

また、コマンドラインオプションを指定するには、Perl, Ruby のように '-s' オプションでコマンドラインオプションによる大域変数の設定などはないので、自前でコマンドラインオプション解析を行うか、'getopt', 'argparse' モジュールを利用するか検討すべきだ。

拡張パッケージの利用

Python には多様な拡張パッケージが備わり、その上、Ruby 以上に数多のサードパーティ製拡張パッケージが用意されている。インストール済みの拡張パッケージを利用するには、以下のようにする。

#!/usr/bin/python
import stat
	:

例題

代表的な Unix コマンドに相当する Python スクリプトを以下にあげる。

`echo`

Python では '-l' オプションで print メソッドで自動的に改行がなされ、$, = ' ' で配列やリストのフィールドセパレータがスペースになる。

#!/usr/bin/python
import sys
print " ".join(sys.argv[1:])

Ruby と比べるとかなり違和感がある。それは、'sys.argv[1:].join(" ")' のようなメソッドが用意されていないからであるが、'import string' した上で 'string.join(sys.argv[1:], " ")' とすれば多少違和感が無いが、これは廃止されるクラスメソッドだ。

このように、echo と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`true`

Python における終了コードは特に明示しない限り、正常終了である。

#!/usr/bin/python

このように、true と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`false`

Python における終了コードは 'sys' モジュールの 'sys.exit' 関数で指定できる。ちなみに、GNU Awk では可能だが、Awk では終了コードを制御できない。

#!/usr/bin/python
import sys
sys.exit(1)

実は、自動的に読み込まれる 'site' モジュールにより、以下のようにほぼ同等なことが出来てしまう。設計思想からして、理解不能な名前空間の扱いである。

#!/usr/bin/python
exit(1)

このように、false と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`date`

#!/usr/bin/python
from datetime import datetime
print datetime.today().strftime("%c")

このように、date と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`dirname`

#!/usr/bin/python
import sys
import os
def dirname(p):
    if not isinstance(p, str): return "."
    if not len(p): return "."
    if p == os.sep: return os.sep
    if p == "." or p == "./": return "."
    if p == ".." or p == "../": return "."
    b = p.rstrip(os.sep)
    i = b.rfind(os.sep)
    if i < 0:
        return "."
    else:
        b = b[0:i]
    return b
argv = sys.argv[1:]
argv or sys.exit("usage: dirname path")
print dirname(argv[0])	# Do not use os.path.dirname().

Python 標準の 'os.path.dirname' は、残念ながら POSIX 規格に沿った仕様を有しない。よって、以上のように代替品を自作する必要がある。Python は各所 DOS の匂いがする。

このように、dirname と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`basename`

#!/usr/bin/python
import sys
import os
def basename(p, x = ""):
    if not isinstance(p, str): return "."
    if not len(p): return ""
    if p == os.sep: return os.sep
    b = p.rstrip(os.sep)
    i = b.rfind(os.sep)
    if not i < 0:
        b = b[i+1:]
    if x and b[-len(x):] == x:
        b = b[0:-len(x)]
    return b
argv = sys.argv[1:]
argv or sys.exit("usage: basename string [suffix]\n       basename [-a] [-s suffix] string [...]")
if len(argv) > 1:
    print basename(argv[0], argv[1])	# Do not use os.path.basename().
else:
    print basename(argv[0])	# Do not use os.path.basename().

Python 標準の 'os.path.basename' は、残念ながら POSIX 規格に沿った仕様を有しない。よって、以上のように代替品を自作する必要がある。Python は各所 DOS の匂いがする。

このように、basename と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`logname`

#!/usr/bin/python
import os
import pwd
name = pwd.getpwuid(os.getuid()).pw_name
#name = os.getlogin()
print name

このように、logname と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`id`

#!/usr/bin/python
import sys, getopt
import os
import pwd, grp
import re

opts, argv = getopt.getopt(sys.argv[1:], "hGgunr", [ "help", ])

g = { 'G': False, 'g': False, 'u': False, 'n': False, 'r': False,
      'name': None, 'id': None, 'gname': None, 'gid': None, }
for o, a in opts:
    if o in ('-h', '--help'):
        sys.exit(0)
    elif o == '-G': g['G'] = True
    elif o == '-g': g['g'] = True
    elif o == '-u': g['u'] = True
    elif o == '-n': g['n'] = True
    elif o == '-r': g['r'] = True

gids, gnames = [], {}
if not argv:
    g['id'] = os.geteuid()
    gids.append(os.getegid())
    for group in os.getgroups():
        gids.append(group)
    if g['G']: gids.pop(0)
else:
    if not re.match(r"^\d+$", argv[0]):
        g['name'], g['id'] = pwd.getpwnam(argv[0]).pw_name, pwd.getpwnam(argv[0]).pw_uid
        if not g['name']: sys.exit(1)
    else:
        g['id'] = int(argv[0])
    g['name'], g['gid'] = pwd.getpwuid(g['id']).pw_name, pwd.getpwuid(g['id']).pw_gid
    if not g['name']: sys.exit(1)
    gids.append(g['gid'])
    for grent in grp.getgrall():
        gname, gid, members = grent.gr_name, grent.gr_gid, grent.gr_mem
        members = filter(lambda id: id == g['id'], map(lambda member: pwd.getpwnam(member).pw_uid, members))
        if members: gids.append(gid)
if g['G']:
    if not g['n']:
        if gids: sys.stdout.write(' '.join(map(lambda gid: str(gid), gids)))
    else:
        if gids: sys.stdout.write(' '.join(map(lambda gid: grp.getgrgid(gid).gr_name, gids)))
elif g['g']:
    if not g['n']:
        sys.stdout.write(str(gids[0]))
    else:
        g['gname'] = grp.getgrgid(gids[0]).gr_name
        if not g['gname']: sys.exit(1)
        sys.stdout.write(g['gname'])
elif g['u']:
    if not g['n']:
        sys.stdout.write(str(g['id']))
    else:
        g['name'] = pwd.getpwuid(g['id']).pw_name
        if not g['name']: sys.exit(1)
        sys.stdout.write(g['name'])
else:
    g['name'], g['gid'] = pwd.getpwuid(g['id']).pw_name, pwd.getpwuid(g['id']).pw_gid
    if not g['name']: sys.exit(1)
    g['gname'] = grp.getgrgid(g['gid']).gr_name
    if not g['gname']: sys.exit(1)
    sys.stdout.write("uid=%u(%s) gid=%u(%s) " % (g['id'], g['name'], g['gid'], g['gname']))
    sys.stdout.write("groups=")
    for i in range(len(gids)):
        if not gids[i] in gnames:
            gname = grp.getgrgid(gids[i]).gr_name
            if i != 0: sys.stdout.write(",")
            sys.stdout.write("%u(%s)" % (gids[i], gname))
            gnames[gids[i]] = gname
sys.stdout.write("\n")

このように、id と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`touch [-c] [-a] [-m] [-r pathname|-t [[CC]YY]MMDDhhmm[.SS]]`

#!/usr/bin/python
import sys, getopt
import stat
import time
import os, os.path
import re

opts, argv = getopt.getopt(sys.argv[1:], "hamcr:t:", [ "help", ])

g = { 'a': False, 'm': False, 'c': False, 'r': None, 't': None, }
for o, a in opts:
    if o in ('-h', '--help'):
        sys.exit(0)
    elif o == '-a': g['a'] = True
    elif o == '-m': g['m'] = True
    elif o == '-c': g['c'] = True
    elif o == '-r': g['r'] = a
    elif o == '-t': g['t'] = a

ntime = time.time()
atime, mtime = None, None
if g['r']:
    status = os.stat(g['r'])
else:
    if g['t']:
        m = re.match(r"(?:((?:\d{2})?\d{2}))?(\d{2})(\d{2})(\d{2})(\d{2})(?:\.(\d{2}))?$", g['t'])
        if g['t']:
            CCYY, MM, DD, hh, mm, SS = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6))
            if CCYY:
                if CCYY <= 99:
                    CCYY += 1900 if (69 <= CCYY) else 2000
            else:
                CCYY = time.localtime(ntime).tm_sec + 1900
            if not SS: SS = 0
            ntime = time.mktime((CCYY, MM, DD, hh, mm, SS, 0, 0, -1))
if g['a'] and g['m'] or not g['a'] and not g['m']:
    atime = status.st_atime if g['r'] else ntime
    mtime = status.st_mtime if g['r'] else ntime
elif g['a']:
    atime = status.st_atime if g['r'] else ntime
elif g['m']:
    mtime = status.st_mtime if g['r'] else ntime
for a in argv:
    if not g['c'] and not os.path.exists(a):
        f = open(a, 'w')
        f.close()
        if not atime: atime = mtime
        if not mtime: mtime = atime
    if not atime: atime = ntime
    if not mtime: mtime = ntime
    os.utime(a, (atime, mtime))

このように、touch と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`du [-H|-L] [-a|-s] [-d depth] [-k|-m|-g|-h] [file ...]`

`du` コマンドを実現するには 'opendir', 'readdir', 'closedir' 及び 'stat', 'lstat' を使えれば十分だ。Python にはそれに対応する 'os.listdir', 'os.stat', 'os.lstat' がある。

#!/usr/bin/python
import sys
import stat
import os, os.path
import math
units = {
    'K':           1024, # KibiBytes
    'M':      1024*1024, # MebiBytes
    'G': 1024*1024*1024, # GibiBytes
}
g = { 'H': False, 'L': False, 'a': False, 's': False, 'd': 0, 'h': False, 'S': 1, }
argv = sys.argv[1:]
while len(argv):
    if argv[0] == '--': argv.pop(0); break
    elif argv[0] == '-H': g['H'] = True
    elif argv[0] == '-L': g['L'] = True
    elif argv[0] == '-a': g['a'] = True
    elif argv[0] == '-s': g['s'] = True
    elif argv[0] == '-d' and 1 < len(argv): argv.pop(0); g['d'] = int(argv[0])
    elif argv[0] == '-k': g['S'] = 2
    elif argv[0] == '-m': g['S'] = 2*1024
    elif argv[0] == '-g': g['S'] = 2*1024*1024
    elif argv[0] == '-h': g['h'] = True
    else: break
    argv.pop(0)
def resize(size):
    if not g['h']:
        return str(long(math.ceil(size/g['S'])))
    else:
        if not size < units['G']/512:
            p = 'G'
        elif not size < units['M']/512:
            p = 'M'
        else:
            p = 'K'
        s = size/(units[p]/512)
        if s < 1: p = 'B'
        f = "%3.0f"
        if not s < 1 and s < 10: f = "%3.1f"
        return f % (s) + p
total_sizes = [ 0.0 ]
def do_one(one):
    global total_sizes
    size = 0
    if os.path.isdir(one) and (g['L'] or (not g['L'] and not os.path.islink(one))):
        if not (os.stat(one).st_mode & stat.S_IRUSR and os.stat(one).st_mode & stat.S_IXUSR): return
        total_sizes.append(0.0)
        for entry in os.listdir(one):
            do_one("%s/%s" % (one, entry))
        size = total_sizes.pop()
        if (not g['s'] or (g['s'] and not len(total_sizes)-1)) and (not g['d'] or not g['d'] < len(total_sizes)-1):
            print resize(size) + "\t" + one
    else:
        status = os.stat(one) if g['L'] and os.path.exists(one) else os.lstat(one)
        if not status: return
        size = status.st_blocks
        if g['a']: print resize(size) + "\t" + one
    total_sizes[len(total_sizes)-1] += size
if not argv:
    do_one('.')
    sys.exit(0)
while len(argv):
    do_one(argv[0])
    argv.pop(0)

このように、du と同じ python スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

参考文献

  1. Python 言語リファレンス
  2. Python 標準ライブラリ
Written by Taiji Yamada <taiji@aihara.co.jp>