めもぶろぐ

お勉強したこと、趣味なんかを適当に書いてます。。。

pythonでGmail送信

注意点はGoogleアカウントの設定
1. 二段階認証はしない
2. 信頼性の低いアプリの実行を許可する
3. IMAPを有効にする

ということで、捨てアカウントか、重要でないアカウントでしかできません。
とくに乗っ取られる可能性も高いことから、パスワードが他サイトのパスワードと類似しないように気をつけましょう。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate
from platform import python_version
import argparse
import getpass
import codecs

release = python_version()
if release > '2.6.2':
    from smtplib import SMTP_SSL
else:
    SMTP_SSL = None

def create_message(from_addr, sender_name, to_addr, subject, body, encoding):
    msg = MIMEText(body, 'plain', encoding)
    msg['Subject'] = Header(subject, encoding)
    form_jp = u"%s <%s>" % (str(Header(sender_name, encoding)), from_addr)
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Date'] = formatdate()
    return msg

def send_via_gmail(from_addr, to_addr, passwd, msg):
    if SMTP_SSL:
        print "send via SSL..."
        s = SMTP_SSL('smtp.gmail.com', 465)
        s.login(from_addr, passwd)
        s.sendmail(from_addr, [to_addr], msg.as_string())
        s.close()
        print 'mail sent!'
    else:
        print "send via TLS..."
        s = smtplib.SMTP('smtp.gmail.com', 587)
        if release < '2.6':
            s.ehlo()
        s.starttls()
        if release < '2.5':
            s.ehlo()
        s.login(from_addr, passwd)
        s.sendmail(from_addr, [to_addr], msg.as_string())
        s.close()
        print "mail sent!"

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Send Gmail.')
    parser.add_argument('-t', '--to', dest='to_addr', type=str,
                        default='hoge@gmail.com',
                        help='To address')
    parser.add_argument('-s', '--subject', dest='subject', type=str,
                        default='hoge@gmail.com', help='Title')
    parser.add_argument('-b', '--body', dest='body', type=str,
                        default='no message', help='Body of the mail.')
    args = parser.parse_args()

    from_addr = 'hoge@gmail.com'
    sender_name=u'hoge@gmail.com'
    print "from: %s <%s>" % (sender_name, from_addr)
    passwd = getpass.getpass()
    to_addr = args.to_addr
    title = args.subject
    body = args.body
    msg = create_message(from_addr, sender_name, to_addr, title, body, 'utf-8')
    send_via_gmail(from_addr, to_addr, passwd, msg)

inotify, incron ではなくpyinotify!!

inotifyやincronはよく使用されると思うが標準パッケージに入ってなかったり。

探してみるとpyinotifyがある。パッケージ名からしてpython版のinotifyだ。

調べてみればやはり。


inotifyをそのまま使ってもいいが、pythonで、モージュールとしてインポートしてカスタマイズもできるのでおすすめ。
標準でも入っているし、なかなかいい。

とりあえずインストールパッケージ探し

# yum search pyinotify
読み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * epel: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
=============================== 一致: pyinotify ================================
python-inotify.noarch : Monitor filesystem events with Python under Linux
python-inotify-examples.noarch : Examples for Python inotify module

見つかったら、それでインストール。サンプルもいれてみる。

yum -y install python-inotify*

サンプルを見てみる
cat /usr/share/pyinotify/autocompile.py

#!/usr/bin/env python
#
# Usage:
#   ./autocompile.py path ext1,ext2,extn cmd
#
# Blocks monitoring |path| and its subdirectories for modifications on
# files ending with suffix |extk|. Run |cmd| each time a modification
# is detected. |cmd| is optional and defaults to 'make'.
#
# Example:
#   ./autocompile.py /my-latex-document-dir .tex,.bib "make pdf"
#
# Dependencies:
#   Linux, Python 2.6, Pyinotify
#
import subprocess
import sys
import pyinotify

class OnWriteHandler(pyinotify.ProcessEvent):
    def my_init(self, cwd, extension, cmd):
        self.cwd = cwd
        self.extensions = extension.split(',')
        self.cmd = cmd

    def _run_cmd(self):
        print '==> Modification detected'
        subprocess.call(self.cmd.split(' '), cwd=self.cwd)

    def process_IN_MODIFY(self, event):
        if all(not event.pathname.endswith(ext) for ext in self.extensions):
            return
        self._run_cmd()

def auto_compile(path, extension, cmd):
    wm = pyinotify.WatchManager()
    handler = OnWriteHandler(cwd=path, extension=extension, cmd=cmd)
    notifier = pyinotify.Notifier(wm, default_proc_fun=handler)
    wm.add_watch(path, pyinotify.ALL_EVENTS, rec=True, auto_add=True)
    print '==> Start monitoring %s (type c^c to exit)' % path
    notifier.loop()

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print >> sys.stderr, "Command line error: missing argument(s)."
        sys.exit(1)

    # Required arguments
    path = sys.argv[1]
    extension = sys.argv[2]

    # Optional argument
    cmd = 'make'
    if len(sys.argv) == 4:
        cmd = sys.argv[3]

    # Blocks monitoring
    auto_compile(path, extension, cmd)


使えそう。ちょうどコンパイルするinotifyのサンプルがあった
コンパイルはしなけれど、保存したら自動実行したい



なので、こんな形で作った。
vimで編集して、保存したら実行する感じ。

swpファイルは頻繁にできるので、無視。

import asyncore
import pyinotify
import subprocess

wm = pyinotify.WatchManager()  # Watch Manager
mask = pyinotify.IN_MODIFY  # watched events

class EventHandler(pyinotify.ProcessEvent):
    def process_IN_MODIFY(self, event):
      if "swp" in event.pathname or "~" in event.pathname:
        dummy = event.pathname
      else:
        try:
          rst = subprocess.check_output(['python', event.pathname, '>', 'tty'])
          print "\n"
          print "+-----------------+"
          print "| print result"
          print "+-----------------+"
          print rst
        except:
          print "failed to execute " + event.pathname

notifier = pyinotify.AsyncNotifier(wm, EventHandler())
wdd = wm.add_watch("/home/user/python", mask, rec=True)

asyncore.loop()

保存時にコマンドを実行するようにできたので、あとは保存時にShellCheckをしたり、ファイルオープン時にCREATEイベントでswpファイルができたらファイルをバックアップするなど、適当に作れるはず。

bash-completionのカスタマイズ・自作

bash-completionってなんて便利なんだろう。。。
シェルスクリプトを作成していて、引数をよく使うのですが、どれ使えばいいんじゃい
となってしまったときタブ補完できたらなあ



なんて思い、bash-completionを調べました。
とりあえずどこに有るかは、下記コマンドを実行してみてください。


updatedb && locate bash-completion

#
# [実行結果] 1行抜粋
# /usr/share/bash-completion/completions/vgextend
#


ということで、/usr/share/bash-completion/completionsにありました。



addpartというやつが一番単純そうなので、これを参考に自作補完機能を実装します。
ログインプロファイルにでも自作補完機能を配置しましょう。


_addpart_module()
{
	local cur prev
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	case $COMP_CWORD in
		1)
			local DEVS=''
			while read dev; do DEVS+="$dev " ; done < <(lsblk -pnro name)
			OPTS="--help --version $DEVS"
			COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
			;;
		2)
			# FIXME: how to determine next free partition number
			;;
		3)
			COMPREPLY=( $(compgen -W "start" -- $cur) )
			;;
		4)
			COMPREPLY=( $(compgen -W "length" -- $cur) )
			;;
	esac
	return 0
}
complete -F _addpart_module addpart

addpartは上記のような感じ。
配列に引数を格納していってそれを表示させる感じです。



試しに自作completionとシェルを作ってみます。



/var/log/message向けに時間を絞り込んでログを出力するシェルをつくります。

CentOS7から、もしくは他のディストリビューションでは
journalctl --sice=12:00 とかで対応できるので、全く意味のないシェル。





お試しシェル1号

#!/bin/bash

argv=()
while ([ ${#} -ne 0 ]); do
  case "${1}" in
    --*)
      if [[ "${1}" =~ "start=" ]]; then sTime=$(echo "${1}" | cut -d= -f2); fi
      if [[ "${1}" =~ "end=" ]];   then eTime=$(echo "${1}" | cut -d= -f2); fi
      shift
      ;;
    *)
      argv=("${argv[@]}" "${1}")
      shift
      ;;
  esac
done

awk -v sTime=${sTime} -v eTime=${eTime} '{
  if (sTime <= $3 && $3 <= eTime) {
    print
  }
}' ${argv[0]}

この時点では当然タブで補完ができない。
実行するときは下記のような指定方法が必要。


なお、引数は順不同。



/root/work/ExtractLog.sh /var/log/messages --start=22:00 --end=22:10


で、ここでの問題点は引数に--startやら--endやらが必要であること。
引数のオプションなんて忘れるし、順番も忘れる。




まあ、--foo --barって指定できると便利なことも有るのだけど。




補完の設定をしましょう。
.bash_profileに追記しましょう。
もしくは/etc/profile.d/user_bash_completion.shとかいう感じで新規作成もよし。




タブ補完向けの関数作成

_exlog_complete() {
  local cur
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"

  ORG=$IFS
  IFS="
  "
  OPTS=("--start=" "--end=")
  COMPREPLY=( $(compgen -W '${OPTS[@]}' -- $cur) )

  IFS=$ORG
  return 0
}
complete -F _exlog_complete -o default ExtractLog.sh

"complete -F 関数名 -o default 補完機能を実行するときの対象になるコマンド"


"-o default" はファイルとかディレクトリを補完時に出力してくれます。
ファイル・ディレクトリ名の補完が不要のときはつけなくて良いです。




今回はログファイル名の指定をするようにしているので、オプションを入れています。





末尾のコマンド名はシェル名でも良いです。なので下記のようにして
複数シェルにたいして補完機能が効くようにしても便利です。



Command=$(find /root/work -type f -name "*.sh" -exec basename {} \;)
complete -F _exlog_complete -o default ${Command[@]}


できあがったので、ログインし直してシェル実行してみます。

#
# まずはシェル名を指定する
#

[root@localhost work]$./ExtractLog.sh

#
# タブを押して見る。
#

[root@localhost work]$./ExtractLog.sh --
--end=    --start=

#
# おお、共通部分まで保管してくれた。
# eを押してタブを押して見る。
#

[root@localhost work]$./ExtractLog.sh --end=


できた。。。

おわり