'^$'`'^$'`'^.+'`'^.+'`':' -f 1,6`':' -f 1,6 -s`'A-Za-z' 'N-ZA-Mn-za-m'`
python -c 'script' [file...]
python script_file [file...]
Python は sed, Awk, Perl からの影響を受けていないので、Ruby のようにそれらの流れを汲むシンタックスシュガーは持ち合わせていない。
よって、コマンドライン引数をファイル指定と看做したり、コマンドライン引数が無ければ標準入力から行を簡便に読み込むなどのオプションは存在しない。そこで、敢えてそうした処理をする場合には如何にコードを書くべきか、簡単にまとめておく。
#!/usr/bin/python
import fileinput
for line in fileinput.input():
line = line.rstrip("\r\n")
:
fileinput ライブラリはファイル名のリスト、規定値は sys.argv[1:] を一行ずつ読み込む。ファイル名に '-' が指定された場合やリストが空の場合は標準入力から一行ずつ読み込む。
#!/usr/bin/python
import fileinput
for line in fileinput.input():
line = line.rstrip("\r\n")
:
print line
Python はパターンスペースを自動的に出力するようなことはしない為、明示的に出力する必要がある。また、改行を切り取るようなオプションもないので、明示的に rstrip("\r\n") する必要がある。
#!/usr/bin/python
import fileinput
for line in fileinput.input():
:
print line,
Python にはパターンスペースを自動的に出力するようなことはしない為、明示的に出力する必要がある。また、'print' 文は ',' で終っていれば、改行を出力しない。
':'`, `perl -F':' -na`, `ruby -F ':' -na`
#!/usr/bin/python
import fileinput
for line in fileinput.input():
F = line.split(":")
:
Python は Awk のようにフィールドセパレータでレコードとなる行を切り分けるようなことはしないので、明示的に split(FS) する必要がある。
Python には Awk, Perl, Ruby のように 'BEGIN', 'END' のような特殊ブロックなどのようなものは存在しない。しかし、これは上述の Perl, Ruby における '-n', '-p' オプションが存在しないので不要である。
一方、sed, Awk のマッチの範囲「式, 式」に処理されるブロック, Perl, Ruby のような範囲演算子 '..', '...' もサポートされない。よって、明示的にそれらに同等なコードを書く必要がある。
例えば以下は、Awk では awk となる、HTML の '/^<pre>/,/<\/pre>$/''pre' タグを含むそれに囲まれた行を表示する Python スクリプトである。
python -c ' import fileinput import re flag = False for line in fileinput.input(): if not flag: m = re.match(r"^<pre>", line) if m: flag = True; print line, else: print line, m = re.match(r"^</pre>$", line) if m: flag = False '
このように Python スクリプトは、sed, Awk 風な省略した Perl, Ruby コードのようなものは書けないようになっている。さらには、複文はセミコロン ';' で一行で書くことは出来るが、制御構文には改行とインデントとその深さが必須となっている。よって、Python では同じ目的なら誰が書いても似たようなコードになるという根拠にもなっている。
例えば、sed では sed -e となる、ソースコードを HTML にペーストできるように「<」から「<」への変換等を行なう Python スクリプトは以下のように書かなければならない。
's/&/\&/g;s/</\</g;s/>/\>/g'
python -c ' import fileinput import re for line in fileinput.input(): line = re.sub(r"&", "&", line) line = re.sub(r"<", "<", line) line = re.sub(r">", ">", line) print line, '
先の perl -p, ruby -p に対応する python スクリプトは、以下とほぼ等価なスクリプトとなる。
python -c '
import sys
argv = sys.argv[1:]
if not argv: argv.insert(0, "-")
for filename in argv:
argf = stdin if filename == "-" else open(filename, "r")
for line in argf:
print line,
'
ここで for line in ファイルオブジェクト は一行をあたかもリストの一要素かのように次々と辿るイテレータによるループとなる。しかし、残念ながらこのループ内で argf.tell() による「現在のファイル読み込みの位置」を取得しても意図した動作にならない。これは、実は効率化の為に既に多くを読み込んでしまっているからである。ちなみに、先の fileinput ライブラリによる方法でも argf.tell() は実現不可能なようである。
これはさらに以下とほぼ等価なスクリプトとなる。
python -c '
import sys
argv = sys.argv[1:]
for filename in argv:
argf = stdin if filename == "-" else open(filename, "r")
line = argf.readline()
while line:
print line,
line = argf.readline()
'
これならこの while ループ内で argf.tell() による「現在のファイル読み込みの位置」を取得しても意図した動作になる。
ところで、Python ではこの while ループは以下のようには書けない。
while line = argf.readline():
print line,
代入文は Python では「式」ではないので値を返さないからである。あまりに杓子定規だが、初心者にはわかりやすいのだろう。
例えば先の、ソースコードを HTML にペーストできるように「<」から「<」への変換等を行なう Python スクリプトは、なるべく少ない行数で書けば、推奨はされないが以下のようになる。
python -c ' import fileinput; import re; for line in fileinput.input(): line = re.sub(r"&", "&", line); line = re.sub(r"<", "<", line); line = re.sub(r">", ">", line); print line, '
このように、'for' のような制御構文の前には必ず改行が必要となるが、他の文はセミコロン ';' で継続してもよい。
例えば先の、HTML の 'pre' タグを含むそれに囲まれた行を表示する Python スクリプトは、なるべく少ない行数で書けば、推奨はされないが以下のようになる。
python -c ' import fileinput; import re; import sys; flag = False for line in fileinput.input(): flag, rv = ((True, sys.stdout.write(line)) if re.match(r"^<pre>", line) else (False, None)) if not flag else (False if re.match(r"^</pre>", line) else True, sys.stdout.write(line)) '
ここで、'print' ではなく 'sys.write' を使っているのは、'print' は文なので右辺値にはなれないからである。そして、'sys.write' は返り値はないが関数なので、変数 'rv' に代入している。あとは三項演算子 'c ? a : b' に対応する Python の 'a if c else b' 演算子の組み合わせの代入式を 'for' ループで回している。
また、Python では未代入の変数の使用は禁止されており、Perl の 'undef'、Ruby の 'nil' のように Python の 'None' が変数の値になっているわけではない。よって、'flag = False' は必須である。
スクリプトは、さまざまなリテラルからなる式および、それらからなる文(単純文、複合文)からなる。複合文は後に続く文のブロック構造を知らしめる為に、必ず先立って改行が必要となる。ブロック構造は空白によるインデントレベルによって表される。
以下の識別子、リテラル、式がある。
_*, __*__, __* の特別な識別子'', "", r'\d', r"\d" などexpr, …, ()[expr, …]{expr: expr, …}{expr, …}`expr, …`さて、Python には Perl における「リファレンス」などは無い、が、オブジェクトのインスタンス変数へのアクセスによって、それが不要となっている。
詳しくは、Python 言語リファレンス「リテラル」, 「式」を参照のこと。
Python における「偽」は、'None', 'False', 数値 '0', 空のコンテナ(文字列、タプル、リスト、辞書、集合) である。他に、'__len__(self)', '__nonzero__(self)' を持つオブジェクトで偽をかえすものもある。他のオブジェクトはすべて「真」となる。Perl のように文字列 "0" が偽にはならないので安心である。
Python には言語仕様としては変数展開はなく、文字列型の '%' 演算子により、C の sprintf や C++ の boost::format に似た操作を行う。
#!/usr/bin/python
a = 'foo'
b = "bar"
print "%s: %s" % (a, b) #=> "foo: bar\n"
ここで、第二項はタプルだが要素は式である。よって、以下のような式展開もできてしまう。
#!/usr/bin/python
a = [ 0, 1, 2, 3, ]
print '%s' % (",".join(map(str, a))) #=> "0,1,2,3\n"
詳しくは、Python 言語リファレンス「文字列フォーマット操作」を参照のこと。
以下の演算子式がある。
() [] {} : `` | 式結合またはタプル、リスト、辞書、文字列変換 |
[] () . | 配列または辞書添字またはスライス、関数呼び出し、属性参照 |
** | 二項累乗 |
+ - ~ | 単項正、単項負、ビット否定 |
* / // % | 二項乗、除、除(切り捨て)、法 |
+ - | 二項加、減 |
<< >> | ビット左、右シフト |
& | ビットAND |
^ | ビットXOR |
| | ビットOR |
> >= < <= <> == != in, not in, is, is not | 関係等号不等号、メンバシップ検査 |
not | 論理否定 |
and or | 論理AND、論理OR |
if else | 三項条件 |
lambda | ラムダ式 |
特に、Python の演算子は Perl よりもむしろ C++ に近く、文字列の連結は Perl のように '.' ではなく '+' 二項加算演算子である。
しかし、残念ながら代入は Python では式ではないので、これが言語に慣れているものにとっては非常に面倒になる。さらに、多くのプログラミング言語にある ++, -- の前置・後置ともに、Python には存在しない。これは設計思想によるもので、慣れれば Python では不要であることがわかってくる。とは言え、すべての代入や、++, -- の前置・後置は「返値」のあるメソッドによるオブジェクトの破壊的操作と捉えれば導入可能に思えるのだが… すると +=, -= に加えて =+, =- のような新たな演算子も導入可能であろう。
一方、優れているのは関係等号不等号と論理ANDの組み合わせで、よくある 'x < y and y < z' を 'x < y < z' と直感的に書けることである。これは他の言語でも採用すべきだ。
また、優先順位の高い '&&', '||', '!' の論理演算が敢えて存在しないのは、これもまた設計思想なのだろう。
詳しくは、Python 言語リファレンス「式」を参照のこと。
以下の代入文がある。文なので、値を返さない。
= += -= *= /= //= %= **= &= |= ^= <<= >>= | 代入、累算代入 |
Python には C/C++ などにはない「タプル代入」なる式がある。Perl のリスト代入 '($a, $b, $c) = (1, 2, 3)' ともまた異なり、Python では 'a, b, c = 1, 2, 3' というスタイルとなる。
詳しくは、Python 言語リファレンス「代入文」を参照のこと。
以下の複合文がある。
if … : … [elif … : …] [else : …]while … : … [else : …]for … in … : … [else : …]try … : … [except [… [as|, …]] : …] [else : …] [finally : …]try … : … finally : …with … [as …] [, … [as …]] : …def …(…): …class …[(…)]: …ブロック構造を表す改行とインデントレベルは省略できないので注意。
特に、'for (i=0; i<10; i++)' のような繰り返し構文がないので、代わりに 'for i in range(10): …' のようにリスト生成による繰り返し文で実現する。しかしこれだと、'for (i=10; i>0; i--)' のような降順の繰り返しができないので、降順の場合は 'for i in range(10, 0, -1):' のようにリスト生成による繰り返し文で実現する。
詳しくは、Python 言語リファレンス「複合文」を参照のこと。
式文と代入文の他に、以下の単純文がある。
assertpass … 何もしない文。構文として文は必須なので C/C++ の「;」のように使う。delprintreturn … 値を返して抜ける。yieldraisebreak … 繰り返しを終了continue … 繰り返しを継続import … モジュールのロードglobal … グローバル変数の宣言exec詳しくは、Python 言語リファレンス「単純文」を参照のこと。
Python の正規表現は Perl とほとんど同じだ。
但し、Perl における '%-' に対応する機能や POSIX 文字クラスがサポートされていないようだ。
Python における定数と特殊変数は、それぞれのライブラリ内で定義されており、以下の組み込み定数、および、対話モードで最後に表示された結果である '_' 以外のグローバルスコープ変数は存在しないようだ。
False … bool 型の「偽」True … bool 型の「真」None … types.NoneType 型のただ一つの値で、値がないことを表すのに使用される。NotImplemented … リッチ比較メソッドで返される未実装を表すのに使用される。Ellipsis__debug__
よく使う 'sys', 'os', 'fileinput' ライブラリの定数、変数、メソッドを以下に示す。それぞれ 'import' 文を要する。
sys.argv[0] … 実行中のスクリプト名sys.argv … 実行中のスクリプト名とコマンドライン引数の配列 sys.argv[0], sys.argv[1] … sys.argv[len(sys.argv)-1]sys.__stdin__ … 標準入力sys.__stdout__ … 標準出力sys.__stderr__ … 標準エラー出力sys.stdin … sys.__stdin__ が初期値sys.stdout … sys.__stdout__ が初期値sys.stderr … sys.__stderr__ が初期値os.environ … 環境変数の辞書。os.environ[名前]fileinput.lineno() … 入力のレコード数fileinput.filename() … 現在読み込み中のファイル名
すべての変数は何もしなければローカルスコープとなる。しかし、'import' したモジュール名と同一の変数名は実質使えないので、'sys', 'os' などの変数名は避ける必要がある。
Python におけるエスケープ文字(バックスラッシュ記法と正規表現におけるメタ文字)は以下の通りである。
\^J - 無視\\ - バックスラッシュ\' - シングルクォート\" - ダブルクォート\a - "^G,BEL,アラート(alert)"\b - "^H,BS,バックスペース(backspace)"、但し、[] 内のみ。\t - "^I,HT,水平タブ(horizontal tab)"\n - "^J,LF,改行(new-line)"\f - "^L,FF,改頁(form feed)"\r - "^M,CR,行頭復帰(carriage return)"\v - "^K,VT,垂直タブ(vertical tab)"、但し、[] 内のみ。\OOO - 8進数の文字コード\xHH - 16進数の文字コード\uHHHH - Unicode 文字(16-bit)\UHHHHHHHH - Unicode 文字(32-bit)\w - 英数字とアンダースコア「_」(以降、正規表現に関して)\W - 上記以外\s - 空白\S - 上記以外\d - 数字\D - 上記以外\X - Unicode「拡張書記素クラスタ」。[] 内では不可。 (1.9以降)\K - 直前を保持、$& に含めない。[] 内では不可。 (1.9以降)\b - ワード境界。但し、[] 内では上述。\B - 上記以外。[] 内では不可。\A - 文字列の先頭。[] 内では不可。\Z - 文字列の末尾。[] 内では不可。\1 - 後方参照。1 には正の整数。[] 内では不可。\g<1> - 後方参照。1 には正の整数。[] 内では不可。\g<name> - 名前後方参照。[] 内では不可。このように Python 1.5 以降では非常に多くのエスケープ文字がサポートされる。
Python には組み込み型、組み込み関数、組み込みオブジェクト、組み込みモジュールにより、標準ライブラリがサポートされる。
ここで主な組み込み型、標準ライブラリのリファレンスを列挙しておく。
next などindex, count などadd, remove, discard, pop, clear, issubset, issuperset などpop, clear, items, keys, values, iteritems, iterkeys, itervalues などclose, fileno, flush, isatty, next, read, readline, seek, tell, truncate, write などlen, cmp, filter, map, reduce など、下記参照sin, cos, exp, log などmatch, search, split, findall, finditer, sub, subn および「マッチオブジェクト」などnext, read, seek, tell などリンク先のメソッドや定数をよく参考にすること。
組み込み関数は以下の通りである。
len(obj) -> int
cmp(o, p) -> int
chr(i) -> str
unichr(i) -> str
bin(i) -> str
hex(i) -> str
str([obj]) -> str
repr(obj) -> str
ord(c) -> int
bool([o=False]) -> bool
int([v=0[, base=10]]) -> int
long([v=0[, base=10]]) -> long
float([v=0]) -> float
complex([rv=0[, iv=0]]) -> complex
abs(v) -> numbers
oct(i) -> str
pow(xv, yv[, zv]) -> xv**yv|xv**yv%zv
round(v[, i=0]) -> float
divmod(v, w) -> (v//w, v%w)
print(obj[,...][,sep=''][,end=''][,file=sys.stdout]) # print文とは別なので注意!
unicode([obj[, encoding[, errors]]]) -> unicode
all(it) -> bool
any(it) -> bool
max(it[…]) -> obj
min(it[…]) -> obj
next(it[, def]) -> obj
sum(it[, start=0]) -> obj
open(name[, mode[, bufsize]]) -> file
format(v[, fs]) -> fmt
iter(o[, sentinel]) -> iterator
list([it]) -> list, []
set([it]) -> set, {}
frozenset([it]) -> frozenset
dict([arg]) -> dictionary, { …: …, ... }
memoryview(obj) -> memoryview
tuple([it]) -> tuple, ()
bytearray([so[, encoding[, errors]]]) -> sequence
xrange([startv, ]stopv[, stepv]) -> xrange
slice([startv, ]stopv[, stepv]) -> slice
range([startv, ]stopv[, stepv]) -> []
enumerate(s[, i]) -> ((i, s[i]), ...)
filter(f, it) -> [it[], ...]
map(f, it, ...) -> []
reduce(f, it[, init]) -> []
reversed(it) -> iterator
sorted(it[, cmpf[, keyf[, reverse]]) -> []
zip([it, ...]) -> [(), ...]
file
basestring
classmethod
staticmethod
property(getter[, setter[, deleter[, doc]]]) -> property
callable(obj) -> bool
isinstance(obj, classinfo) -> bool # classinfo: class | (class1, ...)
issubclass(obj, classinfo) -> bool
hasattr(obj, name) -> bool
getattr(obj, name[, def]) -> obj|def
setattr(obj, name, v)
delattr(obj, name)
dir([obj]) -> [ name, ... ]
hash(obj) -> int
id(obj) -> int
object() -> obj
super([type, [obj|type]])
type(obj) -> type
type(name, bases, dict) -> type
globals() -> {}
locals() -> {}
vars([obj]) -> {}|property
__import__(name[, globals[, locals[, fromlist[, level]]]])) -> obj
reload(module) -> obj
compile(so, name, mode[, flags[, dont_inherit]]) -> obj
eval(expr[, globals[, locals]]) -> obj
execfile(name[, globals[, locals]]) -> obj
help([obj])
input([prompt]) -> obj
raw_input([prompt]) -> str
代表的な Unix コマンドに相当する Python スクリプトを以下にあげる。
python -c ' import fileinput for line in fileinput.input(): print line, '
このように、cat と同じ python スクリプトは以上のようになる。
python -c ' import fileinput for line in fileinput.input(): if (fileinput.lineno() == 1): print line, '
python -c ' import fileinput for line in fileinput.input(): if (fileinput.lineno() == 1): print line,; break '
このように、head -n 1 と同じ python スクリプトは以上のようになるが、後者の方が効率がよいだろう。
python -c ' import fileinput for line in fileinput.input(): b = line print b, '
このように、tail -n 1 と同じ python スクリプト以上のようになる。
python -c ' import fileinput for line in fileinput.input(): if (not (fileinput.lineno() > 8)): print line, '
python -c ' import fileinput for line in fileinput.input(): if (not (fileinput.lineno() > 8)): print line, else: break '
このように、head -n 8 と同じ python スクリプトは以上のようになるが、後者の方が効率がよいだろう。
さて、`head -n 1`, `tail -n 1`, `head -n n` は以上のように大変簡単であるが、`tail -n n` は少し工夫が必要であり、以下のようになる。
python -c ' import fileinput n = 8 a = [""]*n for line in fileinput.input(): a[fileinput.lineno() % n] = line for i in range(0, n): print a[(fileinput.lineno()+i+1) % n], '
ここでは、配列にラウンドロビン的に行を格納していき、最後にそれらを出力している。
このように、tail -n 8 と同じ python スクリプトは以上のようになる。
python -c ' import fileinput for line in fileinput.input(): pass print fileinput.lineno() '
このように、wc -l と同じ python スクリプトは以上のようになる。
sed では大変面倒になる `wc -c` は python だと算術演算があるので簡単である。
python -c ' import fileinput l = 0 for line in fileinput.input(): l += len(line) print l '
ここで、'l = 0' は Python だと変数の型を決定する為に必要になる。
このように、wc -c と同じ python スクリプトは以上のようになる。
sed では大変面倒になる `wc -w` は python だと算術演算があるので簡単であるが、少々工夫が必要であり、以下のようになる。
python -c ' import fileinput import re w = 0 def match_count(m): global w w += 1 return m.group(0) for line in fileinput.input(): re.sub(r"[^\t\n ]+", match_count, line) print w '
ここで、're.sub' に関数を渡してマッチ全体をそのまま返しつつ、'global' 宣言した変数 'w' でマッチ回数をカウントしている。しかし、're.subn' を使えば、置換結果とマッチ回数をタプルで返してくれるので、より簡単に書けて以下のようになる。
python -c ' import fileinput import re w = 0 for line in fileinput.input(): w += re.subn(r"[^\t\n ]+", r"\g<0>", line)[1] print w '
このように、wc -w と同じ python スクリプトは以上のようになる。
'^$'`python -c ' import fileinput import re for line in fileinput.input(): if re.search(r"^$", line): print line, '
このように、基本正規表現の grep '^$' と同じ python スクリプトは以上のようになるが、Python では基本正規表現はサポートされないので、他のパターンではPython正規表現に書き直す必要がある。
'^$'`python -c ' import fileinput import re for line in fileinput.input(): if not re.search(r"^$", line): print line, '
このように、マッチの否定、基本正規表現の grep -v '^$' と同じ python スクリプトは以上のようになるが、Python では基本正規表現はサポートされないので、他のパターンではPython正規表現に書き直す必要がある。
'^.+'`python -c ' import fileinput import re for line in fileinput.input(): if re.search(r"^.+", line): print line, '
このように、拡張正規表現の grep -E '^.+' と同じ python スクリプトは以上のようになる。
'^.+'`python -c ' import fileinput import re for line in fileinput.input(): if not re.search(r"^.+", line): print line, '
このように、拡張正規表現のマッチの否定、grep -E -v '^.+' と同じ python スクリプトは以上のようになる。
':' -f 1,6`
python -c '
import fileinput
for line in fileinput.input():
F = line.split(":")
if len(F) >= 6:
print "%s:%s" % (F[0], F[5])
else:
print line,
'
このように、区切り ':' のフィールド切り取り、cut -d ':' -f 1,6 但しフィールド数不足の行はそのまま出力と同じ python スクリプトは以上のようになる。
':' -f 1,6 -s`
python -c '
import fileinput
for line in fileinput.input():
F = line.split(":")
if len(F) >= 6: print "%s:%s" % (F[0], F[5])
'
このように、区切り ':' のフィールド切り取り、cut -d ':' -f 1,6 但しフィールド数不足の行は出力しないと同じ python スクリプトは以上のようになる。
この例、1行あたりの文字数(既定値は80)を越えたら改行を挿入する python スクリプトを示そう。まずは正規表現を使わない方法:
python -c '
import fileinput
w = 80
for line in fileinput.input():
h = line.rstrip("\r\n")
while True:
if len(h) > w:
print h[0:w]
h = h[w:]
else:
print h
break
'
もしくは、正規表現を使った方法:
python -c '
import fileinput
import re
w = 80
for line in fileinput.input():
h = line.rstrip("\r\n")
while not re.search(r"^.{%d}$" % w, h):
m = re.search(r"^.{%d}" % w, h)
if not m: break
print m.group(0)
h = h[m.end(0):]
print h
'
ここで、二つの正規表現は同一ではないので注意。Python では 'while' の条件式に代入が行えないので、このように Perl や Ruby などに比べて冗長になってしまう。
このように、fold -b と同じ python スクリプトは以上のようになる。
python -c '
import fileinput
f = open("filename.out", "w")
for line in fileinput.input():
print line,
f.write(line)
f.close()
'
このように、標準入力を標準出力とファイルに書き出し、tee と同じ python スクリプトは以上のようになる。
'A-Za-z' 'N-ZA-Mn-za-m'`
この例、ROT13(と呼ばれる暗号化と言うより難読化)は tr コマンドを使うと表題のように簡単に実現できる。そして、python には tr コマンドに似た 'translate' メソッドがあるのだが、2つの引数の長さが等しくないといけない仕様なので、以下のように冗長になる。
python -c '
import fileinput
import string
for line in fileinput.input():
print line.translate(string.maketrans("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm")),
'
このように、文字置換、tr 'A-Za-z' 'N-ZA-Mn-za-m' と同じ python スクリプトは以上のようになる。
sed ではいささか面倒になる `cat -n` は python だと極めて簡単である。
python -c ' import fileinput for line in fileinput.input(): print "%6d\t%s" % (fileinput.lineno(), line), '
このように、cat -n と同じ python スクリプトは以上のようになる。
sed では大変面倒になる `cat -n` は python だと極めて簡単である。しかし、変数の型を決める為の初期化を要するので Perl よりも繁雑になる。
python -c ' import fileinput import re i = 0 for line in fileinput.input(): if not re.search(r"^$", line): i += 1 print "%6d\t%s" % (i, line), else: print line, '
このように、cat -b と同じ python スクリプトは以上のようになる。
ところで、これらは GNU sed や GNU Awk で示される好例となっている。
sed ではいずれも 'N' コマンドで入力行をパターンスペースの末尾に '\n' に続いて追加し、正規表現にて '^\(.*\)\n\1$' のように後方参照 '\1' を利用していることにある。awk においては、基本的には以下に示す例と同等である。
これらを python で実現すると、単純に文字列の比較と制御の組み合わせとなり、順に以下のようになる。
python -c ' import fileinput for line in fileinput.input(): if fileinput.lineno() == 1: h = line print line, continue if h != line: h = line print line, '
python -c ' import fileinput d = False for line in fileinput.input(): if fileinput.lineno() == 1: h = line continue if h != line: h = line d = False else: if not d: print line, d = True '
python -c ' import fileinput d = False for line in fileinput.input(): if fileinput.lineno() == 1: h = line continue if h != line: if not d: print h, h = line d = False else: d = True if not d: print h, '
このように、uniq, uniq -d, uniq -u と同じ python スクリプトは以上のようになる。
このタブを複数の空白に置換するコマンドを python スクリプトで示そう。この例では expand.py という実行権のついたファイルに記述するものとする。まずは正規表現を使った方法:
#!/usr/bin/python
import fileinput
import re
import sys
n = 8
for line in fileinput.input():
h = line.rstrip("\r\n")
m = re.match(r"^([^\t]{0,%d}\t|[^\t]{%d})" % (n-1, n), h)
while m:
u = m.group(0)
h = h[m.end(0):]
p = u.find("\t")
if (p < 0): p = len(u)
u = u[0:p]
for i in range(n-p): u += " "
sys.stdout.write(u)
m = re.match(r"^([^\t]{0,%d}\t|[^\t]{%d})" % (n-1, n), h)
print h
やはり、Python では 'while' の条件式に代入が行えないので、このように Perl や Ruby などに比べて冗長になってしまう。
もしくは、正規表現を使わない方法:
#!/usr/bin/python
import fileinput
import sys
n = 8
for line in fileinput.input():
line = line.rstrip("\r\n")
l = 0
for i in range(len(line)):
c = line[i:i+1]
d = n - (l % n) if (c == "\t") else 1
if c == "\t":
c = ""
for j in range(d): c += " "
sys.stdout.write(c)
l += d
print
両者の効率と柔軟性におけるメリット・デメリットを考えてみるのも興味深い。個人的には、前者の方が判り易くてよいと思うが、例えば、'\b' が一文字戻るとみなす処理を加えるには後者の方が簡単である。
このように、expand と同じ python スクリプトは以上のようになる。
この複数の空白をタブに置換するコマンドを python スクリプトを示そう。この例では unexpand-a.py という実行権のついたファイルに記述するものとする。まずは正規表現を使った方法:
#!/usr/bin/python
import fileinput
import re
import sys
n = 8
for line in fileinput.input():
h = line.rstrip("\r\n")
m = re.match(r"^([^\t]{0,%d}\t|[^\t]{%d})" % (n-1, n), h)
while m:
u = m.group(0)
h = h[m.end(0):]
if re.search(r"^ ", h):
m = re.search(r" {1,}$", u)
if m: u = u[0:m.start(0)] + "\t"
else:
m = re.search(r"^([^\t]{%d})\t$" % (n-1), u)
if m: u = m.group(1) + " "
m = re.search(r" {2,}$", u)
if m: u = u[0:m.start(0)] + "\t"
sys.stdout.write(u)
m = re.match(r"^([^\t]{0,%d}\t|[^\t]{%d})" % (n-1, n), h)
print h
ちなみに、最初の 'if' 文は、完全に `unexpand -a` の挙動を再現するためのものである。やはり、Python では 'while' の条件式に代入が行えないので、このように Perl や Ruby などに比べて冗長になってしまう。しかも、ここでは後述の 're.finditer' は使えないので注意。
もしくは、一文字ずつ調べる方法:
#!/usr/bin/python
import fileinput
import re
import sys
n = 8
for line in fileinput.input():
line = line.rstrip("\r\n")
buf = ""
l = 0
for i in range(len(line)):
c = line[i:i+1]
d = n - (l % n) if (c == "\t") else 1
l += d
buf += c
if l % n == 0:
if (line[i+1:i+2] == " "):
m = re.search(r" +$", buf)
if m: buf = buf[0:m.start(0)] + "\t"
else:
m = re.search(r"^([^\t]{%d})\t$" % (n-1), buf)
if m: buf = m.group(1) + " "
m = re.search(r" +$", buf)
if m: buf = buf[0:m.start(0)] + "\t"
sys.stdout.write(buf)
buf = ""
print buf
両者の効率と柔軟性におけるメリット・デメリットを考えてみるのも興味深い。個人的には、前者の方が判り易くてよいと思うが、例えば、'\b' が一文字戻るとみなす処理を加えるには後者の方が簡単である。
このように、unexpand -a と同じ python スクリプトは以上のようになる。
行毎に文字列を反転する BSD rev(1) コマンド。実用したことはないが、sed で実現するには秀逸な技法が必要であった。しかし、python では極めて単純に、文字列を逆順に取り出して出力すればよい。この例では rev.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import fileinput
import sys
for line in fileinput.input():
line = line.rstrip("\r\n")
for i in range(len(line)-1, -1, -1):
sys.stdout.write(line[i:i+1])
print
このように、BSD rev(1) と同じ python スクリプトは以上のようになる。
最終行から先頭行まで逆順で出力する GNU tac コマンド、`tail -r` に同じ。これも実用したことはないが、GNU sed における、好ましくない実現例のように、python における好ましくない実装は以下の通りである。この例では tac.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import fileinput
import sys
b = ''
for line in fileinput.input():
b = line + b
sys.stdout.write(b)
変数に行を逆順に連結して最後にそれを出力するという処理なので、メモリが足りなくなるか、仮想メモリで非常に遅くなるだろう。しかし、Python には 'tell', 'seek' とそれらのアクセサ 'pos' がある。より安全な実装は以下のようになる。
#!/usr/bin/python
import sys
f = open(sys.argv[1])
p = []
p.append(f.tell())
line = f.readline()
while line:
p.append(f.tell())
line = f.readline()
p.pop
for i in range(len(p) - 1, -1, -1):
f.seek(p[i])
sys.stdout.write(f.readline())
ここではファイルを入力しながらすべての改行の次のファイルハンドルの位置を配列に覚えておき、改めてその配列の逆順にファイルハンドルを移動する処理を行なっている。ファイル全体をオンメモリで処理しないので、先の例よりは安全であるが、改行が膨大に存在する長大なファイルの場合、配列のためのメモリ不足になるかもしれない。よって、以下のような安全な実装も考えられる。
#!/usr/bin/python
import sys
import os
f = open(sys.argv[1])
f.seek(0, os.SEEK_END)
p = f.tell() - 2
while p >= 0:
f.seek(p)
p -= 1
if not p < 0: c = f.read(1)
if p < 0 or c == "\n":
sys.stdout.write(f.readline())
ここでは始めにファイルハンドルの位置をファイルのの末尾に移動し、1バイト読み込みつつ、そのファイルハンドルの位置を1バイトずつ戻す処理を行なっている。ファイル全体をオンメモリで処理しないので、先の例よりは安全である。
この例、1行あたりの制御コードを考慮した文字数(既定値は80)を越えたら改行を挿入する python スクリプトを示そう。これはカウントが必要になるので sed では困難な好例となっている。この例では fold.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import sys
import fileinput
n = 8
w = 80
argv = sys.argv[1:]
while argv:
if argv[0] == "--": argv.pop(0); break
elif argv[0] == "-n" and 1 < len(argv): argv.pop(0); n = int(argv[0])
elif argv[0] == "-w" and 1 < len(argv): argv.pop(0); w = int(argv[0])
else: break
argv.pop(0)
for line in fileinput.input(argv):
line = line.rstrip("\r\n")
l = 0
for i in range(len(line)):
c = line[i:i+1]
d = (-1 if (l > 0) else 0) if (c == "\b") else -l if (c == "\r") else n - (l % n) if (c == "\t") else 1
if l+d > w:
print
l = d
else:
l += d
sys.stdout.write(c)
print
ここで、'sys.argv' のスライスを 'argv' にコピーして、自前でコマンドラインオプションを解析しているが、これは Python では Perl や Ruby のように '-s' オプションでコマンドラインオプションによる大域変数の設定などはないからである。とは言え、本質的ではないので行数を少なめに示していることに留意。その場合、'fileinput.input' の引数はコマンドラインオプションが消費された 'argv' となる。
このように、fold と同じ python スクリプトは以上のようになる。
この例、1行あたりの制御コードを考慮した文字数(既定値は80)を越えようとするブランクに変わり改行を挿入する python スクリプトを示そう。これもカウントが必要になるので sed では困難な好例となっているだけでなく、python でもかなり複雑にならざるを得ない。この例では fold-s.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import sys
import fileinput
import re
n = 8
w = 80
def increment(l, c):
return (-1 if (l > 0) else 0) if (c == "\b") else -l if (c == "\r") else n - (l % n) if (c == "\t") else 1
argv = sys.argv[1:]
while argv:
if argv[0] == "--": argv.pop(0); break
elif argv[0] == "-n" and 1 < len(argv): argv.pop(0); n = int(argv[0])
elif argv[0] == "-w" and 1 < len(argv): argv.pop(0); w = int(argv[0])
else: break
argv.pop(0)
for line in fileinput.input(argv):
line = line.rstrip("\r\n")
buf = ""
l = siz = 0
if line == "":
print ""
continue
for i in range(len(line)):
c = line[i:i+1]
if l + increment(l, c) > w:
j = siz - 1
while j >= 0 and not re.search(r"[\t-\r ]", buf[j:j+1]):
j -= 1
space = j
if space != -1:
space += 1
print "%.*s" % (space, buf)
buf = buf[space:siz]
siz -= space
l = 0
for j in range(siz): l += increment(l, buf[j:j+1])
else:
print "%.*s" % (siz, buf)
l = siz = 0
l += increment(l, c)
buf = buf[0:siz] + c
siz += 1
if siz != 0:
print "%.*s" % (siz, buf)
ここでユーザ定義関数を効果的に使用していることに注目したい。しかし、もっと簡単になるような気もする…
このように、fold -s と同じ python スクリプトは以上のようになる。
この例、ファイルの連続した印字可能な4文字以上の文字列を表示する python スクリプトを示す。この例では strings.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import sys
import fileinput
import re
n = 4
t = None
argv = sys.argv[1:]
while argv:
if argv[0] == "--": argv.pop(0); break
elif argv[0] == "-n" and 1 < len(argv): argv.pop(0); n = int(argv[0])
elif argv[0] == "-t" and 1 < len(argv): argv.pop(0); t = argv[0]
else: break
argv.pop(0)
if t: fmt = "%x %s" if (t == "x") else "%o %s" if (t == "o") else "%d %s"
for line in fileinput.input(argv):
if fileinput.isfirstline(): p = 0
p += len(line)
line = line.rstrip("\r\n")
for m in re.finditer(r"([\f !-\/0-9:-\@A-Z[-`a-z{-~]{%d,})" % (n), line):
if not t:
print m.group(1)
else:
print fmt % ((p - (len(line) + 1) + m.start(1)), m.group(0))
この strings.py では、'-n 4', '-t d|o|x' オプションが指定できるように自前でしている。ちなみに、Python では 'getopt', 'argparse' のようなコマンドラインオプション解析ライブラリがサポートされるので、それらの利用も検討しよう。
このように、strings -a と同じ python スクリプトは以上のようになる。
さて、ここまでは入力ファイル駆動型のプログラムばかりであったが、そうではなく自律型のプログラムでは 'fileinput' は不要となる。
この例、環境変数をすべて表示する python スクリプトを示す。この例では printenv.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python
import os
for k, v in os.environ.iteritems():
print "%s=%s" % (k, v)
辞書(ハッシュ)のキー値をすべて取り出すには、このように 'iteritems' メソッドを使う。
無論、以下のようにキー値をすべて取り出すやり方でも構わない。
python -c ' import os for k in iter(os.environ): print "%s=%s" % (k, os.environ[k]) '
このように、printenv と同じ python スクリプトは以上のようになる。
この例、プロセス停止まで永遠に "yes^J" を印字し続ける python スクリプトを示す。但しこのコマンド、第一引数を指定した場合には "yes" の代わりにそれを印字する仕様となっている。この例では yes.py という実行権のついたファイルに記述するものとする。
#!/usr/bin/python import sys y = sys.argv[1] if sys.argv[1:] else "yes" while True: print y
このように、yes と同じ python スクリプトは以上のようになる。
この例、二つのファイルのバイトの差異を返す python スクリプトを示す。バイナリファイルとしての扱いが可能な Python での好例となる。
#!/usr/bin/python
import sys
s, l = False, False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-s': s = True
elif argv[0] == '-l': l = True
else: break
argv.pop(0)
blksize = 4096
bc, lc, rv = 0, 0, 0
if argv[0] == argv[1]: sys.exit(0)
f = (open(argv[0], "rb"), open(argv[1], "rb"))
while True:
b = (f[0].read(blksize), f[1].read(blksize))
sz = len(b[0]) if (len(b[0]) < len(b[1])) else len(b[1])
if not l:
for i in range(sz):
if b[0][i:i+1] == "\n": lc += 1
if b[0][i] != b[1][i]:
if not s: print argv[0] + " " + argv[1] + " differ: char " + (bc + i + 1) + ", line " + (lc + 1)
rv = 1
break
bc += sz
else:
for i in range(sz):
if b[0][i] != b[1][i]:
print "%4d %3o %3o" % (bc + i + 1, b[0][i], b[1][i])
rv = 1
bc += sz
if not (len(b[0]) == blksize and len(b[1]) == blksize):
sys.stderr.write("cmp: EOF on " + (argv[0] if (len(b[0]) < len(b[1])) else argv[1]) + "\n")
rv = 1
if not (len(b[0]) == blksize and len(b[1]) == blksize): break
f[0].close()
f[1].close()
sys.exit(rv)
このように cmp と同じ python スクリプトは以上のようになる。
この例、複数のファイルの内容を行毎に結合する python スクリプトを示す。但しこのコマンド、'-s' オプションを指定した場合はファイル毎に改行を除去して結合する仕様となっており、特に難しくはない。この例では paste.py という実行権のついたファイルに記述するものとする。
Python ではファイル操作関数が揃っているので、以下のように sed では困難な、複数のファイルの平行な入力に対応できる。
#!/usr/bin/python
import sys
import fileinput
def getlinepos(f, p):
if p < 0: return None, -1
fh = open(f, "r")
fh.seek(0, 2)
e = fh.tell()
fh.seek(p, 0)
b = fh.readline()
p = fh.tell()
p = p if p != e else -1
fh.close()
return b, p
s = False
d = "\t"
argv = sys.argv[1:]
while argv:
if argv[0] == "--": argv.pop(0); break
elif argv[0] == "-s": s = True
elif argv[0] == "-d" and 1 < len(argv): argv.pop(0); d = argv[0]
else: break
argv.pop(0)
if not s:
po = [ 0 ]*len(argv)
c = len(argv)
while c:
l = ""
for a in range(len(argv)):
if a != 0: l += "%s" % (d)
if po[a] < 0: continue
b, po[a] = getlinepos(argv[a], po[a])
b = b.rstrip("\r\n")
l += "%s" % (b)
if po[a] < 0: c -= 1
print l
else:
previous_nr = 0
for line in fileinput.input(argv):
if fileinput.isfirstline():
previous_nr = fileinput.lineno() - 1
fnr = fileinput.lineno() - previous_nr
line = line.rstrip("\r\n")
if fnr == 1 and previous_nr > 0:
sys.stdout.write("%s" % ("\n"))
elif fnr != 1:
sys.stdout.write("%s" % (d))
sys.stdout.write("%s" % (line))
if fileinput.lineno() > 0:
sys.stdout.write("%s" % ("\n"))
このように、paste と同じ python スクリプトは以上のようになる。
この例、複数のファイルの内容を行毎に比較する python スクリプトを示す。この例では comm.py という実行権のついたファイルに記述するものとする。
入力ファイルは、行でソートされていることが前提となっているので、複数のファイルをキーの文字列の大小で並行して読み進めていけば良い。
#!/usr/bin/python
import sys
def getlinepos(f, p):
if p < 0: return None, -1
fh = open(f, "r")
fh.seek(0, 2)
e = fh.tell()
fh.seek(p, 0)
b = fh.readline()
p = fh.tell()
p = p if p != e else -1
fh.close()
return b, p
s1 = False
s2 = False
s3 = False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-1': s1 = True
elif argv[0] == '-2': s2 = True
elif argv[0] == '-3': s3 = True
else: break
argv.pop(0)
cf, s, sc, po = [ -1 ]*len(argv), [ False ]*(len(argv)+1), [ 0 ]*(len(argv)+1), [ 0 ]*len(argv)
s[0] = s1
s[1] = s2
s[2] = s3
for a in range(len(argv)+1):
for j in range(a+1):
if s[j]: sc[a] += 1
km = None
c = len(argv)
last_a = -1
ceq = 0
while c:
for a in range(len(argv)):
cf[a] = -1
while not po[a] < 0 and cf[a] == -1:
b, po[a] = getlinepos(argv[a], po[a])
b = b.rstrip("\r\n")
if not km:
km = b
cf[a] = None
last_a = a
ceq = 0
else:
if km < b:
if ceq + 1 != len(argv):
if not s[last_a]: print "%s%s" % ("\t"*(last_a-sc[last_a]), km)
km = b
cf[a] = 1
last_a = a
ceq = 0
elif km == b:
if ceq + 1 != len(argv):
if not s[len(argv)]: print "%s%s" % ("\t"*(len(argv)-sc[len(argv)]), b)
ceq += 1
else:
ceq = 0
cf[a] = 0
last_a = a
else:
if not s[a]: print "%s%s" % ("\t"*(a-sc[a]), b)
cf[a] = -1
ceq = 0
if po[a] < 0:
c -= 1
if c == 0 and km:
if ceq + 1 != len(argv):
if not s[last_a]: print "%s%s" % ("\t"*(last_a-sc[last_a]), km)
continue
このように、comm と同じ python スクリプトは以上のようになる。
この例、複数のファイルの内容を行のキー毎に結合する python スクリプトを示す。この例では join.py という実行権のついたファイルに記述するものとする。
入力ファイルは、キーとなるフィールドでソートされていることが前提となっているので、複数のファイルをキーの文字列の大小で並行して読み進めていけば良い。とは言え、以下のように多少繁雑になるだろう。
#!/usr/bin/python
import sys
def getlinepos(f, p):
if p < 0: return None, -1
fh = open(f, "r")
fh.seek(0, 2)
e = fh.tell()
fh.seek(p, 0)
b = fh.readline()
p = fh.tell()
p = p if p != e else -1
fh.close()
return b, p
def printout():
global argv
global t, va, na, km
n, m, o = 0, 0, None
for i in range(len(argv)):
if not va[i]: continue
n = i + 1
m += 1
if not o:
o = va[i]
else:
o += t + va[i]
va[i] = None
if o and (m == len(argv) or n in na): print km + t + o
na = []
nv = []
t = ' '
n1 = 1
n2 = 1
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-a' and 1 < len(argv): argv.pop(0); na.append(int(argv[0]))
elif argv[0] == '-v' and 1 < len(argv): argv.pop(0); nv.append(int(argv[0]))
elif argv[0] == '-t' and 1 < len(argv): argv.pop(0); t = argv[0]
elif argv[0] == '-1' and 1 < len(argv): argv.pop(0); n1 = int(argv[0])
elif argv[0] == '-2' and 1 < len(argv): argv.pop(0); n2 = int(argv[0])
else: break
argv.pop(0)
cf, kn, va, po = [ -1 ]*len(argv), [ 1 ]*len(argv), [ None ]*len(argv), [ 0 ]*len(argv)
kn[0] = n1
kn[1] = n2
km = None
c = len(argv)
last_a = -1
while c:
for a in range(len(argv)):
cf[a] = -1
while not po[a] < 0 and cf[a] == -1:
b, po[a] = getlinepos(argv[a], po[a])
b = b.rstrip("\r\n")
f = b.split(t)
k = f[kn[a]-1]
v = None
for i in range(len(f)):
if i+1 == kn[a]: continue
if not v:
v = f[i]
else:
v += t + f[i]
if not km:
km = k
va[a] = v
cf[a] = 0
last_a = a
else:
if km < k:
if last_a+1 in nv:
if va[last_a]: print km + t + va[last_a]
va[last_a] = None
if a+1 in nv and last_a == a: print k + t + v
if len(nv) == 0: printout()
km = k
if len(nv) == 0: va[a] = v
cf[a] = 1
last_a = a
elif km == k:
if len(nv) == 0: va[a] = v
cf[a] = 0
last_a = a
else:
if a+1 in na or a+1 in nv: print k + t + v
va[a] = None
cf[a] = -1
if po[a] < 0:
c -= 1
if c == 0: printout()
continue
このように、join と同じ python スクリプトは以上のようになる。但し、正確には単一ファイル内の重複するキーにおける挙動には対応していない。
この例、単一のファイルの内容を行数で複数のファイルに分割する python スクリプトを示す。この例では split.py という実行権のついたファイルに記述するものとする。
Python では、`split` コマンドにおける '-b' オプションによるバイナリファイルとしての分割にも対応可能である。
#!/usr/bin/python
import sys
import re
def outputfilename_digit(nf):
global pre, n, suf
return '%s%0*d%s' % (pre, n, nf, suf)
def outputfilename_lower(nf):
global pre, n, suf
b = d = 26
while int(nf / d) > 0:
d *= b
d /= b
xxxxxx = ''
while True:
r = int(nf / d)
nf -= d * r
xxxxxx = xxxxxx + '%c' % (r + 97)
d = int(d / b)
if not d > 0: break
while len(xxxxxx) < n:
xxxxxx = '%c' % (0 + 97) + xxxxxx
return '%s%s%s' % (pre, xxxxxx, suf)
def outputfilename(nf):
global d
return outputfilename_digit(nf) if d else outputfilename_lower(nf)
units = {
'b': 512, # blocks
'KB': 1000, # KiloBytes
'K': 1024, # KibiBytes
'k': 1024, # KibiBytes
'MB': 1000*1000, # MegaBytes
'M': 1024*1024, # MebiBytes
'm': 1024*1024, # MebiBytes
'GB': 1000*1000*1000, # GigaBytes
'G': 1024*1024*1024, # GibiBytes
'g': 1024*1024*1024, # GibiBytes
}
k = None
s = True
d = None
pre = ''
suf = ''
n = 2
lc = 1000
bc = 0
f = ''
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-k': k = True
elif argv[0] == '--verbose': s = None
elif argv[0] == '-d': d = True
elif argv[0] == '-f' and 1 < len(argv): argv.pop(0); pre = argv[0]
elif argv[0] == '-x' and 1 < len(argv): argv.pop(0); suf = argv[0]
elif argv[0] == '-a' and 1 < len(argv): argv.pop(0); n = int(argv[0])
elif argv[0] == '-l' and 1 < len(argv): argv.pop(0); lc = int(argv[0])
elif argv[0] == '-b':
argv.pop(0)
m = re.match(r"^(\d+)(KB|MB|GB|[KMGbkmg])?$", argv[0])
if m:
bc = int(m.group(0))
if m.group(2): bc *= units[m.group(2)]
else:
if not f:
f = argv[0]
else:
break
argv.pop(0)
if argv: pre = argv[0]
if bc == 0:
l = 0
nr = 0
nf = 0; of = open(outputfilename(nf), 'w')
fh = open(f)
for line in fh:
nr += 1
of.write(line)
if nr % lc == 0:
if not s: sys.stdout.write(l)
l = 0
of.close(); nf += 1; of = open(outputfilename(nf), 'w')
l += len(line)
if not s: sys.stdout.write(l)
else:
buf = ' ' * bc
nf = 0
fh = open(f, "rb")
of = None
buf = fh.read(bc)
while buf:
if of: of.close()
of = open(outputfilename(nf), 'wb'); nf += 1
of.write(buf)
buf = fh.read(bc)
このように、split と同じ python スクリプトは以上のようになる。
この例、単一のファイルの内容を行のパターンや行番号で複数のファイルに分割する python スクリプトを示す。この例では csplit.py という実行権のついたファイルに記述するものとする。
Python では、'seek' 等のファイル操作関数が揃っているので、なんら問題はない。
#!/usr/bin/python
import sys
import re
def outputfilename(nf):
global pre, n, suf
return '%s%0*d%s' % (pre, n, nf, suf)
def nextsplit():
global argv
global p, op
global previous_stl, stl, ope, rep, ln
global reg, ofs
global np, c, oc
previous_stl = stl
ope, rep, stl, ln = argv.pop(0) if argv else None, 0, False, 0
if ope:
m = re.match(r"^\{(\d+)\}$", argv[0])
if m:
rep = int(m.group(1))
argv.pop(0)
else:
ope = 0
return
m = re.match(r"^[/%](.*)[/%]([-+]?\d+)?$", ope)
if m:
reg, ofs = m.group(1), int(m.group(2)) if m.group(2) else 0
np, c, oc = -ofs+1, 0, 0
p = [ 0 ]*(np+1)
op = [ 0 ]*(np+1)
stl = True if re.match(r"^%(.*)%([-+]?\d+)?$", ope) else False
ope = 1
else:
m = re.match(r"^(\d+)$", ope)
if m:
ln = int(m.group(1))
ope = 2
k = None
s = None
d = None
pre = 'xx'
suf = ''
n = 2
f = '-'
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-k': k = True
elif argv[0] == '-s': s = True
elif argv[0] == '-d': d = True
elif argv[0] == '-f' and 1 < len(sys.argv): argv.pop(0); pre = argv[0]
elif argv[0] == '-x' and 1 < len(sys.argv): argv.pop(0); suf = argv[0]
elif argv[0] == '-n' and 1 < len(sys.argv): argv.pop(0); n = int(argv[0])
else: f = argv[0]; argv.pop(0); break
argv.pop(0)
stl = False
ofs = 0
c, oc = 0, 0
ol = 0
fh = open(f, "r")
nf = 0
of = open(outputfilename(nf), 'w'); nf += 1
nextsplit()
if ope == 1 and ofs < 0:
p[c % (np+1)] = fh.tell()
c += 1
if ope == 1 and ofs < 0 and not stl:
op[oc % (np+1)] = of.tell()
oc += 1
lineno = 0
for line in fh:
lineno += 1
if ofs < 0:
p[c % (np+1)] = fh.tell()
c += 1
if ope == 1:
if re.search(reg, line):
if not ofs < 0:
for k in range(ofs):
if not stl:
of.write(line)
ol += len(line)
for line in fh: break
if not line: break
lineno += 1
if not stl:
if ofs < 0:
of.truncate(op[(oc-1-(np-1)) % (np+1)]) # or warn("cannot truncate: #{$!}")
ol -= of.tell() - op[(oc-1-(np-1)) % (np+1)]
if not s: print ol
of.close(); of = open(outputfilename(nf), 'w'); nf += 1
ol = 0
if ofs < 0:
for i in range(np-1):
fh.seek(p[(c-1-(np-i)) % (np+1)], 0) # == 0 or warn("cannot seek")
for line in fh: break
of.write(line)
ol += len(line)
fh.seek(p[(c-1-(np-np)) % (np+1)], 0) # == 0 or warn("cannot seek:")
if not stl:
tf = open(outputfilename(nf-2), 'w+')
tf.truncate(op[(oc-1-(np-1)) % (np+1)]) # or warn("cannot truncate: #{$!}")
tf.close()
if rep == 0:
nextsplit()
else:
rep -= 1
elif ope == 2:
if lineno % ln == 0:
if not s: print ol
of.close(); of = open(outputfilename(nf), 'w'); nf += 1
ol = 0
if rep == 0:
nextsplit()
else:
rep -= 1
if previous_stl or not stl:
of.write(line)
ol += len(line)
if ope == 1 and ofs < 0 and not stl:
op[oc % (np+1)] = of.tell()
oc += 1
if not s: print ol
fh.close()
of.close()
このように、csplit と同じ python スクリプトは以上のようになる。
この例、GNU coreutils seq コマンドのように、単調増加する数列を生成する python スクリプト seq.py を示す。
#!/usr/bin/python
import sys
import re
f = None
s = "\n"
rep, beg, dlt, end = None, 1, 1, None
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-f' and 1 < len(argv): argv.pop(0); f = argv[0]
elif argv[0] == '-s' and 1 < len(argv): argv.pop(0); s = argv[0]
else: break
argv.pop(0)
if len(argv) == 1:
end = float(argv[0])
elif len(argv) == 2:
beg, end = float(argv[0]), float(argv[1])
elif len(argv) == 3:
beg, dlt, end = float(argv[0]), float(argv[1]), float(argv[2])
else:
sys.exit(1)
if not f:
f = "%d"
if (isinstance(beg, str) and re.search(r"\.", beg)) or\
(isinstance(end, str) and re.search(r"\.", end)): f = "%g"
rep = long((end - beg)/dlt)
if rep > 0: sys.stdout.write(f % (beg))
for i in range(1, rep+1):
sys.stdout.write(s + f % (dlt*i + beg))
if rep > 0: sys.stdout.write("\n")
オブジェクトへの検査は Perl, Ruby に比べてシビアだ。よって、'isinstance' が必要で冗長になる。
このように、GNU coreutils seq と同じ python スクリプトは以上のようになる。
この例、BSD jot コマンドのように、単調増減する数列、乱数列、定数列を生成する python スクリプト jot.py を示す。
#!/usr/bin/python
import sys
import re
import random
f = None
s = "\n"
rep, beg, dlt, end = None, 1, 1, None
w, b = None, None
r = False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-w' and 1 < len(argv): argv.pop(0); w = argv[0]
elif argv[0] == '-c': f = "%c"
elif argv[0] == '-b' and 1 < len(argv): argv.pop(0); b = argv[0]
elif argv[0] == '-s' and 1 < len(argv): argv.pop(0); s = argv[0]
elif argv[0] == '-r': r = True; dlt = None
else: break
argv.pop(0)
if len(argv) == 1:
rep = long(argv[0])-1
elif len(argv) == 2:
rep, beg = long(argv[0])-1, argv[1]
elif len(argv) == 3:
rep, beg, end = None if (argv[0] == "-") else long(argv[0])-1,\
None if (argv[1] == "-") else argv[1],\
None if (argv[2] == "-") else argv[2]
elif len(argv) == 4:
rep, beg, end, dlt = None if (argv[0] == "-") else long(argv[0])-1,\
None if (argv[1] == "-") else argv[1],\
None if (argv[2] == "-") else argv[2],\
None if (argv[3] == "-") else float(argv[3])
else:
sys.exit(1)
integer_flag = True
if f == None:
f = "%d"
if (isinstance(beg, str) and re.search(r"\.", beg)) or\
(isinstance(end, str) and re.search(r"\.", end)):
f = "%g"
integer_flag = False
if w != None:
if re.search(r"%", w):
f = w
else:
f = w + f
if beg != None:
if re.search(r"^\D$", str(beg)):
beg = ord(beg[0])
else:
beg = float(beg)
if end != None:
if re.search(r"^\D$", str(end)):
end = ord(end[0])
else:
end = float(end)
if r:
random.seed(dlt)
dlt = None
if rep == -1:
pass
elif rep != None:
if beg == None: beg = end - dlt*rep
if end == None: end = beg + dlt*rep
dlt = (end - beg)/rep
else:
rep = long((end - beg)/dlt)
if b != None:
if rep == -1:
while True:
sys.stdout.write(b + s)
else:
if rep > 0: sys.stdout.write(b)
for i in range(1, rep+1):
sys.stdout.write(s + b)
if rep > 0: sys.stdout.write("\n")
elif r:
dlt = (end - beg)
if rep == -1:
i = 0
while True:
sys.stdout.write(f + s % (random.randint(0, dlt-0) + beg))
else:
if rep > 0: sys.stdout.write(f % (random.randint(0, dlt-0) + beg))
for i in range(1, rep+1):
sys.stdout.write(s + f % (random.randint(0, dlt-0) + beg))
if rep > 0: sys.stdout.write("\n")
else:
if rep == -1:
i = 0
while True:
sys.stdout.write(f + s % (dlt*i + beg))
i += 1
else:
if rep > 0: sys.stdout.write(f % (long(beg) if integer_flag else beg))
for i in range(1, rep+1):
sys.stdout.write(s + f % (long(dlt*i + beg) if integer_flag else dlt*i + beg))
if rep > 0: sys.stdout.write("\n")
オリジナルと微妙に既定の書式の扱いが異なるが、ほぼ等価な実装となっている。
このように、BSD jot と同じ python スクリプトは以上のようになる。
この例、GNU coreutils shuf コマンドはオプションの有無によって以下の機能を持つ。
'-r' のとき無限回数または '-n times' で指定回数で生成。'-r' のとき無限回数または '-n times' で指定回数で生成。'-r' のとき無限回数または '-n times' で指定回数で生成。
ここでは、shuf.py という実行権のついたファイルに記述するものとし、以下のように使用するものとする。
#!/usr/bin/python
import sys
import re
import random
r, n, e, i = False, 0, False, None
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-r': r = True
elif argv[0] == '-n' and 1 < len(argv): argv.pop(0); n = int(argv[0])
elif argv[0] == '-e': e = True
elif argv[0] == '-i' and 1 < len(argv): argv.pop(0); i = argv[0]
else: break
argv.pop(0)
if not r:
if e:
random.seed()
while argv:
sys.stdout.write(argv.pop(random.randint(0, len(argv)-1)) + "\n")
elif i:
lo, hi = 1, 6
m = re.match(r"(\d+)-(\d+)", i)
lo, hi = int(m.group(1)), int(m.group(2))
if lo > hi: sys.exit(1)
a = []
for it in range(lo, hi+1):
a.append(it)
random.seed()
while a:
sys.stdout.write(str(a.pop(random.randint(0, len(a)-1))) + "\n")
else:
if not argv: argv.insert(0, "-")
f = sys.stdin if (argv[0] == "-") else open(argv[0], "r")
a = []
a.append(f.tell())
line = f.readline()
while line:
a.append(f.tell())
line = f.readline()
a.pop()
random.seed()
while a:
f.seek(a.pop(random.randint(0, len(a)-1)), 0)
sys.stdout.write(f.readline())
if f != sys.stdin: f.close()
else:
if e:
random.seed()
if n:
for it in range(1, n+1):
sys.stdout.write(argv[random.randint(0, len(argv)-1)] + "\n")
else:
while True:
sys.stdout.write(argv[random.randint(0, len(argv)-1)] + "\n")
elif i:
lo, hi = 1, 6
m = re.match(r"(\d+)-(\d+)", i)
lo, hi = int(m.group(1)), int(m.group(2))
if lo > hi: sys.exit(1)
a = (hi - lo + 1)
random.seed()
if n:
for it in range(1, int(n)+1):
sys.stdout.write(str(random.randint(0, a-1) + lo) + "\n")
else:
while True:
sys.stdout.write(str(random.randint(0, a-1) + lo) + "\n")
else:
if not argv: argv.insert(0, "-")
f = sys.stdin if (argv[0] == "-") else open(argv[0], "r")
a = []
a.append(f.tell())
line = f.readline()
while line:
a.append(f.tell())
line = f.readline()
a.pop()
random.seed()
if n:
for it in range(1, int(n)+1):
f.seek(a[random.randint(0, len(a)-1)], 0)
sys.stdout.write(f.readline())
else:
while True:
f.seek(a[random.randint(0, len(a)-1)], 0)
sys.stdout.write(f.readline())
if f != sys.stdin: f.close()
このように、shuf と同じ python スクリプトは以上のようになる。
POSIX sort コマンドは主に以下の3つの機能がある。
また、'-u' オプションで重複する行の出力の抑止、'-r' オプションで逆順、'-d|-f|-i' オプションで順に、blank と alnum のみの比較、小文字を大文字と見なして比較、印字可能文字のみの比較でソートする。
さらに、'-k key' オプションでソートのキーフィールド指定、'-t char' オプションでフィールド区切りを指定できる。'-k key', '-t char' オプションの実装は多少複雑になるので、行そのものをキーとするソートの実装を示す。
#!/usr/bin/python
import sys
import re
r = 1
d, f, i, u = False, False, False, False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-r': r = -1
elif argv[0] == '-d': d = True
elif argv[0] == '-f': f = True
elif argv[0] == '-i': i = True
elif argv[0] == '-u': u = True
else: break
argv.pop(0)
def normalize_line(b):
global d, f, i
b = b.rstrip("\r\n")
if d: b = re.sub(r"[^\t 0-9A-Za-z]", '', b)
if f: b = b.upper()
if i: b = re.sub(r"[^ !-/0-9:-@A-Z[-`a-z{-~]", '', b)
return b
def compare_lines(a, b):
global r
p, q = str(a), str(b)
p = normalize_line(p)
q = normalize_line(q)
return cmp(p, q)*r
if not len(argv) > 0: argv.insert(0, "-")
fh = sys.stdin if (argv[0] == "-") else open(argv[0], "r")
lineno = 0
for b0 in fh:
lineno += 1
break
for b in fh:
lineno += 1
if (not u and compare_lines(b0, b) == 1) or (u and compare_lines(b0, b) != -1):
sys.stderr.write("sort: %s:%d: disorder: %s\n" % (argv[0], lineno, b))
sys.exit(1)
b0 = b
fh.close()
sys.exit(0)
行を読み込み、前の行と比較して 1 以外なら正常終了となる。'-u' オプションのときは 0 つまり重複する行も異常終了となる。
#!/usr/bin/python
import sys
import re
def getlinepos(f, p):
if p < 0: return None, -1
fh = open(f, "r")
fh.seek(0, 2)
e = fh.tell()
fh.seek(p, 0)
b = fh.readline()
p = fh.tell()
p = p if p != e else -1
fh.close()
return b, p
ofh = sys.stdout
r = 1
d, f, i, u = False, False, False, False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-o' and 1 < len(argv): argv.pop(0); ofh = open(argv[0], 'w')
elif argv[0] == '-r': r = -1
elif argv[0] == '-d': d = True
elif argv[0] == '-f': f = True
elif argv[0] == '-i': i = True
elif argv[0] == '-u': u = True
else: break
argv.pop(0)
def normalize_line(b):
global d, f, i
b = b.rstrip("\r\n")
if d: b = re.sub(r"[^\t 0-9A-Za-z]", '', b)
if f: b = b.upper()
if i: b = re.sub(r"[^ !-/0-9:-@A-Z[-`a-z{-~]", '', b)
return b
def compare_lines(a, b):
global r
p, q = str(a), str(b)
p = normalize_line(p)
q = normalize_line(q)
return cmp(p, q)*r
def compare_line_positions(a, b):
global argv, p
r, q = getlinepos(argv[a], p[a])
s, q = getlinepos(argv[b], p[b])
return compare_lines(r, s)
a, p = [i for i in range(len(argv))], [0]*len(argv)
b0 = None
while len(a) > 1:
a.sort(cmp=compare_line_positions)
b, p[a[0]] = getlinepos(argv[a[0]], p[a[0]])
if not u or (u and (not b0 or compare_lines(b0, b) != 0)):
b0 = b
ofh.write(b0)
for i in range(len(a)):
if a < len(a) and p[a[i]] == -1:
a.pop(i)
while p[a[0]] != -1:
b, p[a[0]] = getlinepos(argv[a[0]], p[a[0]])
if not u or (u and (not b0 or compare_lines(b0, b) != 0)):
b0 = b
ofh.write(b0)
各ファイルはソート済みであることが前提としてあるので、すべてのファイルを並行して読み、それらの行を並び変えて先頭のファイルの行のみを出力して進めばよい。
#!/usr/bin/python
import sys
import re
ofh = sys.stdout
r = 1
d, f, i, u = False, False, False, False
argv = sys.argv[1:]
while argv:
if argv[0] == '--': argv.pop(0); break
elif argv[0] == '-o' and 1 < len(argv): argv.pop(0); ofh = open(argv[0], 'w')
elif argv[0] == '-r': r = -1
elif argv[0] == '-d': d = True
elif argv[0] == '-f': f = True
elif argv[0] == '-i': i = True
elif argv[0] == '-u': u = True
else: break
argv.pop(0)
def normalize_line(b):
global d, f, i
b = b.rstrip("\r\n")
if d: b = re.sub(r"[^\t 0-9A-Za-z]", '', b)
if f: b = b.upper()
if i: b = re.sub(r"[^ !-/0-9:-@A-Z[-`a-z{-~]", '', b)
return b
def compare_lines(a, b):
global r
p, q = str(a), str(b)
p = normalize_line(p)
q = normalize_line(q)
return cmp(p, q)*r
class File_Position:
def __init__(self, name, position):
self.name = name
self.position = position
file_position_list = []
def file_position_getline(file_position):
fh = open(file_position.name, "r")
fh.seek(file_position.position, 0)
b = fh.readline()
fh.close()
return b
def compare_file_position_getlines(a, b):
p = file_position_getline(a)
q = file_position_getline(b)
return compare_lines(p, q)
for a in range(len(argv)):
fh = open(argv[a], "r")
while True:
file_position_list.append(File_Position(argv[a], fh.tell()))
b = fh.readline()
if not b: break
fh.close()
file_position_list.pop()
file_position_list.sort(compare_file_position_getlines)
b0 = None
for file_position in file_position_list:
b = file_position_getline(file_position)
if not u or (u and (not b0 or compare_lines(b0, b) != 0)):
b0 = b
ofh.write(b0)
各ファイルの行頭のファイルポジションをファイル名とのペアで保持しておき、それを行の比較でソートすればよい。
このように、sort (但し、キーフィールド指定なし) と同じ python スクリプトは以上のようになる。