gologiusの巣

プログラミング、モデリングなどのメモです。誰かの役に立てるとうれしいです。

【バッチ】powershell で複数ファイルをPW付圧縮するバッチを作る話

業務で自動化したかったので作った(業務自体を潰したほうがいいのでは

何がやりたいの?

もらったファイルをリネームして、PW付ZIP圧縮する。

作ったバッチ

※7ZIPのインストールなどをして、7z.exeを叩ける状態にする必要がある

圧縮・解凍ソフト 7-Zip

gist.github.com

使い方

別途下記のようなbatを用意して、D&Dで実行させる。

cd %~dp0
powershell -NoProfile -ExecutionPolicy Unrestricted .\process.ps1 %1 %2 %3 %4 %5 %6 %7 %8 %9
pause

こんな感じ↓ f:id:gologius:20201108162247g:plain

補足感想など

ファイル名の置換

バッチ(DOS)でやろうとしたが、ごり押しバッチしか出てこなかった。 replace() 的な関数使ってスマートにやりたいよね、ということでpowershellを採用した。

PW付圧縮ファイルについて

社内で使えるソフトのほうがいいよねという個人的理由で7zipを採用。

公式に

C:\Program Files\7-Zip\7z.exe" a -pPassWord data.zip data.xlsx

と書いているせいで -pPassWord がオプション指定用と勘違いした。よく考えたらすぐ分かるのだが。 (-p → オプション指定文字列 | PassWord → 任意のパスワード文字列)

コマンドでZIPや7zにパスワードを付ける | 7-Zip

7zipの実行

直接下記のように指定したら、$password が変数展開してくれず、 「$password 」という文字列がパスワードとして設定されてしまった。

&$sevenzip_path a .\$result_file_name .\$work_dir_name  -p$password 

スペースを挟まないと変数展開してくれない模様。とりあえず別変数を用意して対応した。

感想

powershell気持ち悪いし永遠に慣れる気がしない

POSTパラメータがなぜか固定文字列 "on" になってしまう

事象

POSTパラメータやGETパラメータに、セットした覚えのない固定文字列 "on" がセットされてしまう

f:id:gologius:20200715123207p:plain
理想

f:id:gologius:20200715123222p:plain
現実

原因

input type="radio"

かつ

input タグに全角スペースが混じっている

と発生する模様

再現コード

<?php
if (isset($_POST) ) {
    print("<pre>");
    var_dump($_POST);
    print("</pre>");    
}
?>

<form method="post" action="./test_post.php">

    <input type="text" name="group1" value="fugafuga">

        NG<input type="radio" name="group2" value="hogehoge1">
        NG<input type="radio" name="group2" value="hogehoge2">
        NG<input type="radio" name="group2" value="hogehoge3">
        OK<input type="radio" name="group2" value="hogehoge4">

    <button type="submit">検索</button>
</form>

hogehoge1,2,3 のタグには、全角スペースが混じっています。

hogehoge4 は半角スペースで正しく区切られています。

補足

<input type="text" name="group1" value="fugafuga1">

のように、type=text に全角スペースが入っていると、value値は空になる模様。

f:id:gologius:20200715124114p:plain
type=text の全角スペース混じり

なんでや工藤・・・

Vueっぽく階層式プルダウンを作ったが、もっとスマートに書きたいという話

※Vue始めて3か月くらいです。

とりあえず作ったものを下記リンクに置いておく。

Vueでプルダウンテスト

ソースは下記に置いておく。JSなら動くのでgithubio便利。

gologius.github.io/test/PULLDOWN_SAMPLE at master · gologius/gologius.github.io · GitHub

リンク先を見るのが面倒な人向けにGIFも貼っておく

f:id:gologius:20200606152036g:plain
プルダウンのGIF

なぜ作ったのか

  • 3階層、4階層以降に対応したサンプルがなかった(気がする)
  • 超適当に調べたが、微妙にやりたいこととずれたものだった(気がする)
  • こんなんサイトちゃんと見なくても自力実装できるわwwwww(所感)

感想

1

HTMLはそこそこ綺麗かなと思いました

<div id="pulldown">
    <div v-for="(choices,layer) in selectable_items">

        <div v-bind:id="'layer' + layer" v-show="choices.length >= 1">    
            <h2>第{{layer+1}}階層</h2>
            <select v-bind:id="'pd-'+ layer" v-model="selected_stack[layer]">
                <option v-for="item in choices" v-bind:value="item.id">
                    {{item.name}}
                </option>
            </select>
        </div>

    </div>
</div>

<script src="pulldown.js"></script>

2

現状の選択に紐づく選択肢をJSでフィルターして渡してるんだけど、 HTML側のフィルターで頑張るべきなのか、JS側で頑張るべきなのか、ライブラリの思想としてどっちがいいのかがよくわからない

//現状のユーザー選択(selected_stack)に対して、表示すべき選択肢を取得
selectable_items: function () {
    
    var results = [];
    var layer = 0;
    for (layer = 0; layer <= this.selected_stack.length; layer++) {
        
        //上位層の選択結果を取得
        before_selected_id  = ''
        if (layer >= 1 ) {
            before_selected_id = this.selected_stack[layer-1];
        }

        //該当層かつ、上位層の選択肢に合致するもののみ抽出
        layer_result = MENU_M.filter(function (value) {
            return value.layer === layer && value.before_id === before_selected_id;
        });

        results.push(layer_result);
    }
    return results;
},

3

JS側が汚い。初期化処理とかキモイ。watch使っているんだけど謎ラッパーかまさないとListの検知ができない

computed: {
    //watch関数をまともに動作させるためのラッパー
    //参考:https://qiita.com/haruyanagi17/items/d74c0b9546719ff88c63
    watch_selected_stack: function () {
        return Object.assign({}, this.selected_stack); // ディープコピーしたものを返す
    },
},
watch: {
    //選択肢が入力された際の動作定義
    watch_selected_stack: function (newval, oldval) {
        
        //変更箇所を検知
        var update_flag = false;
        var layer = 0;
        for(layer=0; layer < this.selected_stack.length; layer++){
            
            //変更箇所より後の選択肢は初期化する
            if(update_flag) {
                this.selected_stack[layer] = '';
            }

            //変更されている箇所があれば、フラグを立てる
            if (newval[layer] !== oldval[layer]) {
                update_flag = true;
            }
        }
    },      
}

今後

もうちょいきれいに書く方法探しますわ

MySQLで住所CSVをDBテーブルにロードする

概要

住所情報をDBに入れて、検索させて候補表示したりすることはよくあると思います。

なので、その準備方法の一つとして、方法を記載しておきます。

なお、今回使うのは住所.jp様のCSVです。

項目の意味等は別途下記サイトから調べてください。

住所データCSV【住所.jp】

日本郵政にも同じようなCSVファイルがありましたが、読みが半角カナで変換が面倒だったので、 こちらを使用。

環境

Ubuntu 18.04

MySQL 14.14

テーブルを作る

下記SQLでテーブルを作成します。命名がダサいとおもった方は適宜変えてください。

 CREATE TABLE JUSYO(
        JUSYO_CD VARCHAR(10),
        TODOFUKEN_CD VARCHAR(10),
        SIKU_CD VARCHAR(10),
        CHOU_CD VARCHAR(10),
        ZIP VARCHAR(8),
        JIGYOSHO_FLG VARCHAR(1),
        HAISI_FLG VARCHAR(1),
        TODOFUKEN VARCHAR(100),
        TODOFUKEN_KANA VARCHAR(100),
        SIKU VARCHAR(100),
        SIKU_KANA VARCHAR(100),
        CHOU VARCHAR(100),
        CHOU_KANA VARCHAR(100),
        CHOU_HOSOKU VARCHAR(100),
        KYOTO_TORINA VARCHAR(100),
        AZA VARCHAR(100),
        AZA_KANA VARCHAR(100),
        HOSOKU VARCHAR(100),
        JIGYOSHO_NAME VARCHAR(100),
        JIGYOSHO_KANA VARCHAR(100),
        JIGYOSHO_JUSYO VARCHAR(100),
        NEW_JUSYO_CD VARCHAR(100)
    );

ファイルをサーバーに置く

適当な場所でよいです。

もしかしたらサーバーとDBの設定を変えれば、リモート越しでもできるかもしれません。

CSVロード

該当ファイルがある場所でmysql にログイン

先ほど作ったテーブルが存在するDBを選択し、下記SQLを実行 (ファイル名は zenkoku.csv を想定)

LOAD DATA LOCAL INFILE "zenkoku.csv"
INTO TABLE JUSYO
CHARACTER SET SJIS
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"';
IGNORE 1 LINES;

参考: 【MySQL】CSVファイルをデータベースにインポートする | (株)シャルーン

エラー対処法

住所.jp様からDLできるCSVファイルはSJISなので、 DBがUTF8 かつ CHARACTER SET SJIS がない場合、下記のようなエラーで落ちる

ERROR 1300 (HY000): Invalid utf8 character string: '"' 

INTO TABLE *** の直後に CHARACTER SET SJIS をもってこないと、下記エラーになる模様

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version 
for the right syntax to use near 'character SET sjis IGNORE 1 LINES' at line 1

おまけ

全部連結したテーブルを作っておけば、 サジェストなどに使いやすい形になると思います。

こんな感じのデータになります。

郵便番号 住所 住所読み
060-0042 北海道札幌市中央区大通西1丁目 ホッカイドウサッポロシチュウオウクオオドオリニシ01チョウメ

作成したければ、下記のようなSQLで、SELECT結果からTABLEを作成することができます。

create table JUSYO_MIN
as 
SELECT 
zip, 
replace(concat(TODOFUKEN,SIKU,CHOU,AZA,JIGYOSHO_NAME,JIGYOSHO_JUSYO)," ", "") as JUSYO_ALL, 
replace(concat(TODOFUKEN_KANA,SIKU_KANA,CHOU_KANA,AZA_KANA,JIGYOSHO_KANA), " ", "") as JUSYO_KANA_ALL 
FROM JUSYO;

【2019/12/03】生存報告

Python箱庭諸島を書き直してみたりしているのですが、サーバーを立てるまで公開できんのです・・・

ちなみにPHPは先駆者がいました(核爆)

1月には今流行りのAWSを立ててみる予定

MySQL+Python(Flask) でdatetimeが返却される

MySQLのDATETIME型を、SELECT文で取得すると、 pythonのdatetime型で返却される事象があるようです。

私も下記記事のようになりました

Python - PythonでMySQLのTIME型をSELECTする方法|teratail

例コード

import mysql.connector
DB_HOST = "192.168.0.XXX"
DB_NAME = "your_db_name"
DB_USER = "your_db_user"
DB_PW = "your_user_pw"
conn = mysql.connector.connect(user=DB_USER, password=DB_PW, host=DB_HOST, database=DB_NAME)

cur = conn.cursor(dictionary=True) #引数指定すると、辞書型で返してくれる
sql = "SELECT ADD_DATE FROM USER;"
cur.execute(sql)
data = cur.fetchall(); 

print(data)

>>[ { "ADD_DATE": datetime.datetime(2019, 08, 08) } ]

ちなみに、APIを実装するために、Flaskの関数 flask.jsonify() なんかでJSON変換して送信すると、 文字列がMon Mar 22 05:06:07 GMT 1999 のようになってしまう。

素直にYYYYMMDD hh:mm:ss で表示してほしい・・・

解決方法

MySQLならDATE_FORMAT関数があるので、そちらを使ってフォーマットされた文字列を出力する。

ただしそのままSELECTすると、Pythonで結果をfetchする際に、辞書型のキー名がDATE_FORMAT(****)になっていまいます。

なので、SQL文を

sql = 'SELECT DATE_FORMAT(ADD_DATE, %s) AS ADD_DATE  FROM USER;'
cur.execute(sql, ('%Y年%m月%d日 %h時%i分%s秒',))

のようにASを使用してあげるとよいと思います。

ダメな例

なお、Pythonパッケージmysql-connector-pythonを使用する場合、 リファレンス通りのSQLを作るとエラーになります。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.7 日付および時間関数

NG例

sql = 'SELECT  DATE_FORMAT(LAST_UPDATE_DATE,'%Y年%m月%d日 %h時%i分%s秒') FROM USER;"'
cur.execute(sql)

恐らくSQL Statementで使用する'%'がエラーで引っかかるのだと思います。

Use mysql DATE_FORMAT in Python - CodeProject

以上

PythonでMySQLに接続、操作する(2019年風)

(追記 2019/08/21) 私のググり方が悪かったのか、

'rt'ではない方のパッケージ使っている方はちょくちょくいますね・・・

====

PythonMySQLに接続と操作をしようとしました。ググると下記記事が出てきます。

qiita.com

上記記事によると、 mysql-connector-python-rfmysql-connector-python の二つのパッケージが存在し、上記の記事は'rf'の方のパッケージについて記載されています。 rfはサポート終了してるようですので、今後はもう一つのパッケージを使うべきたと思います。

(追記8/13)また、上記記事ではSQL結果を辞書型で扱えないという旨が記載されていますが、 cursorの引数指定で扱うことが可能です。

というわけでmysql-connector-pythonを使用して、MySQLを触っていきます。

ぶっちゃけ大して使い方は変わっていないようです

ソースと参考文献

今回のソースまとめ

https://gist.github.com/gologius/b210556b7ce1f4b5ac33277bd1629e49

公式ドキュメント(詳細はここを読んでください ※英語)

MySQL :: MySQL Connector/Python Developer Guide :: 5.1 Connecting to MySQL Using Connector/Python

前準備

環境はPython実行側が Win10、Python3.7、DBサーバーはUbuntu18.04です。 MySQLの設定等はここでは触れません。

DBは下記のような構造を想定しています。

項目名 TYPE
id int
username varchar
createtime datetime

パッケージインストール

pip install mysql-connector-python

これだけ

接続

import mysql.connector

DB_HOST = "192.168.0.XXX"
DB_NAME = "your_db_name"
DB_USER = "your_db_user"
DB_PW = "your_user_pw"

conn = mysql.connector.connect(user=DB_USER, password=DB_PW, host=DB_HOST, database=DB_NAME)

INSERT

# 1レコードのみ
cur = conn.cursor()
insert_sql = "INSERT INTO USER () VALUES(%s, %s, %s);"
insert_value = (random.randint(1,100), 'testusername', datetime.datetime.now())
cur.execute(insert_sql, insert_value) #第二引数はタプル
conn.commit()

# 複数レコードある場合
insert_values = []
for i in range(10):
    v =  (random.randint(1,100), 'testusername', datetime.datetime.now())
    insert_values.append(v);

cur.executemany(insert_sql, insert_values) #第二引数はタプルリスト
conn.commit()

SELECT

(追記08/13)引数指定することで、辞書型で返却できる旨を追記

#シンプルなSQL実行
cur = conn.cursor(dictionary=True) #引数指定すると、辞書型で返してくれる
select_sql = "SELECT * FROM USER;"
cur.execute(select_sql)

# 辞書型リストを取得
data = cur.fetchall(); 

# 変数埋め込み
cur = conn.cursor() #引数に何も指定しない場合、タプルのリストが返却される
select_sql = "SELECT * FROM USER where id >= %s ORDER BY ID;"
where_value = random.randint(1,100)
cur.execute(select_sql , (where_value,)) #第二引数はタプル ※条件が一つの場合でもタプル型にする必要がある

#ループでも取り出し可能
for id, username, createtime in cur:
    print(id, username, createtime)

切断

cur.close()
conn.close()

あとがき

mysql-connector-python-rfと使い勝手はあまり変わらない気も・・・

随時追記します