|
#! /usr/bin/env python3 |
|
|
|
import os |
|
import sys |
|
|
|
# 複数のファイルを読み込んで重複する行を集計する |
|
def crosscheck(files, common_part_size=0): |
|
contents={} |
|
|
|
for fidx, file in enumerate(files, start=1): |
|
with open(file, 'r') as fh: |
|
for line in fh: |
|
text = line.strip() |
|
if text == '': |
|
continue |
|
common_part = get_file_common_part(text, common_part_size) |
|
if common_part in contents: |
|
fmap = contents[common_part] |
|
else: |
|
fmap = {} |
|
contents[common_part] = fmap |
|
if (fidx, text) in fmap: |
|
fmap[(fidx, text)] += 1 |
|
else: |
|
fmap[(fidx, text)] = 1 |
|
return contents |
|
|
|
# ファイル名の配列と共通部分のサイズを指定して重複するファイル名を表示する |
|
def crosscheck_print(contents, selector, select=0, verbose=False, eol=os.linesep): |
|
for common_part, fmap in contents.items(): |
|
# fmapとfiles_lenが一致しているか |
|
fidxs = set(sorted([fidx for (fidx, text), count in fmap.items() if count > 0])) |
|
if verbose: |
|
print("<%s> : %s" % (common_part, fidxs), file=sys.stderr) |
|
|
|
if selector(fidxs): |
|
if select == 0: |
|
print(common_part, end=eol) |
|
else: |
|
texts = [text for (fidx, text), count in fmap.items() if fidx == select] |
|
if texts: |
|
print(texts[0], end=eol) |
|
|
|
# ファイルをトークンに分解し指定した末尾数で共通部分を取得する |
|
def get_file_common_part(file, common_part_size): |
|
tokens=[] |
|
while True: |
|
file, token = os.path.split(file) |
|
if token != '': |
|
tokens.insert(0, token) |
|
else: |
|
break |
|
return os.path.sep.join(tokens[-common_part_size:]) |
|
|
|
# Main |
|
if __name__ == "__main__": |
|
import argparse |
|
# 引数の解析 |
|
parser = argparse.ArgumentParser(description=''' |
|
[Crosscheck クロスチェック. |
|
引数に指定したファイルを読み込み、重複する行を集計します. |
|
重複する行がある場合は、その行を出力します. |
|
複数ファイルを指定した場合は、すべてのファイルに存在する行のみを出力します. |
|
オプションで逆に、すべてのファイルに存在しない行を出力することもできます. |
|
比較する行は、デフォルトでは行一致ですが、パスとみなして末尾から指定した数のトークン数で比較できます. |
|
''') |
|
# パスを短縮する数値指定オプション |
|
parser.add_argument('-t', '--token', type=int, default=0, |
|
help='パスの末尾からのトークン数、1はファイル名のみ、2は親ディレクトリ+ファイル、0はすべて') |
|
parser.add_argument('-s', '--select', type=int, default=0, |
|
help='出力するファイル名のソース元、0は共通、1以降はファイル引数の順序') |
|
parser.add_argument('-m', '--match-mode', type=str, help='一致モード(match, unmatch, single, all, any)') |
|
parser.add_argument('-v', '--verbose', action='store_true', help='詳細') |
|
parser.add_argument('-0', '--print0', action='store_true', help='改行コードをnulにする') |
|
parser.add_argument('files', nargs='*', help='ファイル名') |
|
args = parser.parse_args() |
|
|
|
files_len = len(args.files) |
|
|
|
match_mode = args.match_mode if args.match_mode else 'match' |
|
|
|
if match_mode == 'match' or match_mode == 'all': |
|
selector = lambda fidxs: len(fidxs) == files_len |
|
elif match_mode == 'unmatch' or match_mode == 'any': |
|
selector = lambda fidxs: len(fidxs) != files_len |
|
elif match_mode == 'single': |
|
selector = lambda fidxs: len(fidxs) == 1 |
|
else: |
|
# それ以外はエラーとする |
|
print("Error: unknown match mode '%s'" % match_mode, file=sys.stderr) |
|
sys.exit(1) |
|
|
|
eol = os.linesep if not args.print0 else '\0' |
|
|
|
contents = crosscheck(args.files, args.token) |
|
crosscheck_print(contents, selector, select=args.select, verbose=args.verbose, eol=eol) |