Go 言語の range は2番目の返り値に同じ参照を返す場合がある
言葉で説明するのだるいので次のコードを見てください。
package main import ( "fmt" ) func main() { array := []int{1, 2, 3} // これだと &a が同じアドレスになってしまうことがある // (range は array[i] のコピーを返す) for i, a := range array { fmt.Printf("%d: %p\n", i, &a) } // こうするか fmt.Println("----------------------------") for i := range array { a := array[i] fmt.Printf("%d: %p\n", i, &a) } // こうしないといけない fmt.Println("----------------------------") for i := range array { fmt.Printf("%d: %p\n", i, &array[i]) } }
for i, a := range ... {
とした時に &a が毎回同じアドレスになってしまうことがある、ということです(結局言葉で説明…)。
あくまで range の2番目の返り値は配列の各要素へのポインタではなく値を取得したい場合に使うらしいです。
なので以下のコードはダメ。
package main import ( "fmt" ) type MyObj struct { id int } func main() { array := []MyObj{MyObj{id: 1}, MyObj{id: 2}, MyObj{id: 3}} // id >= 2 の要素だけ抜き出す filtered := filterGreaterThan1(array) // 出力すると自分の環境だと 3 が2回表示された for _, a := range filtered { fmt.Println(a.id) } } func filterGreaterThan1(array []MyObj) []*MyObj { filtered := make([]*MyObj, 0, len(array)) for _, a := range array { if a.id > 1 { // a は同じアドレスの可能性があるため &a を持ちまわると同じ参照を指してしまうことがある filtered = append(filtered, &a) } } return filtered }
Special Thanks
for int i=0; i<len(array; i++ {
— MURAOKA Taro (@kaoriya) 2017年8月26日
a := array[i]
// ...
}
こんなのと一緒の意味で、aが再利用されているイメージ。
Subsonic に不満があったので OSS 版の Libresonic をインストールしてみた
追記
続編です。 これと組み合わせれば Subsonic でも曲をフォルダに置いたら即スキャンしてくれるようになります。
本文
ずっと Subsonic というストリーミングサーバーを使っていた。 1ドル /月 払えば Android アプリでも接続できるようになる。 概ね満足だったんだけどそれでもいくつか不満があった。
- (flac ファイル特有の現象なのかは分からないが)flac ファイルを再生すると曲が終わっても次の曲に行ってくれない
- 曲の時間が明らかに長かったりするので、終わりが正しく取得できてない?
- 音楽フォルダのスキャンが一日一回に限られている
- 曲が追加されたら一定時間後に自動的にスキャンしてほしい
- 最近のバージョンのコードはクローズド(version 6.0-beta1 以降)
これらの問題点を解決するためにLibresonic という Subsonic の OSS 版を試してみた。
ちなみに 2 に関しては解決しなかったが、スキャンを行う REST API はあるようなので、曲が追加されたら REST API 叩くスクリプトを書けば解決しそう。 *1
(追記 2017/8/27 0:30)勘違いでした。Subsonic にはありますが、現在の Libresonic にはありません
Java 8+ のインストール
$ sudo yum install java-1.8.0-openjdk $ java -version openjdk version "1.8.0_141" OpenJDK Runtime Environment (build 1.8.0_141-b16) OpenJDK 64-Bit Server VM (build 25.141-b16, mixed mode)
java-1.8.0-openjdk を入れれば自動的に Java 8 になってるはず。 なってなかったら alternatives コマンドで Java 8 を選ぶようにしてください。 以下は Java 7 と 8 が入ってる環境の図。
$ sudo alternatives --config java 2 プログラムがあり 'java' を提供します。 選択 コマンド ----------------------------------------------- 1 java-1.7.0-openjdk.x86_64 (/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.141-2.6.10.1.el7_3.x86_64/jre/bin/java) *+ 2 java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.el7_3.x86_64/jre/bin/java) Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:
Standalone バージョンのインストール
WAR を持ってきて java -jar {war} すれば動くやつです。 それを systemd で動かすようにします。
まずユーザの作成。
$ sudo useradd --system libresonic $ id libresonic uid=994(libresonic) gid=992(libresonic) groups=992(libresonic)
次に必要なディレクトリを作りつつ WAR をダウンロードしてインストール。
$ sudo mkdir /opt/libresonic $ curl -LO https://libresonic.org/release/libresonic-v6.2.war $ sudo install -o root -g root -m 644 libresonic-v6.2.war /opt/libresonic/libresonic.war $ sudo mkdir /var/libresonic/ $ sudo chown -R libresonic:libresonic /var/libresonic/
次に systemd の設定ファイルを作成。
$ vim /etc/default/libresonic JAVA_HOME=/usr/lib/jvm/jre $ vim /etc/systemd/system/libresonic.service [Unit] Description=Libresonic After=network.target [Service] Type=simple EnvironmentFile=-/etc/default/libresonic ExecStart=/usr/bin/java -jar /opt/libresonic/libresonic.war User=libresonic Group=libresonic PrivateTmp=true Restart=always [Install] WantedBy=multi-user.target
設定ファイルをロード。
$ sudo systemctl daemon-reload
ffmpeg のインストール
インストールした後、以下の手順を実行。
$ sudo mkdir /var/libresonic/transcode $ cd /var/libresonic/transcode $ ln -s /usr/bin/ffmpeg $ ls -alh lrwxrwxrwx 1 user user 15 mai 4 19:57 ffmpeg -> /usr/bin/ffmpeg
起動
いよいよ起動ですが、結構時間かかります。 HP ProLiant MicroServer (Gen8 ではない) で 74秒 ぐらい。
都度 sudo systemctl status -l libresonic
するのもだるいので、
sudo systemctl start libresonic
したら journalctl -ef -u libresonic
で起動するまでログを眺めておくといいでしょう。
$ sudo systemctl start libresonic $ journalctl -ef -u libresonic
初回は必要なファイル作成や DB の初期化などでだばーっとログが出ます。 起動したらこんなログが出ます。
2017-08-21 00:43:55.960 INFO --- o.l.player.boot.Application : Started Application in 72.08 seconds (JVM running for 74.073)
起動後 /var/libresonic
がこんな感じになってました。
$ ls -F /var/libresonic/ db/ libresonic.log libresonic.properties lucene2/ rollback.sql
Could not verify the provided CSRF token because your session was not found.
公式のインストール手順通りにインストールして java -jar libresonic.war と実行してみたのだが、Web UI でログイン後にこんなページに飛ばされてしまう。
ログファイル(/var/libresonic/libresonic.log
)を見ても特にエラーは出ていなかった。
Tomcat のインストールしたら直った?
$ sudo yum install tomcat $ sudo yum remove tomcat tomcat-lib
とかやってたら正常にログインできるようになってしまった。謎。
======================================================================================================================================================================================== Package アーキテクチャー バージョン リポジトリー 容量 ======================================================================================================================================================================================== インストール中: tomcat noarch 7.0.69-12.el7_3 updates 89 k 依存性関連でのインストールをします: apache-commons-collections noarch 3.2.1-22.el7_2 base 509 k apache-commons-daemon x86_64 1.0.13-6.el7 base 54 k apache-commons-dbcp noarch 1.4-17.el7 base 167 k apache-commons-logging noarch 1.1.2-7.el7 base 78 k apache-commons-pool noarch 1.6-9.el7 base 113 k avalon-framework noarch 4.3-10.el7 base 88 k avalon-logkit noarch 2.1-14.el7 base 87 k ecj x86_64 1:4.2.1-8.el7 base 1.4 M geronimo-jms noarch 1.1.1-19.el7 base 31 k geronimo-jta noarch 1.1.1-17.el7 base 20 k javamail noarch 1.4.6-8.el7 base 758 k log4j noarch 1.2.17-15.el7 base 443 k tomcat-el-2.2-api noarch 7.0.69-12.el7_3 updates 80 k tomcat-jsp-2.2-api noarch 7.0.69-12.el7_3 updates 93 k tomcat-lib noarch 7.0.69-12.el7_3 updates 3.8 M tomcat-servlet-3.0-api noarch 7.0.69-12.el7_3 updates 211 k xalan-j2 noarch 2.7.1-23.el7 base 1.9 M xerces-j2 noarch 2.11.0-17.el7_0 base 1.1 M xml-commons-apis noarch 1.4.01-16.el7 base 227 k xml-commons-resolver noarch 1.2-15.el7 base 108 k
このうちのどれかが必要なパッケージだったのか?*2 今となっては謎。雑。
Vim の :terminal がもたらすもの
追記
本記事を書いた後いろいろ状況が変わって :terminal コマンドが使える Vim が配布されるようになったので、 とりあえず現時点(2017/09/15)での状況を書きました。
序文
今絶賛開発中の Vim の :terminal コマンドについて書こうと思う。
:terminal コマンドを実行するとシェルが開き、Vim 上で操作することができる、というもの。 つまり実質的に Vim は端末ソフトウェアとなった。 その上 Vim script で端末上の様々な内容にアクセスしたり変更することができる。
expect コマンド、parallel-ssh のようなツールを Vim script で自作することも可能となった。 しかし、すでにそれらのコマンドがある以上 Vim プラグインで実現する必要性は薄いだろう。
Vim の :terminal がもたらすもの、それは外部コマンドとのさらなる相互運用性(interoperability)だと思う。
Vim 8 リリース時にも多数の機能が追加された。 job 機能や JSON パーサー/シリアライザーも他プロセスと通信するための大きな前進となった。 それが今度は :terminal によって端末まで内包してしまった。 つまり Vim ユーザは直接プロセスを起動する UI も手に入れた。*1
mattn さんが作った vim-fz というプラグインがある。 これは言ってみれば denite.vim(unite.vim)のような機能を外部コマンドにより実現したものだ。 これまで Vim script で実現するしかなかった部分を外部コマンドで代替することができるようになった。 vim-fz を見ると、これからは Vim プラグインの作り方も大きく変わってくる、そんな気がする。
Windows Subsystem Linux
最近 Vim 以外で嬉しいニュースもあった。 Windows Subsystem Linux(以下 WSL)がベータ版じゃなくなったことだ。
もともと自分は Linux 環境を手に入れるために MSYS2 を使っていたが、WSL が正式にサポートされたことによりちょっとの手順でサクッと Linux 環境が手に入る。
しかし今度は端末ソフトがない。 なので MSYS2 で使っていた mintty の WSL 版である wsltty という端末ソフトを仕方なく使っていた。
そこにちょうどタイミング良く Vim が端末機能を実装した。
慣れ親しんだ Vim のキーストロークでコピペができる。 ウィンドウ分割ができる。 これがどんなに嬉しいことか…
ちなみに自分はこれまでシェルを複数起動したい場合、端末を複数起動していた。 同じように tmux や screen をただの多画面でシェルを起動するだけの使い方をしていた Vim ユーザはその必要もなくなった(セッション機能は Vim には無いが…)。
ある Windows ユーザーが Linux を求める旅が終わりを告げようとしている。
Vim を初めて見たのは Linux でのことだった。
その時はなんて質素なエディタだと思ったけど、:syntax on
したら色がつくようになって、これでいいかと使い続けた。
それから約10年。
こんなに使い続けるとは思わなかった。
いつの間にかプラグインを書くようになってて、vim-jp ができてリポジトリが GitHub に移って、Vim が随分と変わった。*2 これまで Vim のことが頭おかしくなるくらい好きだったけど、頭は無事おかしくなって今までよりももっともっと Vim のことが好きになってきた気がする。
:terminal コマンドを使える Vim を手に入れる
そんな Vim の :terminal コマンドはどうすれば使えるのかというと、
まだ現状配布されている Vim で使えるようになっているものはない(はず)。
Kaoriya 版も :terminal コマンドが使えるバージョンはまだ配布されていない。
Vim の nightly build でも --enable-terminal
付きでビルドされていないので使えなかった。
まだ現状どんどん仕様が変わっている最中のため仕方ないかもしれない。 けどベータ版であっても使ってみたい!と思う人はきっといっぱいいるはず。 Windows で Vim をビルドする環境整えるのは結構大変だから、ベータ版を配布してもらえるとかなり嬉しいのですがどうでしょうかね…?(チラッ
Windows はそんな感じでビルドしんどいのでやってないけど、Linux や WSL 環境ではビルドできて(まぁ他の方も言ってる通り)Vim から普通に ssh したりできてるのを確認してます。 手順は以下の記事を参考にしてください。
私は毎回 configure にオプション付けたり更新したりめんどいので自作のビルドスクリプトを使ってビルドしてます。
そんな感じで、WSL が入ってて WSL 上で :terminal が使える Vim にパスが通っているなら、
ファイル名を指定して実行から bash --login -c "vim +terminal +only"
でとりあえず WSL 上の Vim で端末を開始した状態で起動することはできる。
ただ MSYS2 だとそんなことないんだけど、WSL からはクリップボードレジスタが使えず、コピペするには端末(この場合 cmd.exe)のコピペ機能を使うことになる。
どうにかできるものかも分からないけどどうにかしたい。
バグ報告
ちなみに使ってみたけどなんかこの挙動おかしくない? って場合はここに投稿するといいです。 現状端末機能(だけじゃないけど)に関するパッチを書いてるメインコントリビュータの方々が見てる(というか書き込んでる)ので直してもらえるはず。
ちなみに
この記事を公開して日付見て初めて気付いたのですが、明日誕生日なのでなにかください(Windows 版 GVim +terminal でもいいです)。
https://www.amazon.co.jp/registry/wishlist/2DPWFJWB4GSNY/ref=cm_sw_r_tw_ws_x_XQ3dybZ1HC14Hwww.amazon.co.jp
*1:もちろんこれまでも :!cmd のように外部コマンドを実行することはできたが、対話プログラムは無理だった
*2:http://tyru.hatenablog.com/entry/20130326/vimmers_over_vim_jp