ZipCloudから差分更新するシェル作るよ

#ZipCloudから差分更新するシェル作るよ

### 処理フロー概略
##### CSVファイル取得シェル
- htmlキャッシュ (最新 / 過去分)
- DL用URL取得し、最新/過去分のURLリストをマージ
- ZIPファイルを取得し、MD5値チェックし、解凍(cnvファイル)。csvファイルは削除

##### 差分データ登録シェル
- 取得したcnvファイルから、DELETE文を作成・実行（ファイル名昇順）
- 取得したcnvファイルから、INSERT文を作成・実行（ファイル名昇順）

※ファイル単位コミット。コミット後、cnvファイル削除


### curlでzipCloudからhtml取得し、sedでxmllintでエラーになる\<a\>タグのIdを除去し、xmllintで必要要素の取得
```
curl -so- http://zipcloud.ibsnet.co.jp | \
sed 's|\sid="[^\"]*"></a>|></a>|g' | \
xmllint --xpath '//div[@class="dlEntry"][2]' --html -
```

###  Htmlを取得してキャッシュ
```
hoge=`curl -so- http://zipcloud.ibsnet.co.jp `
```

### download URL取得
```
curl -so- http://zipcloud.ibsnet.co.jp | sed 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath '//div[@class="dlEntry"][2]/div[@class="dlButton"]/a/@href' --html - | sed -r 's|\shref="([^"]*)"|\1|g'
```

### 検証用md5値　取得
```
curl -so- http://zipcloud.ibsnet.co.jp | sed 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath '//div[@class="dlEntry"][2]/div[@class="dlDetail"]/table/tr[3]/td' --html - | sed 's/<[^>]*>//g'
echo $hoge | sed 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath '//div[@class="dlEntry"][2]/div[@class="dlDetail"]/table/tr[3]/td' --html - | sed 's/<[^>]*>//g'
```

### 実md5値　
```
curl -so- http://zipcloud.ibsnet.co.jp$hoge2 | md5sum | awk '{ print $1 }' | tr -d '\n'
```

### md5値の検証
```
diff \
 <(echo $hoge | sed 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath '//div[@class="dlEntry"][2]/div[@class="dlDetail"]/table/tr[3]/td' --html - | sed 's/<[^>]*>//g') \
 <(curl -so- http://zipcloud.ibsnet.co.jp$hoge2 | md5sum  | awk '{ print $1 }' | tr -d '\n')
```

### 差分ファイル取得
```
curl -so- http://zipcloud.ibsnet.co.jp/zipcodedata/download?di=1493367014346 > hoge.zip
unzip hoge.zip
cat aaa | iconv -f SJIS -t UTF8
```

### 過去分の download URL取得
```
aryDlUrl=`curl -so- http://zipcloud.ibsnet.co.jp/zipcodedata/list | sed 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath '//div[@class="dlEntry"]/div[@class="dlButton"]/a/@href' --html - | sed -r 's|\shref="([^"]*)"|\1|g' | sed 's|/zipcodedata| /zipcodedata|g'`
```

### zipファイルをDL・解凍・SJIS=>UTF-8変換(cnv拡張子)を行う。
```
zipFileBasePath=".";
zipBaseDlUrl="http://zipcloud.ibsnet.co.jp/zipcodedata";

for tmpDlUrl in ${aryDlUrl}
do \
  tmpDlFileHeader=`curl -Is $zipBaseDlUrl$tmpDlUrl | sed 's|\r||g'`;
  targetZipFilePath=$zipFileBasePath/`echo "$tmpDlFileHeader" | grep 'filename' | cut -d '=' -f2`;
  # 指定zipファイルが存在しない場合
  ! test -f $targetZipFilePath && \
  curl -OJs $zipBaseDlUrl$tmpDlUrl && \
  unzip $targetZipFilePath
done
find $zipFileBasePath -maxdepth 1 -name '*.CSV' -type f -print | xargs -i sh -c "iconv -s -f SJIS -t UTF-8 {} >{}.cnv";
find $zipFileBasePath -maxdepth 1 -name '*.CSV' -type f -print | xargs -i rm {};
```

### CSV から SQL生成
```
#!/bin/bash

### CSV EDITOR ###

# 定数
readonly zipFileBasePath=".";
readonly regexp="^([0-9]+),\"([0-9 ]+)\",\"([0-9 ]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",([01]),([01]),([01]),([01]),([012]),([0-6]).+$"

# 変更郵便番号群
for targetZipCode in `cat *.CSV.cnv | cut -d, -f3 | sort | uniq | sed -r 's|"||g' | head -3`
do
  echo -e "DELETE FROM HOGE WHERE zipCode='$targetZipCode'"
  for line in "`grep $targetZipCode x-ken-all.csv.cnv`"
  do
    if [[ $line =~ $regexp ]]
    then
      echo -e "AND NOT EXISTS(SELECT 'X' FROM FOO WHERE zipCode='"${BASH_REMATCH[3]}"' AND ADDRESS1='"${BASH_REMATCH[7]}"') "
      echo -e "INSERT INTO HOGE VALUES(" \
      "'"${BASH_REMATCH[3]}"'," \
      "'"${BASH_REMATCH[7]}"'" \
      ");"
    else
      exit 1;
    fi
  done
done

echo "done...("`date +%Y/%m/%d_%H:%M:%S`")";
```
### Oracle 側　( Delete / Insert文想定 )
↓最終的にこんな風にしたい

```
#!/bin/bash

### QUERY EXECUTOR ###

SQL_FILE=$SCRPT_SHELL/LOGTBL_DEL.sql

# DB接続  user/pass@host[:port]の構成
CONNECT_DB="$ORACLE_USER/$ORACLE_PASS@$DB_HOST:$DB_PORT/$DB_NAME"

# sqlplusの文字化け対策
export NLS_LANG=Japanese_Japan.AL32UTF8

#sqlplus起動 ここからEOFまでsqlplus側のコード
sqlplus -s /nolog << EOF

conn $CONNECT_DB
-- エラー時の戻り値設定
WHENEVER OSERROR EXIT 9
WHENEVER SQLERROR EXIT 9
SET SERVEROUTPUT ON SIZE UNLIMITED
-- 不必要な項目の表示抑制
SET NEWPAGE NONE
SET SPACE 0
SET TIME OFF
SET TIMING OFF
SET ECHO OFF
SET FEEDBACK OFF
SET HEADING OFF
SET PAGESIZE 0
SET VERIFY OFF
-- 問い合わせ結果非表示
SET TERMOUT OFF

-- SQL実行
@$SQL_FILE $1
exit COMMIT
EOF
```

###↓使いたかったが、要件を満たしそうにない
```
merge into EMP_BK t
using EMP f
on ( t.EMPNO = f.EMPNO )
when matched then
    update set
        t.ENAME    = f.ENAME,
        t.JOB      = f.JOB,
        t.MGR      = f.MGR,
        t.HIREDATE = f.HIREDATE,
        t.SAL      = f.SAL,
        t.COMM     = f.COMM,
        t.DEPTNO   = f.DEPTNO
    where 7300 <= f.EMPNO and f.EMPNO < 7600
    delete
    where t.SAL < 1000
when not matched then
    insert (t.EMPNO, t.ENAME, t.JOB, t.MGR, t.HIREDATE, t.SAL, t.COMM, t.DEPTNO)
    values (f.EMPNO, f.ENAME, f.JOB, f.MGR, f.HIREDATE, f.SAL, f.COMM, f.DEPTNO)
    where 7300 <= f.EMPNO and f.EMPNO < 7600
```
### 現状最終形
- CsvDownLoader.sh

```sh
#!/bin/bash

### CSV DOWNLOADER ###

## 定数
readonly DOMAIN='zipcloud.ibsnet.co.jp'
readonly zipFileBasePath=".";
readonly zipBaseDlUrl="http://$DOMAIN";

# Htmlを取得してキャッシュ
readonly cacheGetRecentHtml=`curl -so- $zipBaseDlUrl`
readonly cacheGetPastHtml=`curl -so- $zipBaseDlUrl/zipcodedata/list`

## zipファイル取得・md5検証・解凍
function getFileWithUnzip () {

  # Htmlキャッシュ
  local cacheGetHtml=$1

  # dlEntry要素番号
  local cnt=$2

  # ダウンロードURL取得
  local tmpDlUrl=`echo "$cacheGetHtml" | sed -r 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath "//div[@class='dlEntry'][$cnt]/div[@class='dlButton']/a/@href" --html - | sed -r 's|\shref="([^"]*)"|\1|g' | sed -r 's|/zipcodedata|/zipcodedata|g'`
  local tmpDlFileHeader=`curl -Is $zipBaseDlUrl$tmpDlUrl | sed -r 's|\r||g'`
  local targetZipFilePath=$zipFileBasePath/`echo "$tmpDlFileHeader" | grep 'filename' | cut -d '=' -f2`

  # Zipファイルが存在する場合、即復帰
  test -f $targetZipFilePath && return

  # 検証用md5値　取得
  local verifyMd5Val=`echo "$cacheGetHtml" | sed -r 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath "//div[@class='dlEntry'][$cnt]/div[@class='dlDetail']/table/tr[3]/td" --html - | sed -r 's/<[^>]*>//g'`

  # 実md5値
  local actualMd5Val=`curl -so- $zipBaseDlUrl$tmpDlUrl | md5sum | awk '{ print $1 }' | tr -d '\n'`

  # md5値が一致する場合
  diff <(echo $verifyMd5Val) <(echo $actualMd5Val) && \
  curl -OJs $zipBaseDlUrl$tmpDlUrl && \
  unzip $targetZipFilePath
}

## 最新分
getFileWithUnzip "$cacheGetRecentHtml" 2

## 過去分
readonly pastElementCnt=$(( `echo "$cacheGetPastHtml" | sed -r 's|\sid="[^\"]*"></a>|></a>|g' | xmllint --xpath 'count(//div[@class="dlEntry"])' --html -` + 1 ))
for (( cnt=1; cnt<$pastElementCnt; cnt++));
  do
    getFileWithUnzip "$cacheGetPastHtml" $cnt
done

## 全国一括データ （加工済バージョン）分
getFileWithUnzip "$cacheGetRecentHtml" 3

## 最新 / 過去分 / 全国一括データ （加工済バージョン） の SJIS=>UTF-8変換(cnv拡張子)・ファイル削除
find $zipFileBasePath -maxdepth 1 -iname '*.CSV' -type f -print | sed 's|\r||g' | xargs -i sh -c "iconv -s -f SJIS -t UTF-8 {} >{}.cnv";
find $zipFileBasePath -maxdepth 1 -iname '*.CSV' -type f -print | xargs -i rm {};

## End.
echo "done...("`date +%Y/%m/%d_%H:%M:%S`")";
exit 0;
```
<br/>
- CsvEditor.sh

```sh
#!/bin/bash

### CSV EDITOR ###

# 定数
readonly zipFileBasePath=".";
readonly regexp="^([0-9]+),\"([0-9 ]+)\",\"([0-9 ]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",\"([^,]+)\",([01]),([01]),([01]),([01]),([012]),([0-6]).+$"

# 変更郵便番号群
for targetZipCode in `cat *.CSV.cnv | cut -d, -f3 | sort | uniq | sed -r 's|"||g' | head -30`
do
  echo -e "DELETE FROM HOGE WHERE zipCode='$targetZipCode'"
  for line in "`grep $targetZipCode x-ken-all.csv.cnv`"
  do
    if [[ $line =~ $regexp ]]
    then
      echo -e "AND NOT EXISTS(SELECT 'X' FROM FOO WHERE zipCode='"${BASH_REMATCH[3]}"' AND ADDRESS1='"${BASH_REMATCH[7]}"') "
      echo -e "INSERT INTO HOGE VALUES(" \
      "'"${BASH_REMATCH[3]}"'," \
      "'"${BASH_REMATCH[7]}"'" \
      ");"
    else
      exit 1;
    fi
  done
done

echo "done...("`date +%Y/%m/%d_%H:%M:%S`")";
```

### 総括・ハマりポイント
- while文などの処理は、bash側で別プロセス処理されるらしい。その為、グローバル変数？から受け取っても、返却時に値を破壊してしまうらしい。
対策としては、今回のように同期？させてしまうのがスマートっぽい（らしい）。
- sedは挙動が掴めないので、perlのsed？(rオプション）を使うのが良いっぽい
- xmllintはクセが強く（そもそもxmlのlint（妥当性検証）用？）の為、なぜかaタグのidが既に定義済と怒られてしまう。不具合？。対策は、idを消す
- iconvにoオプションで出力ファイル名が指定できると説明するブログなどがあるが、全環境でoオプションが使えるわけではないっぽい。