概要
このスクリプトは、Bashスクリプトの中でPythonを実行し、2つのExcelファイルを比較して差分を出力する。具体的には、以下のことを行う:
・第一列(キー列)を基準に、片方のファイルにしかない行をリストアップする。
・キー列が一致する行について、各列の差分をチェックし、違いがある場合にそれを出力する。
スクリプト
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "使用方法: compare_excel <file1.xlsx> <file2.xlsx>"
exit 1
fi
FILE1="$1"
FILE2="$2"
if [ ! -f "$FILE1" ]; then
echo "エラー: ファイル $FILE1 が見つかりません。"
exit 1
fi
if [ ! -f "$FILE2" ]; then
echo "エラー: ファイル $FILE2 が見つかりません。"
exit 1
fi
python3 <<EOF
import pandas as pd
def compare_excel(file1, file2):
try:
print(f"Comparing files: {file1} and {file2}")
# Excelファイルを読み込む
df1 = pd.read_excel(file1, dtype=str)
df2 = pd.read_excel(file2, dtype=str)
print("DF1:", df1.head(), sep="\n")
print("DF1:", df1.tail(), sep="\n")
print("DF2:", df2.head(), sep="\n")
print("DF2:", df2.tail(), sep="\n")
# 1列目(キー列)を動的に検出
key_column = df1.columns[0]
print(f"キー列: {key_column}")
# 列ラベルの一致を確認し、共通列のみを使用
common_columns = df1.columns.intersection(df2.columns)
if not common_columns.any():
print("共通する列がありません。比較できません。")
return
df1 = df1[common_columns]
df2 = df2[common_columns]
# デバッグ: インデックスの確認
print("インデックス確認: df1インデックス:", df1.index)
print("インデックス確認: df2インデックス:", df2.index)
# デバッグ: 比較前のデータを表示
print(f"比較開始: df1のサイズ: {df1.shape}, df2のサイズ: {df2.shape}")
# 追加された行
added_rows = df2[~df2[key_column].isin(df1[key_column])]
print("\n[file2にのみ存在]")
if not added_rows.empty:
print(added_rows)
# 削除された行
removed_rows = df1[~df1[key_column].isin(df2[key_column])]
print("\n[file1にのみ存在)]")
if not removed_rows.empty:
print(removed_rows)
print("\n")
# セルごとの比較(キー列が一致する行のみ)
for col in common_columns:
if col == key_column:
continue # キー列は比較しない
merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
for _, row in diff.iterrows():
print(f"行 '{row[key_column]}', 列 '{col}' の違い: '{row[f'{col}_file1']}' (file1) vs '{row[f'{col}_file2']}' (file2)")
except Exception as e:
print(f"エラーが発生しました: {e}")
if __name__ == "__main__":
# Bashの変数をPythonに渡す
file1 = "${FILE1}"
file2 = "${FILE2}"
compare_excel(file1, file2)
EOF
解説
Excelファイル読み込み
# Excelファイルを読み込む
df1 = pd.read_excel(file1, dtype=str)
pandasのread_execelを用いてファイルを読み込む。dtype=strは全てのデータを文字列として読み込むオプション。
キー列検出
# 1列目(キー列)を動的に検出
key_column = df1.columns[0]
1列目をキー列として指定する。
共通列検出
# 列ラベルの一致を確認し、共通列のみを使用
common_columns = df1.columns.intersection(df2.columns)
if not common_columns.any():
print("共通する列がありません。比較できません。")
return
列ラベル同士を比較して両方のデータフレームに共通する列を見つける。共通列がなければ比較できない旨を表示して終了する。
行差分の検出
added_rows = df2[~df2[key_column].isin(df1[key_column])]
・df2[key_column]: df2データフレームの key_column 列(キー列)の値を取得。
・.isin(df1[key_column]): df2のキー列の各値が df1のキー列に含まれているかどうかをチェック。ブール型を返し、Trueは df1に存在する値、Falseは存在しない値に対応。
・~ (チルダ): 論理否定演算子で、ブール値を反転させる。この場合、df1に存在しない値がTrueとなる。
・df2[...]: ここで、df2の各行について、~df2[key_column].isin(df1[key_column]) の条件を満たす行(Trueになる行)だけを選択して新しいデータフレームを作成する。
つまり、このコードの意味は:
「df2 に含まれるが df1 に含まれていないキー列の行」を added_rows に格納する ということ。これにより、file2 にだけ存在する行(追加された行)を抽出している。
セルの比較
# セルごとの比較(キー列が一致する行のみ)
for col in common_columns:
if col == key_column:
continue # キー列は比較しない
merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
for _, row in diff.iterrows():
print(f"行 '{row[key_column]}', 列 '{col}' の違い: '{row[f'{col}_file1']}' (file1) vs '{row[f'{col}_file2']}' (file2)")
両方のデータフレームに共通する列について比較する。キー列は比較対象外なのでスキップする。
merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
・pd.merge は df1 と df2 の対応する列(key_columnとcol)を基にマージする。
・on=key_column はキー列をマージの基準にする。
・suffixes=('_file1', '_file2') は同じ名前の列に _file1 や _file2 というサフィックスを付け分けることで、どちらのファイルのデータかを区別する。
diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
・file1 と file2 の対応する列(例えばcol)の値が異なる行を抽出。
・!= は不一致のチェック、isna() は NaN(欠損値)のチェック。~ は論理否定。
・&(アンパサンド) は論理積で、両方の条件が成り立つ行を選ぶ。特に、両方がNaNの場合も除外。
・diff.iterrows() で diff データフレームの各行を反復する。_,は本来はindexが入るのだが、使用しないので省略したという書き方
・print 文で、キー列の値と、その列で差異が生じた具体的な値を表示する。file1とfile2の値を比較して、どこに差異があるのかを明確に伝える。