2017/08/17


2020/07/20 追記: 最近の Windows 10 ではこの動作が変更されている様です。

https://play.golang.org/p/CHGYhtzRsK Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS=...

Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS="windows"』だと途中でエラーになるプログラム。


osやsyscallパッケージを使っている時、こういう事があって当然なのかパッケージの実装に問題があるのか判断する規準となりそうな記述って公式になにかありますでしょうか。

https://plus.google.com/u/0/103737163485109218200/posts/gfNS2Bz4QrH?cfem=1

UNIX だと以下のコードはパスするのに Windows だとエラーになるという話。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

UNIX の場合、ファイルを開いている最中にファイルを削除するとファイルシステム上からは unlink されるけど、実際は存在していてファイルの読み込みが出来ます。そして close(2) されたタイミングで削除されます。Windows の FILE_SHARE_DELETE は一見、UNIX のそれっぽく見えるのですが動作が異なります。

例えば test.txt というファイルを作り以下のコードを実行します。

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

getchar() で処理が停止している間に別のコマンドプロンプトでファイルを消してみて下さい。まずは実行

delete1

そして削除。ファイルが消えると思いきや残っています。そしてアクセスするとエラーになります。

delete2

Windows の FILE_SHARE_DELETE の動作は UNIX のそれとは異なるのです。例えばファイルを消した後で、ファイルが存在しないつもりで処理を続行してしまうと別のエラーが発生する事になります。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

この様な OS 間の動作の違いを吸収する事は言語レベルでは出来ません。マルチプラットフォームで動作するコードを書きたいと思われるのであれば、os.Rename や os.Remove の前に Close する事をおすすめします。

ちなみに「みんなのGo言語」にも解説が書いてあります。

みんなのGo言語[現場で使える実践テクニック] みんなのGo言語[現場で使える実践テクニック]
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧大輔, 鈴木健太
技術評論社 Kindle版 / ¥2,178 (2016年09月09日)
 
発送可能時間:

Posted at by



2017/07/11


Visual Studio Code で ssh 先のファイルを編集するには、Remote VSCode を使います。

Remote VSCode - Visual Studio Marketplace

A package that implements the Textmate's 'rmate' feature for VSCode.

https://marketplace.visualstudio.com/items?itemName=rafaelmaiolla.remote-vscode
なかなか仕組みが面白かったので紹介したいと思います。Remote VSCode は、実際は Sublime TextTextmate の rmate というコマンドの派生版です。どうやって ssh 先のファイルを編集するかというと、勘の良い方であれば以下のコマンドを見れば理屈は分かるはず。
$ ssh -R 52698:127.0.0.1:52698 user@example.org rmate /path/to/the/file.txt

rmate は Remote VSCode と通信してファイルの中身をやり取りします。実際には ssh でログインした際のポートフォワード機能によりリモート側のポート 52698 番で受け待ちすると同時に rmate コマンドを起動、ローカルで Remote VSCode と通信します。

rmate のプロトコルは至って簡単で、最初にファイルセットを送ります。

open
display-name: file.txt
data-on-save: yes
re-activate: yes
token: xxxxxxxxxxxxxxxx
data: 14

This is a file
.

以降は Remote VSCode が保存する度に以下のメッセージを送ってきます。

save
token: xxxxxxxxxxxxxxxx
data: 16
This is the file

面白い事にこの rmate、なぜだか色んな言語で実装されている。

Ruby version: https://github.com/textmate/rmate
Bash version: https://github.com/aurora/rmate
Perl version: https://github.com/davidolrik/rmate-perl
Python version: https://github.com/sclukey/rmate-python
Nim version: https://github.com/aurora/rmate-nim
C version: https://github.com/hanklords/rmate.c
Node.js version: https://github.com/jrnewell/jmate

いや、そんなにいらんやろw と思いつつも golang 版が無かったので作った。おとなげない。

GitHub - mattn/gomate

README.md gomate Edit files from an ssh session in TextMate/VSCode Usage 1 gomate /path/to/the/file....

https://github.com/mattn/gomate

まぁ、ssh してリモートで vim 使えばいいじゃんって思うけど、どうしても Visual Studio Code じゃないと嫌やねんって人はどうぞ。

Posted at by



2017/06/29


今日とある場所で虫が入り込む瞬間を見た。虫といってもバグの方。それはプログラマ向けの Q&A サイトで始まった。質問の内容はこうだ。

アース渦巻香 蚊取り線香 [12時間長持ち ジャンボ50巻缶入] アース渦巻香 蚊取り線香 [12時間長持ち ジャンボ50巻缶入]

アース製薬 ヘルスケア&ケア用品 / ¥897 (¥18 / 個) (1970年01月01日)
 
発送可能時間:

文字列には0または4がだけが含まれる。文字列は 4 から始まり、例えば 440, 44, 40, 4400, 4440 など、これらは正しいとするが 404 は正しくない。今のところ、私は 0 の直後に 4 が現れるかどうかでチェックしている。これは果たして効率的だろうか。

始め僕はこの質問文をちゃんと読んでおらず、正規表現を使ってこれを実装した。

package main

import (
    "regexp"
)

func check(s stringbool {
    return regexp.MustCompile(`^4+0*$`).MatchString(s)
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

でも質問をよく見たら彼は効率的かどうかを気にしていた。確かにこのお題で正規表現は無い。僕は慌てて以下のコードを付け添えた。

package main

func check(s stringbool {
    i := 0
    r := []rune(s)
    for i = 0; i < len(r); i++ {
        if r[i] != '4' {
            break
        }
    }
    if i == 0 {
        return false
    }
    for ; i < len(r); i++ {
        if r[i] != '0' {
            return false
        }
    }
    return true
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

いずれのパッケージにも依存しておらく、おそらくちゃんと動くコードだろう。

フマキラー 蚊取り線香 ジャンボ 箱 50巻 フマキラー 蚊取り線香 ジャンボ 箱 50巻

フマキラー ヘルスケア&ケア用品 / ¥1,064 (1970年01月01日)
 
発送可能時間:

その後、周りのオーディエンスが質問した彼に「どんなケースか良く分からないな、コードを見せてくれる?」と言った。そして彼は以下のコードを見せてくれた。

package main

import (
    "fmt"
    "strings"
)

func validate(str stringbool {

    if strings.HasPrefix(str, "4") {
        for i := 0; i < len(str)-1; i++ {
            if (str[i] == '0'&& (str[i+1== '4') {
                return false
            }
        }

    } else {
        return false
    }

    return true
}

func main() {

    data := []string{"4""44""4400""4440""404""004"}
    for _, val := range data {
        fmt.Println(validate(val))
    }
}

なるほど彼が最初に言ってた通り「0 の直後に 4 が現れる事でチェック」している。一見このコードは正しそうに見える。でもこのコードは「406」の様な文字列で正しく機能しない。彼にそれを伝えたところ、彼は「@mattn -1 最初に 0 と 4 しか無いっていったじゃん」と返してきた。

僕はここで「あー、バグが混入するタイミングはここなんだ」と思った。例えばこの関数が「4 から始まり 0 が 0 個以上続く文字列をチェックする」関数だとして他のユーザに配られるとする。それを譲り受けた開発者はそれを使ってテストする。この時点でその開発者は「もちろん 406 みたいな文字列も弾いてくれる」と信じてしまうだろう。こうやってバグってのは混入するんだ、と思った。まぁ、もしかしたら彼のキーボードには 0 と 4 とリターンキーしか付いていなかったのかもしれない。

村田屋産業 防蚊 ブラック 本体サイズ:約16×17×15cm 村田屋産業 防蚊 ブラック 本体サイズ:約16×17×15cm

村田屋産業 ホーム&キッチン / ¥1,959 (1970年01月01日)
 
発送可能時間:

Posted at by