SSDに換装した
成果物がある程度完成させてから書くというスタイルなので何も書いていませんでした。
WindowsのノートPCが遅くてMacBook Airをずっと使っていたのですが、最近SSDが安いという話を聞いてSSDに換装しようと思いました。
ちょうどプライムデーでWD BLUE SSDが安かったので購入してEaseUS Todo Backup(https://jp.easeus.com/backup-software/free.html)
でクローニングして換装しました。
換装作業はドライバーで蓋を開けて抜き差しするだけでした。今までHDDの交換はPATAしかやったことがなかったのですが、SATAは非常に楽で時代の進歩を感じました PATAは15年前のPCだからね
交換後に起動するとすぐに立ち上がり、アプリもすぐに起動しました。
そもそもWindows PCの遅さの原因はディスク由来(タスクマネージャで常に100%になっていた)だったので、それが解決したことによってサクサク動くようになりました。
まとめ
SSDを交換することによって延命できるという話をよく目にするのですが、それは正しいと思いました。
SSDは1TBで従来のHDDよりも容量がある上に1万ちょっとで購入できて、時代は進んでいると思いました。
もうHDDには戻れないと感じましたので、みなさんもSSDにしてみてはいかが、どうでしょうか。
Goで作ったprintfでHello world
この記事は、SLP KBIT Advent Calendar 2017 12日目の記事です。
はじめに
予告していた通りGo言語でHello worldを書きます。 なぜHello worldを書くのかというと、アドベントカレンダーの記事の水準が上がってきていて、初心者が書きにくくなってきているからです。Hello worldを書いた程度の微笑ましい記事でもいいと私は思っています。
package main import "fmt" func main() { fmt.Printf("Hello world\n") }
はい、Hello worldを書けました。これで終わりです・・・と本当に言ったら怒られるので本題に入ります。
printfを作る
*nixのシェルにはprintfコマンドが組み込まれています。printfコマンドはC言語のprintfのように引数を書式にしたがって変換し、出力します。
これを作ろうと思った理由は二つあって、Windowsで*nixシェルっぽいコマンドを作っていたのと、楽に作れそうだからです。 楽に作れそうだったのですが、なかなか大変でした。
fmt.Printfに投げて終わり?
はじめに考えていたのは次のようなプログラムでした。
package main import ( "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Fprintf(os.Stderr, "%s: not enough arguments\n", os.Args[0]) } else if len(os.Args) == 2 { f := os.Args[1] fmt.Printf(f) } else { f := os.Args[1] s := os.Args[2:] fmt.Printf(f, s...) // スライスを可変引数にして渡す } }
os.Args[1](1番目の引数)がフォーマット(書式)で、os.Args[2:](2番目以降の引数)が引数となっています。 このまま実行できればそれで終わりだったのですが、数値の変換が行われません。 なぜそうなるかというと、os.Argsがstringの配列で、stringとして渡されるからです。
引数を変換する
fmt.Printf()の引数をGoDocで見てみましょう。
func Printf(format string, a ...interface{}) (n int, err error)
1番目の引数は書式で文字列、2番目以降の引数はinterface{}(任意の型)の可変引数です。つまり、2番目以降の引数を変換したものを[]interface{}(interface{}の配列)に入れておけばいいわけです。
では、どうやって変換すればいいでしょうか。例えば、99が整数であるか文字列であるか判断することはできません。そこでformatを参照して文字列であるか整数であるか実数であるかを判断します。
判断する関数を作る
使える書式
今回のprintfで使える書式は以下の書式です。
- 文字列
- %s
- %q(ダブルクォーテーションをつける)
- 整数
- %d
- %o
- %x
- %X
- 実数
- %f
- %F
- %e
- %E
- その他
- %%(%を出力)
どう解析するか
1文字ずつ調べて、'%'を見つけたら書式フラグを立てます。 書式であることがわかれば文字列であれば文字列、整数であれば整数、実数であれば実数を配列に加えます。整数か実数が指定されているのにそうでない場合は0もしくは0.0を配列に加えます。 書式でない文字が出てきた場合か書式フラグが立ったまま解析が終わった場合、エラーを返します。コード
- %%(%を出力)
func StrToIF(f string, s []string) ([]interface{}, error) { a := []interface{}{} runes := []rune(f) argNum := 0 percent := false verb := "" for _, r := range runes { if percent { verb += string(r) switch r { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '+', '-', '.', '#': case '%': if verb != "%%" { return a, FormatError{verb} } else { // %% percent = false continue } case 's', 'q': if argNum < len(s) { a = append(a, s[argNum]) argNum++ } else { a = append(a, "") } percent = false case 'd', 'o', 'x', 'X': var i int64 var err error if argNum < len(s) { i, err = strconv.ParseInt(s[argNum], 0, 64) if err != nil { i = 0 } argNum++ } else { i = 0 } a = append(a, i) percent = false case 'f', 'F', 'e', 'E': var fl float64 var err error if argNum < len(s) { fl, err = strconv.ParseFloat(s[argNum], 64) if err != nil { fl = 0.0 } argNum++ } else { fl = 0.0 } a = append(a, fl) percent = false default: return a, FormatError{verb} } } if r == '%' { if percent == false { percent = true verb = "%" } } } if percent { return a, FormatError{verb} } return a, nil }
エスケープシーケンスの処理
書式に合わせた変換はできるようになりましたが、"Hello world\n"を入力すると改行されずにそのまま出力されます。これは"[\n]"が"[\][n]"といったようになるからです。
そのため、エスケープシーケンスに変換します。stringsパッケージを用いて泥臭く実装しました。
func formatReplace(f string) string { f = strings.Replace(f, "\\a", "\a", -1) f = strings.Replace(f, "\\b", "\b", -1) f = strings.Replace(f, "\\f", "\f", -1) f = strings.Replace(f, "\\n", "\n", -1) f = strings.Replace(f, "\\r", "\r", -1) f = strings.Replace(f, "\\t", "\t", -1) f = strings.Replace(f, "\\v", "\v", -1) f = strings.Replace(f, "\\'", "'", -1) f = strings.Replace(f, "\\\"", "\"", -1) return f }
最後に加工した書式と[]interface{}を可変引数に展開してfmt.Printf()に投げて完成です。
まとめ
以上よりprintfを作ることができました。ソースコードはGitHubに上げています。
標準ライブラリのみで使ったのでWindowsでも簡単に使うことができます。 これでprintfを行うことで誰でもシェル上でHello worldを行うことができます。 シェルならWindowsでもechoコマンドでできますが。
Go言語でnkfみたいなやつ
nkfという今年で30周年になる古いソフトがあります。nkfでは文字コードを変換することができます。 nkfには様々な機能がありますが、次の3点を満たすものをGo言語で作ってみました。
- 文字コードを変換する(ISO-2022-JP、Shift_JIS、EUC-JP、UTF-8)
- 文字コードを判定する
- 改行コードを変換する(UNIXはLF、WindowsはCRLF、MacintoshはCR)
文字コードを変換する
文字コードの変換は以下の記事を参考にしました。というか丸コピ
文字コードを判定する
文字コードの判定はchardetを使用しました。chardetは文字コードを判定するためのライブラリです。 判定に失敗した時にはエラーを返します。
改行コードを変換する
改行コードの変換は正規表現を使用しました。
コード
コードは以下のようになりました。
main.go
package main import ( "flag" "fmt" "log" "os" ) var ( inJIS = flag.Bool("J", false, "Input JIS(ISO2022JP)") inEUC = flag.Bool("E", false, "Input EUCJP") inSJIS = flag.Bool("S", false, "Input Shift_JIS") inUTF8 = flag.Bool("W", false, "Input UTF-8") outJIS = flag.Bool("j", false, "Output JIS(ISO2022JP)") outEUC = flag.Bool("e", false, "Output EUCJP") outSJIS = flag.Bool("s", false, "Output Shift_JIS") outUTF8 = flag.Bool("w", false, "Output UTF-8") unix = flag.Bool("Lu", false, "LF(UNIX) Newline") windows = flag.Bool("Lw", false, "CRLF(Windows) Newline") macintosh = flag.Bool("Lm", false, "CR(Macintosh) Newline") override = flag.Bool("override", false, "Override") guess = flag.Bool("g", false, "Detect Charset") ) func setIn() string { switch { case *inJIS: return "ISO2022JP" case *inEUC: return "EUCJP" case *inSJIS: return "ShiftJIS" case *inUTF8: return "UTF8" default: return "" } } func setOut() string { switch { case *outJIS: return "ISO2022JP" case *outEUC: return "EUCJP" case *outSJIS: return "ShiftJIS" case *outUTF8: return "UTF8" default: return "ISO2022JP" } } func setNl() string { switch { case *unix: return "UNIX" case *windows: return "WINDOWS" case *macintosh: return "MACINTOSH" default: return "" } } func main() { var fp *os.File var err error flag.Parse() if len(flag.Args()) < 1 { fp = os.Stdin } else { fp, err = os.Open(flag.Args()[0]) if err != nil { log.Fatal(err) } defer fp.Close() } in := setIn() out := setOut() nl := setNl() if *guess { charset, _ := Guess(fp) fmt.Println(charset) } else { str, err := Convert(fp, in, out, nl) if err != nil { log.Fatal(err) } if *override && !(len(flag.Args()) < 1) { fp.Close() fp, err = os.OpenFile(flag.Args()[0], os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } defer fp.Close() fmt.Fprint(fp, str) } else { fmt.Fprint(os.Stdout, str) } } }
convert.go
package main import ( "errors" "io/ioutil" "os" "regexp" "strings" "github.com/saintfish/chardet" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) var errDet = errors.New("Couldn't detect") func charDet(b []byte) (string, error) { d := chardet.NewTextDetector() res, err := d.DetectBest(b) if err != nil { return "", err } switch res.Charset { case "Shift_JIS": return "ShiftJIS", nil case "UTF-8": return "UTF8", nil case "EUC-JP": return "EUCJP", nil case "ISO-2022-JP": return "ISO2022JP", nil default: return res.Charset, errDet } } func toUtf8(str string, in string) (string, error) { var u8 []byte var err error switch in { case "ISO2022JP": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ISO2022JP.NewDecoder())) case "EUCJP": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.EUCJP.NewDecoder())) case "ShiftJIS": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewDecoder())) case "UTF8": u8, err = []byte(str), nil default: u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewDecoder())) } if err != nil { return "", err } return string(u8), err } func toSjis(str string) (string, error) { sjis, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewEncoder())) if err != nil { return "", err } return string(sjis), err } func toEuc(str string) (string, error) { euc, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.EUCJP.NewEncoder())) if err != nil { return "", err } return string(euc), err } func toJis(str string) (string, error) { jis, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ISO2022JP.NewEncoder())) if err != nil { return "", err } return string(jis), err } func nlRep(str string, nl string) string { rep := regexp.MustCompile(`\r\n|\r|\n`) switch nl { case "UNIX": return rep.ReplaceAllString(str, "\n") case "WINDOWS": return rep.ReplaceAllString(str, "\r\n") case "MACINTOSH": return rep.ReplaceAllString(str, "\r") } return str } /* Only detect Character encoding */ func Guess(file *os.File) (string, error) { input, err := ioutil.ReadAll(file) if err != nil { return "", err } det, err := charDet(input) return det, err } /* Convert Character encoding */ func Convert(file *os.File, in string, out string, nl string) (string, error) { input, err := ioutil.ReadAll(file) if err != nil { return "", err } if in == "" { in, err = charDet(input) if err != nil { return "", err } } u8, err := toUtf8(string(input), in) if err != nil { return "", err } if nl != "" { u8 = nlRep(u8, nl) } var output string switch out { case "ISO2022JP": output, err = toJis(u8) case "ShiftJIS": output, err = toSjis(u8) case "EUCJP": output, err = toEuc(u8) case "UTF8": output = u8 } return output, err }
まとめ
EUC-JPの判定はあまりうまくいかないのでEオプションをつけるといいと思います。
chardetはShift_JISとEUC-JPで誤判定が発生する可能性があるため、overrideオプションを使う時は入力オプションを使うことをおすすめします。
ここまで長くなるとコードをまるまる載せるのではなく、GitHubで公開したほうがいいと思いました。
GitHubに公開しました(2017/07/14)
Windowsでuptimeコマンドもどき(Go言語)
Windowsで稼働時間を確認する方法としてコマンドプロンプトでの"net statistics server"やタスクマネージャなどがあります。
しかし、"net statistics server"で得られる情報は開始日時で稼働時間を得るには計算がいりますし、タスクマネージャは起動しなければいけません。
そのためにコマンドラインで稼働時間を取得するためにGo言語でuptimeもどきを作りました。ロードアベレージを取得するには前もって用意していないと駄目で、ユーザ数を取得するWin32 APIは知らないので現在時刻と稼働時間のみ出力します。
package main import ( "fmt" "log" "os" "syscall" "time" ) func main() { dll, err := syscall.LoadDLL("Kernel32.dll") if err != nil { log.Fatal(err) } defer dll.Release() proc, err := dll.FindProc("GetTickCount64") if err != nil { log.Fatal(err) } tcP, _, lastErr := proc.Call() lastErr = syscall.GetLastError() if lastErr != nil { log.Fatal(lastErr) } tc := (uint64)(tcP) sec := tc / 1000 min := sec / 60 hour := min / 60 day := hour / 24 hour %= 24 min %= 60 var up string if day > 0 { if day > 1 { up = fmt.Sprintf("up %d days, %02d:%02d", day, hour, min) } else { up = fmt.Sprintf("up %d day, %02d:%02d", day, hour, min) } } else if hour > 0 { up = fmt.Sprintf("up %02d:%02d", hour, min) } else { up = fmt.Sprintf("up %d min", min) } t := time.Now() now := fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()) fmt.Fprintf(os.Stdout, " %s %s\n", now, up) }
実行結果は次のようになります。XPの場合、GetTickCount64がないのでGetTickCountを使ってtcのキャストをuint32にしてください。

長い時間稼働させると何が起こるかわからないので定期的に再起動は行いましょう。
Go言語でriver.go.jpからダムの貯水率を取得する
最近雨が降らないな、と思っていたらニュースで早明浦ダムの取水制限が始まることを知り驚きました。 取水制限が始まるくらい貯水率が減っていたのなら早明浦ダムbotが毎時間ツイートしているだろう、 と思っていたら止まっていました。(2017/06/18現在動いています。)
ただいまの貯水率は100.0%です (10:00)
— 早明浦ダム 貯水率 (@sameura_dam) 2017年1月4日
作者の 103yen (@103yen) | Twitter さんによると、 river.go.jpの仕様が変わって貯水率を取得できなかったようです。
早明浦ダムbotですが、https://t.co/qjZXRKPIquの仕様変更で動かなくなったんで止めてます
— 103yen (@103yen) 2017年6月17日
なんでbotが止まっていたかどうかわからなかったので、自分で貯水率を取得してつぶやこうと思いました。 作者さんのWebページ( ダムbotシリーズについて )とブログ( Pythonでriver.go.jpからダムの貯水率を取得する - 99円のへたれ日記! )を参考にしてGo言語で実装しました。(ほぼgoquery)
package main import ( "fmt" "log" "github.com/PuerkitoBio/goquery" ) type Storage struct { date string time string pos string } var ( BASE_URL = "http://www1.river.go.jp" OUTSIDE_URL_BEFORE = "/cgi-bin/DspDamData.exe?ID=" OUTSIDE_URL_AFTER = "&KIND=3&PAGE=0" ID = "1368080700010" ) func GetPerOfStorage() (s Storage) { found := false outside_url := BASE_URL + OUTSIDE_URL_BEFORE + ID + OUTSIDE_URL_AFTER outside, err := goquery.NewDocument(outside_url) if err != nil { log.Fatal(err) } iframe := outside.Find("iframe") iframe_url, _ := iframe.Attr("src") contents_url := BASE_URL + iframe_url contents, _ := goquery.NewDocument(contents_url) contents.Find("tr").EachWithBreak(func(i int, tr *goquery.Selection) bool { if found == true { return false } tr.Find("td").EachWithBreak(func(j int, td *goquery.Selection) bool { if j == 0 { s.date = td.Text() } else if j == 1 { s.time = td.Text() } else if j == 6 { s.pos = td.Text() } if s.pos != "" && s.pos != "-" { found = true return false } return true }) return true }) return } func main() { s := GetPerOfStorage() fmt.Printf("%s %s %s\n", s.date, s.time, s.pos) }
これで取得したデータをRubyを用いてツイートしました。
— もっさんさん (@moxtsuan) 2017年6月17日
2時間ごとにツイートしていたのですが、早明浦ダムbotが復活したのでtwitterでのツイートはやめました。 (GNU socialでは毎時間クイップしてます)
Raspberry Pi 2 Model BでGNU social
この記事は、SLP KBIT Advent Calendar 2016 10日目の記事です。
はじめに
Twitterはことあるごとにダウンします。 自分はツイートをよくするのでじれったく思います。 Twitterは最近身売りを検討していると聞きます。 買収されるならともかく、もしTwitterがなくなったらどうすればいいでしょうか。
Twitterが使えない時にどのSNSを使えばいいでしょうか、FB、Instagram、LINEなど様々なSNSがありますが、Twitterの完全な代替となるようなSNSはほとんどないと思います。 しかし、その中でもGNU socialはTwitterの代わりになりうると思いました。
GNU social
GNU socialはTwitterのようなマイクロブログを提供するソフトウェアです。 名前の通りフリーソフトです。 GNU socialの目的はマイクロブログの連合を作ることによる中央集権化された資本主義なサービスからの離脱、とのことです。 ソースコードはgithubで公開されているため、簡単に導入することができます。PHPで書かれているのでビルドの必要はありません。
導入した理由
以前より、 https://Quitter.no でアカウントを作ってクイップを行っていたのですが、GNU socialの思想が(フリーソフトウェアという思想の他に)自分のサーバに掲示板を建てたりするパソコン通信と似ているのではと思い、導入しようと思いました。導入の際にApacheとnginxの使い方を学ぶこともできました。
Raspberry Piを使った理由
Raspberry Piで建てられるなら安価でGNU socialを普及することができると思ったからです。RPiより安いマシンがあるかも
今回使ったRaspberry Piは「Raspberry Pi 2 Model B」です。
(2017/04/16 追記)OSはRaspbian Jessieです。
参考サイト
以下のサイトを参考にしました。
GNU socialのインストール - Akionux-wiki
Instalar GNUSocial en una Raspberry Pi - la Enredadera
GNU Social · Mesh network Practical Guide
導入まで
下準備
GNU socialには - PHP - MariaDB - サーバ
の3つが必要なので、 GNU socialのインストール - Akionux-wiki を参考にしてインストールします。
nginxを用いる場合、
sudo apt-get install git nginx php5 mariadb-server mariadb-client php5-curl php5-gd php5-intl php5-gmp php5-json php5-mysqli openssl exif gettext
で揃います。
MariaDBの設定
MariaDBにログインして、データベースを作ります(ここではsocialとします)。
CREATE DATABASE social; GRANT ALL on social.* TO 'social'@'localhost' IDENTIFIED BY 'agoodpassword'
GNU socialのダウンロード
GNU Socialを適当なディレクトリにダウンロードします。
git clone https://git.gnu.io/gnu/gnu-social.git
ダウンロードしたら公開するディレクトリ(ここでは/var/wwwとします)にgnu-socialを移動して、書き込み権限を与えます。
sudo chmod a+w /var/www/gnu-social/
gnu-socialに - avatar - background - file
というディレクトリを作って書き込み権限を与えます。
nginxの設定
nginxの設定を GNU Social · Mesh network Practical Guide の"Confiugre Nginx Web Server to Server GNU Social"を参考にして行います。
基本的には参考元の設定(server_nameの名前は変える、httpsを使う場合はポート番号を443にしてSSL設定を追加する)を使えばいいのですが、httpでアクセスされた時にhttpsに変換するために次の設定を追加しました。
server {
listen 80;
server_name example.com;
rewrite ^ https://$server_name$request_uri? permanent;
}
アクセス
アクセスしてうまく設定ができていれば初期設定ができます。初期設定ができれば導入完了です。
最後に
2ヶ月ほどの試行錯誤の末、GNU socialを導入することができました。
グローバルIPの変更をメールで通知するシェルスクリプト
グローバルIPアドレスは固定サービスを使わない限り変わります。
IPアドレスが変わってしまうといろいろ困るため、メールで通知するようなシェルスクリプトを書きました。
DiCE使えばこんな面倒なことしなくていい
#!/bin/sh -eu MAIL_TO="TO" SUBJECT="SUBJECT" globalip="/var/tmp/globalip" #"XXX.XXX.XXX.XXX"だけ書かれたファイル tmp=`mktemp` curl -sS inet-ip.info > ${tmp} set +e cmp ${globalip} ${tmp} > /dev/null res=$? set -e ip=`cat ${tmp}` mail_send () { echo ${ip} | mail -s "$SUBJECT" "$MAIL_TO" } if [ $res -eq 1 ]; then mail_send cat ${tmp} > ${globalip} fi exit 0
このスクリプトの問題点はcurlを使うことで、この手のグローバルIPを教えてくれるサイトはよく落ちます。
そのため、複数の候補から応答するものを使うようにしようと思っています。
また、以前のIPが書いてあるファイルがなければ作るなど改良したい所はいろいろあります。