【Golang】文字列数字をソートする

f:id:suganoo:20180831184054p:plain
string型数字のスライスを小さい順にソートする方法です。

例えば、"2", "1", "11" があった場合にそのままソートしようとすると文字列ソートになってしまいますから、下記のような順序になりますね。

"1" "11" "2"

"11"が最後に来るべきですよね。

サンプルコード

下記がサンプルです。

package main

import (
        "fmt"
        "sort"
        "strconv"
)

func main() {
        str_num_list := []string{"8", "2", "10", "1", "6"}

        fmt.Printf("before : %v\n", str_num_list)

        sort.Slice(str_num_list, func(i, j int) bool {
                num_i, _ := strconv.Atoi(str_num_list[i])
                num_j, _ := strconv.Atoi(str_num_list[j])
                return num_i < num_j
        })

        fmt.Printf("after  : %v\n", str_num_list)
}
before : [8 2 10 1 6]
after  : [1 2 6 8 10]

ポイント

Slice関数

sortパッケージのSliceを見てみます。
https://golang.org/pkg/sort/#Slice

func Slice(slice interface{}, less func(i, j int) bool)

第二引数に自作のless関数を渡せばOKっすね。

なのでサンプルのようにしてます。

strconv.Atoi

strconv.Atoiは第二返却値にエラーが返されてしまうので、変数としてわざわざ確保しました。

Sliceのソース

こうなってますね。クイックソートでやってるみたい。
https://golang.org/src/sort/slice.go?#L7

func Slice(slice interface{}, less func(i, j int) bool) {
	rv := reflect.ValueOf(slice)
	swap := reflect.Swapper(slice)
	length := rv.Len()
	quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
}

ちなみにquickSort_funcは下記。
https://golang.org/src/sort/zfuncversion.go#136

見たところスライスの数が12以上か以下でクイックソートか挿入ソートで分けてるみたいですね。


うーんGolangは実装中身のコードまで簡単にたどれるからいいね。

【Python】TypeError: f() takes exactly 1 arguments (2 given)

エラーを調査してましたら、うっかり紛らわしいケースが見つかりました。

関数を実行するときに引数の数が正しくないと、タイトルのようなエラーがでます。

例1

ためしに、引数が正しい処理と正しくない処理を実行してみます。

test.py

def printNum(a, b):
  print a
  print b

printNum(3, 5)      # <-- うまくいく
printNum(3, 5, 6)   # <-- エラーになる

実行結果

3
5      # <-- ここまではうまくいってる出力結果
Traceback (most recent call last):
  File "test.py", line 7, in <module>
    printNum(3, 5, 6)
TypeError: printNum() takes exactly 2 arguments (3 given)

すぐにわかりますね。

例2

stackoverflowにこんな記事を見つけました。
stackoverflow.com

再現するようにやってみたんですが、再現しないですね。
記事としても3年前ですから、修正したのかもしれません。

test.py

def printNum(a, b=5):
  print b

printNum(b=3)  # aではなくてbを指定してみる。

実行結果

Traceback (most recent call last):
  File "test.py", line 5, in <module>
    printNum(b=3)
TypeError: printNum() takes at least 1 non-keyword argument (0 given)

TypeError: printNum() takes at least 1 non-keyword argument (0 given)
であれば、まあなんとかどこが悪かったかはすぐにわかるでしょう。

【Python】IndentationError: expected an indented block

うっかりしてるとこんなエラーがでることがあります。

期待したインデントがないよ!ってことです。

例1

test.py

def printStr(text):
print(text)  # <--インデントなし

printStr("hogehoge")

実行結果

  File "test.py", line 3
    print(text)
        ^
IndentationError: expected an indented block

例2

#のコメントはインデントしなくても大丈夫で、コメントブロックはダメみたいですね。

def printStr(text):
# comment01
"""
comment02
"""
print(text)

printStr("hogehoge")

実行結果

  File "test.py", line 6
    """
      ^
IndentationError: expected an indented block

例3

ちなみにインデントを全角スペースでやるとこんなエラーがでました

def printStr(text):
 print(text) # <-- 全角スペース

printStr("hogehoge")
  File "test.py", line 4
SyntaxError: Non-ASCII character '\xe3' in file test.py on line 4, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

一瞬けっこうなんのエラーかわからないですね(汗)。

(俺は)まだシェルスクリプトでこんな初歩的なミスしてんの!?

f:id:suganoo:20180816183921p:plain
シェルスクリプトで初歩的なミスをしてました。

それに気づくまでに半日くらいかかってしまい
すげー自己嫌悪でガッカリです。

同じ轍を踏まないようにブログに書いておきます。

クイズ

下記のシェルスクリプトはエラーになりますが
なんでだかわかりますか?

test.sh

echo "---- test script ----"
function ls_test() {
  PATH=$1
  ls -l ${PATH} | grep "hoge"
}

ls_test "."

実行結果

$ sh test.sh
---- test script ----
test.sh: line 5: ls: コマンドが見つかりません
test.sh: line 5: grep: コマンドが見つかりません


あれれ、ごくごく単純な「ls」や「grep」が見つからない
っと言われてますね。

もちろんですが「ls」と「grep」を単独で実行できます。

なんでだかわかりましたか?
簡単ですよね?




















答え

環境変数のPATHが書き換わってるんですね。
なのでコマンドが使えなくなってるんです。

自分はまったく気づきませんでした。

こんな感じにPATHじゃなくて別の変数名にすればOKです。

test.sh

echo "---- test script ----"
function ls_test() {
  LOCAL_PATH=$1
  ls -l ${LOCAL_PATH} | grep "hoge"
}

ls_test "."


ちなみにですが printenv で環境変数を確認できます。

$ printenv
HOSTNAME=hoge01
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
PATH=.........

これに気付くまで数時間かかってしまいました。

こんなアホなミスをすると情けなくなってくるんで
気を付けましょう。。。とほほ

Goで基礎的なwebアプリの学習

f:id:suganoo:20180810180655j:plain
Goと言えば処理速度が速いことにメリットがあります。

最近のWebサイトもGoで作られる話もよく聞きますし、
Goのwebフレームワークもいろいろ出てきています。

Gin、goji、matini、beego....

いろいろありますね。


フレームワークを使う前に単純にwebプログラミングを学ぶと
よく理解できておすすめ(だそうです)。

そこでこんなページを見つけました。

Introduction · Build web application with Golang

プログラミングの基礎的なところからだんだんと
レベルアップして、webプログラミングやセッション、
データベースの接続までやります。


自分は前半初めの方で止まってしまい、そのまま放置してしまいました。。。

ですが、中身はかなりおすすめです。

gitbookは読みやすいなー

元データはこっちのようです。
github.com

原著はやっぱ中国語っぽい。
beegoも中国からできたし、
やっぱ中国はGoがアツいのかな。
beego.me

【Go】配列の途中までのポインタは取れない(cannot take the address of ...)

文字列を[]byteにして、途中までの配列について
ポインタと取りたいと思ってました。

実際やってみたところエラーになった。

        test_str := "aaabbbccc"
        t_b := []byte(test_str)
        fmt.Println(&t_b[0:5])

こんなエラーがでる

cannot take the address of t_b[0:5]

だけどそもそもこれって当たり前のことじゃないか。

&[97 97 97 97 97 98 98 98 98 98 99 99 99 99 99]

配列の変数名って配列のポインタが入ってるから
その中から部分的に取り出そうといったってそれは無理。

そういう場合はあらためて部分配列を新しく作ってから
ポインタを取り出す。

        part_t_b := t_b[0:5]
        fmt.Println(&part_t_b)

stackoverflow.com

package main

import (
        "fmt"
)

func main() {
        test_str := "aaabbbccc"

        t_b := []byte(test_str)

        fmt.Println("-----origin")
        fmt.Println(test_str)
        fmt.Println(t_b)

        fmt.Println("-----pointer")
        fmt.Println(&t_b)
        //fmt.Println(&t_b[0:5])
        fmt.Println(&test_str)
        
        part_t_b := t_b[0:5]
        fmt.Println(&part_t_b)

}
-----origin
aaabbbccc
[97 97 97 98 98 98 99 99 99]
-----pointer
&[97 97 97 98 98 98 99 99 99]
0xc42000e1d0
&[97 97 97 98 98]

スターティングGo言語

スターティングGo言語

Goでトークナイズ処理してみる。

Goのtext/scannerを使うと、トークナイズ処理ができるらしい。

やりたいことは単純で

  • スペースに区切られたログをタブ区切りにしたい。
  • 一回の読み込みでスペース区切りしてみたい。(→なのでトークナイザを使う)

ただ少し難点があって

  • "aaa bbb ccc" [2018-04-10 10:32]といったダブルクオーテーションや[]で囲まれた場合はタブ区切りにしてほしくない。

ほんとやりたいことは単純だ。


これまでに正規表現を使ってみたり、単純にスペースでsplitして各itemを"で判断してまたコンカチ、でもいいんだけど、
Goのscannerを使ってみたかったのでやってみることにした。

ちなみにだけど正規表現処理はpythonでもGoでもめちゃ遅いしCPUをけっこう消費することが分かっている。

text/scanner では解決できなかった

結論から言うと、text/scannerでは解決できないことが分かった。
scanner - The Go Programming Language

※なのでタイトルみたくトークナイズ処理はできてないけど
似た処理はできた。


基本的にScan()関数は一文字ごとに判断している。

その一文字が数値、文字や記号であれば、その文字以降が数値、コメント、識別子・・・といった
意味のあるカテゴリーの文字の羅列になっているときに意味のある単語(トークン)のかたまりとして判断される。

なのでスペースで区切った文字列全部をトークンとして判断させようとしても、
下記のカテゴリに当てはまってなければ1文字づつ区切られてしまうのでダメだった。

src/text/scanner/scanner.go - The Go Programming Language

GoTokens  = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments

思い通りのトークナイザはできないがサンプルコードを載せておく。
ここを参考にした。
https://socketloop.com/tutorials/golang-how-to-tokenize-source-code-with-text-scanner-package

package main

import (
        "fmt"
        "strings"
        "text/scanner"
)

func main() {
        text_log := `2018-04-10T10:32:00.123456Z hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"`
        codeReader := strings.NewReader(text_log)
        fmt.Println(text_log)
        fmt.Println("---------------------------")

        var scn scanner.Scanner
        scn.Init(codeReader)
        // * Whitespaceは区切り文字として削除する文字。デフォルトは下記
        //  GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' '
        // 下記のようにして変更できる。
        //scn.Whitespace = 1<<' '
        
        // * Mode:トークンとして判断するカテゴリ。デフォルトは下記
        // Mode : ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments
        // 下記のようにして変更できる。
        //scn.Mode = scanner.ScanIdents | scanner.ScanComments
        tok := scn.Scan()
        fmt.Println(scn.TokenText())
        for tok != scanner.EOF {
                tok = scn.Scan()
                //fmt.Println(tok)
                fmt.Println(scn.TokenText())
        }
}

これを実行するとこんな結果になる。

2018-04-10T10:32:00.123456Z     hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"
---------------------------
2018     # <--- タイムスタンプがぶつ切りになってしまってる
-
04
-
10
T10
:
32
:
00.123456
Z
hoge1234hage
aaa
10.20     # <--- IPアドレスも数値と判断されてしまってる
.30
.40
:
80
0.000
0.003
200
401
"GET http://hoge.fuga.com/uaaa HTTP/1.1"

テックキャンプ

bufio の scanner で解決できた

そこでどうしようかとググってみたらよさげな
サンプルを見つけたので試してみたところ、うまくいった。
baubaubau.hatenablog.com
参考にさせていただきます!

package main

import (
        "bufio"
        "fmt"
        "strings"
)

func main() {
        fmt.Println("------Sample Text-------")
        text_log := `2018-04-10T10:32:00 [2018-04-10 10:32] hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"`
        codeReader := strings.NewReader(text_log)
        fmt.Println(text_log)
        fmt.Println("---------------------------")
        fmt.Println("Normal splitter")
        fmt.Println()

        scn := bufio.NewScanner(codeReader)
        scn.Split(bufio.ScanWords)
        for scn.Scan(){
                fmt.Println(scn.Text())
        }

        fmt.Println("---------------------------")
        fmt.Println("Customized splitter")
        fmt.Println()

        codeReader = strings.NewReader(text_log)
        scn_sp := bufio.NewScanner(codeReader)

        dbl_q_on := false
        splitSpace := func(data []byte, atEOF bool) (advance int, token []byte, err error){
                for i := 0 ; i < len(data) ; i++ {
                        if data[i] == '"' || data[i] == '[' || data[i] == ']' {
                                dbl_q_on = ! dbl_q_on
                        }
                        if data[i] == ' ' {
                                if ! dbl_q_on {
                                        return i + 1, data[:i], nil
                                }
                        }
                }
                return 0, data, bufio.ErrFinalToken
        }

        scn_sp.Split(splitSpace)
        for scn_sp.Scan(){
                fmt.Println(scn_sp.Text())
        }
}
------Sample Text-------
2018-04-10T10:32:00 [2018-04-10 10:32] hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"
---------------------------
Normal splitter

2018-04-10T10:32:00
[2018-04-10
10:32]            # <--- こういうのだめ
hoge1234hage
aaa
10.20.30.40:80
0.000
0.003
200
401
"GET
http://hoge.fuga.com/uaaa            # <--- こういうのだめ
HTTP/1.1"
---------------------------
Customized splitter

2018-04-10T10:32:00
[2018-04-10 10:32]            # <--- うまくいった
hoge1234hage
aaa
10.20.30.40:80
0.000
0.003
200
401
"GET http://hoge.fuga.com/uaaa HTTP/1.1"            # <--- うまくいった

さらにもうちょっと改善

上述のコードをもとにもうちょっと、一回のreadでパースできないかと
Scanを使わないで一文字づつの判定でパースするようにしてみた。
それとメモリ消費も抑えるようにもやってみた。

package main

import (
        "fmt"
        "unsafe"
)

func parseLog(data *[]byte) *string {
        dbl_q_on := false
        output := ""
        cur_pos := 0
        for i := 0 ; i < len(*data) ; i++ {
                if (*data)[i] == '"' || (*data)[i] == '[' || (*data)[i] == ']' {
                        dbl_q_on = ! dbl_q_on
                }
                if (*data)[i] == ' ' {
                        if ! dbl_q_on {
                                fmt.Println(string((*data)[cur_pos:i]))
                                output += string((*data)[cur_pos:i]) + "\t"
                                cur_pos = i + 1
                        }
                }
        }
        output += string((*data)[cur_pos:])
        return &output
}


func main() {
        text_log := `2018-04-10T10:32:00 [2018-04-10 10:32] hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"`
        fmt.Println(text_log)
        fmt.Println("-------------------------")

        text_log_b := *(*[]byte)(unsafe.Pointer(&(text_log)))
        //fmt.Println(text_log_b)

        output_str := parseLog(&text_log_b)


        fmt.Println("-------------------------")
        fmt.Println(*output_str)
}

unsafeはどこでも言われてるが使う時に注意が必要なので、むやみやたらとunsafeを使うことはおすすめしない。
unsafeはログを読み込んで1行をbyteに変換することも想定にいれてて
メモリ消費を減らせないかと考えたので使ってる。

2018-04-10T10:32:00 [2018-04-10 10:32] hoge1234hage aaa 10.20.30.40:80 0.000 0.003 200 401 "GET http://hoge.fuga.com/uaaa HTTP/1.1"
-------------------------
2018-04-10T10:32:00
[2018-04-10 10:32]
hoge1234hage
aaa
10.20.30.40:80
0.000
0.003
200
401
-------------------------
2018-04-10T10:32:00     [2018-04-10 10:32]      hoge1234hage    aaa     10.20.30.40:80  0.000   0.003   200     401     "GET http://hoge.fuga.com/uaaa HTTP/1.1"

CPUの負荷も下がった

ログをぶったぎるのにトークナイザを使おうとしたのは
ログをreadする処理を減らしたかったからだ。

スペースでsplitして各itemの先頭をさらに判断してパースする場合
推測なんだけど、スペースを探す処理でログを一回readして、
さらに各itemをforループするときにもう一回readしてる。
2回のループ。

でも先頭から一文字づつ読み込んで判定すれば、
適切な場所に来たら1トークンとなるので、readは一回で済む(と思う)。

そのせいか大量ログで試した場合、splitでやるよりも
readを減らした方がCPU負荷がかなり下がった

勉強になりました。


フリーランスコース

Goならわかるシステムプログラミング

Goならわかるシステムプログラミング

【python】組み込み関数open()でTypeError: an integer is required

open()でファイルを読み込もうとしたら
こんなエラーがでた。

file = "zzz_test.txt"
with open(file, "r", "utf-8") as f_r:
  print f_r.readline()
Traceback (most recent call last):
  File "zzz_opentest.py", line 3, in <module>
    with open(file, "r", "utf-8") as f_r:
TypeError: an integer is required

バージョンは2.7を使っている。

Python 2.7.14

結論から言えば、3コ目の引数には文字コードではなく
ファイル読み込みのバッファ数を指定すべきなので、
(もしくは指定しない。)
「integerが必要だよ!」っとメッセージがでるようです。

open(file, "r", "utf-8")  ---> xダメ
open(file, "r")  ---> OK

2. 組み込み関数 — Python 2.7.14 ドキュメント




似たようなことで困った人いたみたい。
stackoverflow.com



どこで間違えたかなーと思い返すと
特殊なファイルをオープンするときに使う
gzipやcodecと間違えてた。

gzip.open(file, "r", "utf-8")
codecs.open(file, "r", "utf-8")

でencodingをつけてもエラーになる。

Traceback (most recent call last):
  File "zzz_opentest.py", line 4, in <module>
    with open(file, "r", encoding="utf-8") as f_r:
TypeError: 'encoding' is an invalid keyword argument for this function

調べてみたら、encodingはpython3からみたいですね。

qiita.com



まとめ

  • python2でopen()は文字コードつけない。つけるんならcodec.open()、io.open()は文字コードつけられる。
  • python3だとopen()に文字コードつけられる。


【PR】搾取されてない.....!?
客先常駐で働いているエンジニアの方お疲れ様です。
IT業界でSESとして働いている人は多いんですが、自分のマージンや月額単価を知らない人がけっこう多いみたいですね。
自分もそうでした。(^^;)
でもそれかなりもったいないですよ。理由はこちらリツアンSTCの紹介記事を見てみてください。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
blog.suganoo.net

Pythonスタートブック [増補改訂版]

Pythonスタートブック [増補改訂版]

echo でバックスペース文字を出力するオプション

たまにjsonの中身に\nが入ってて
「なんでこんなの入ってるのかなー?邪魔だなー」と思ってました。
わざわざ取り除くのがめんどうだなーと削除してました。

例えばAWSのSQSメッセージを見てそのままechoで出力しようとすると
\nが入ってて適切に出力されず、パイプしてjsonを読み取ろうとしても
エラーになることがありました。


でもechoの -e オプション使えばいいんですね


こんな風になります

echo -e "{\n \"Message\" : \"hogehoge\" \n}"

{
 "Message" : "hogehoge"
}

便利ですねー



他にもさまざまなエスケープ文字は使えますが
下記はよく使うかもしれません。

# タブ \t
echo -e "aaa\tbbb"
aaa     bbb

# 垂直タブ \v   これは知らなかったー!!
echo -e "aaa\vbbb"
aaa
   bbb

# 改行 \n
echo -e "aaa\nbbb"
aaa
bbb

参考
echoコマンド(引数で指定した内容を標準出力に出力する)

覚えて便利 いますぐ使える!シェルスクリプトシンプルレシピ54

覚えて便利 いますぐ使える!シェルスクリプトシンプルレシピ54

シェルコマンドのオプションの意味ならこれが便利!

シェルのコマンド理解してますか?

僕はうろ覚えでやってます。
昔こうやってた、で覚えて今はそのまま何も考えずに
そのオプションをつけてコマンド実行とかやってます。


最近になるとman で調べるのもおっくうで。。。

でもこんなんではダメエンジニアになってしまう!


そんでたまたますごく便利なサイトを見つけてしまいました。


これ!
explainshell.com
f:id:suganoo:20180723132101p:plain
コマンドをオプション付きで入力すると
コマンドのオプションがしっかり説明してくれる!

f:id:suganoo:20180723132121p:plain
すごい!

もう無味乾燥な真っ黒画面のmanページを読まなくてよくなるかも。

こんな感じで結果がでてきます。

うーん便利ですなー



[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

シェルで最後に改行の無いファイルを読むと最後の行は読み飛ばされる

たまーにあるんだけど
windowsで作ったファイルをlinuxで読み込むと
BOMが入ったり、末尾に改行がなかったりする。

その末尾の改行がないファイルを読み込むと
最後の行が読み飛ばされることがあった。

気づくまですげーめんどくさかった。。。


まあ解決方法はここをみればOK
qiita.com

while read LINE || [ -n "${LINE}" ]  # || [ -n "${LINE}" ] は末尾に改行がないときのため
  echo ${LINE}
do < ${FILE}

詳解 シェルスクリプト

詳解 シェルスクリプト

シェルプログラミング実用テクニック

シェルプログラミング実用テクニック

curl でURLのファイル存在確認をするには

これでOK

curl -LI http://google.com/

# いろいろ返ってきますがリクエストの結果が返ってきます
HTTP/1.1 200 OK
Date: Tue, 10 Jul 2018 07:39:16 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 1; mode=block
.........................

qiita.com

新しいシェルプログラミングの教科書

新しいシェルプログラミングの教科書

正規表現を確認するのに便利ツール

正規表現を確かめるのに
このサイトは便利だった。

regex101.com


ログをパースするとかけっこう悩むんですよね。


他にも探してみるといろいろありました。
regexper.com
qiita.com
regex-testdrive.com

やっぱ自分は最初のサイトの方が使いやすいかな。



詳説 正規表現 第3版

詳説 正規表現 第3版

正規表現技術入門 ――最新エンジン実装と理論的背景 (WEB+DB PRESS plus)

正規表現技術入門 ――最新エンジン実装と理論的背景 (WEB+DB PRESS plus)

シェルスクリプト内で計算処理したい

シェルスクリプトでちょっとした計算したい時のメモ
いくつか方法はありますが、下記が簡単

二重カッコをつけるとできる。

XXX="90"
YYY=$((${XXX}+10))
echo ${YYY}    # <-- 100

他にもexpr でもできるけど 掛け算はダブルクオート("*")
つけないとだめとか制約がある。

XXX="90"

ZZZ=`expr ${XXX} + 20`
echo ${ZZZ}   #<--- 110

QQQ=`expr ${XXX} "*" 3`
echo ${QQQ}   #<---270

どちらも変数(XXX)が文字列、int型でも関係なく計算できるのがいいね。

www.atmarkit.co.jp

シェルプログラミング実用テクニック

シェルプログラミング実用テクニック