Go の string と range

string を range したときに返るインデックスは
文字列中でのバイト位置を表しているということを知りました。
今まで何文字目というインデックスが返るものと勘違いしてました。
忘れないようにメモしておきます。

参考

https://golang.org/doc/effective_go#for

サンプルコード

https://play.golang.org/p/0HDIL562LUe

package main

import (
    "fmt"
)

func main() {
    for pos, r := range "Aあ1" {
        fmt.Printf("character %c starts at byte position %d\n", r, pos)
    }
}

実行結果

character A starts at byte position 0
character あ starts at byte position 1
character 1 starts at byte position 4

「新しいシェルプログラミングの教科書」を読んだメモ

一時ファイル名を作る

$$ で現在のプロセスのプロセス ID を取得できる。これは一時ファイルの名前生成に利用できる。

$ tmpfile=/tmp/$$
$ echo $tmpfile
/tmp/157490

文字列の長さを得る

expr を使う。

$ expr length "pewpewpew"
9
$ foo=hello; expr length $foo
5

$変数 と "$変数" の違い

$ cat test.sh 
#!/bin/bash

list=("foo" "bar" "hello world")
for i in ${list[@]}
do
    echo $i
done

echo -----

for i in "${list[@]}"
do
    echo $i
done
$ ./test.sh 
foo
bar
hello
world
-----
foo
bar
hello world

算術式評価

  • (( )) を使う
  • カッコ内の変数の型(以下の例では a )は文字列なことに注意する
$ x=10; y=20; ((a=x+y))
$ echo $a
30
$ a=1+1
$ echo $a
1+1

読み取り専用変数

declare -rreadonly は同じ動作になる。

$ declare -r foo=bar
$ foo=pew
bash: foo: 読み取り専用の変数です
$ readonly bark=bow
$ bark=wanwan
bash: bark: 読み取り専用の変数です

整数型の変数

  • declare -i で宣言する。通常の文字列型と違い、右辺に式が書ける
  • sum=x+y のように式の中で変数を参照する時に $ は不要
$ declare -i x=50
$ declare -i y=10
$ declare -i sum=x+y
$ echo $sum
60

配列

  • ( ) を書いて、中に要素を並べると配列になる
    • 明示的に宣言するには declare -a mylist のようにする
    • list=() のように () だけだと空の配列になる
  • 要素を参照するには ${配列名[インデックス]} する
  • ${#配列名[@]} で配列の要素数を得る
  • ${配列名[@]} で全ての要素を得る
  • 配列名[インデックス]=値 で要素を変更する
  • 要素削除には unset を使う
$ dogs=(koro sophia bunta)
$ echo ${dogs[0]}
koro
$ echo ${dogs}
koro
$ echo ${#dogs[@]}
3
$ dogs[0]=KORO
$ echo ${dogs[0]}
KORO
$ echo ${dogs[@]}
KORO sophia bunta
  • 先頭に要素追加
$ dogs=(louis "${dogs[@]}")
$ echo ${dogs[@]}
louis KORO sophia bunta
  • 末尾に要素追加
$ dogs+=(mona)
$ echo ${dogs[@]}
louis KORO sophia bunta mona
  • ${配列名[@]:開始位置:終了位置} で指定の要素を取り出せる
$ echo ${dogs[@]}
louis KORO sophia bunta mona foo
$ echo ${dogs[@]:1:3}
KORO sophia bunta

連想配列

  • declare -A book のようにする
  • 使い方は基本的に配列と同じ
$ declare -A book
$ book=([author]=taro [title]=fugafuga)
$ echo ${book[title]}
fugafuga
$ echo ${book[@]}
fugafuga taro
$ echo ${#book[@]}
2
$ book[publisher]=hogehoge
$ echo ${book[@]}
hogehoge fugafuga taro

パス名展開

パス名展開はシェルが行っている。一致するファイルが無い場合は、もとの記号がそのまま出力される。

$ ls
foo.cpp  foo.txt
$ echo foo.*
foo.cpp foo.txt
$ echo boo.*
boo.*
$ ls foo.*
foo.cpp  foo.txt
$ ls boo.*
ls: 'boo.*' にアクセスできません: そのようなファイルやディレクトリはありません

文字列の切り出し

${変数名:開始位置:終了位置} で切り出せる。

$ path=/etc/systemd/
$ echo ${path:1:3}
etc

拡張子を得る

  • ${変数名#パターン} では 前方一致 したパターン部分を除いて展開される
  • # だと最短一致、 ## だと最長一致になる。拡張子を得るなら ## がよく使われる
$ path=/tmp/foo.tar.gz
$ echo ${path#*.}
tar.gz
$ echo ${path##*.}
gz

ファイル名を得る

$ echo ${path##*/}
foo.tar.gz

ディレクトリ名を得る

  • ${変数名%パターン} では 後方一致 したパターン部分を除いて展開される
  • % で最短一致させる所がポイント
$ echo ${path%/*}
/tmp

if 文

  • シェルスクリプトには boolean型 が無い。if 文ではコマンド実行結果で条件判定を行う
  • コマンド実行結果が 0 なら真と判定される
  • : は常に 0 を返す、組み込みコマンド
$ cat test.sh
#!/bin/bash

# if の直後にはコマンドが来る
# ; が無いと then もコマンドの引数として認識されてしまう
if ls > /dev/null; then
    echo foo
fi

if [ "foo" = "foo" ]; then
    # noop する時は : を使う
    # : は常に 0 を返す、組み込みコマンド
    :
fi

# 常に真になる if 文
if : ; then
    echo true
fi
$ ./test.sh 
foo
true

test コマンド

  • 文字列や数値の比較、ファイルの存在などを判定するコマンド
  • if 文と組み合わせることが多い
  • [test は基本的に同じ
$ test "foo" = "bar"; echo $?
1
$ [ "foo" = "bar" ]; echo $?
1
$ [ "foo" = "foo" ]; echo $?
0
$ [ "foo" = "foo" -a 100 -ge 10 ]; echo $?
0

標準出力と標準エラー出力をまとめる

&> を使うと便利。

$ ls foo
ls: 'foo' にアクセスできません: そのようなファイルやディレクトリはありません
$ ls foo &> result

ヒアストリング

$ cat <<< hello
hello

サブシェル

() で囲われた処理は、現在のシェルの子プロセスとして新しく起動されたシェルで実行される。 () 内での環境変数などの変更は現在(親)のシェルに影響を与えない。

$ export foo=bar; (foo=pewpewpew); echo $foo
bar
$ pwd; (cd /); pwd
/home/fuga/test
/home/fuga/test

コマンドのグループ化

{} または () で複数のコマンドを1つのコマンドとしてまとめることができる。

$ { ls /dev/pts/; echo hello; } > out
$ cat out 
0
1
ptmx
hello

trap によるシグナルの補足

$ cat test.sh 
#!/bin/bash

handler()
{
    echo "catch INT"
    # exit しないとシェルスクリプトが最後まで実行される
    exit
}

trap handler INT

echo start
sleep 10
echo end
$ ./test.sh 
start
^Ccatch INT

grep コマンド

  • -v で検索パターンにマッチしなかった行が出力される
$ cat << EOF | grep -v -E "^a"
> aaa
> abc
> zzz
> bbb
> baa
> EOF
zzz
bbb
baa
  • -o で検索パターンにマッチした部分だけを出力する
$ cat << EOF | grep -o -E "foo|bar"
> aaafooaaa
> bbbddd
> aaa
> bbbbarbbb
> EOF
foo
bar

env コマンド

環境変数を一時的に変更してコマンドを実行する。bash の場合は 変数名=値 コマンド だけでも同じ動作になる。

$ cat main.go 
package main

import (
   "fmt"
   "os"
)

func main() {
    fmt.Println(os.Getenv("foo"))
}
$ foo=hello
$ env foo=bar go run main.go 
bar
$ foo=bar go run main.go 
bar
$ echo $foo
hello

Bats でのテスト

Bats のインストール。

$ sudo apt install bats
$ bats --version
Bats 1.2.0-dev

テストを記述するファイルの拡張子は bats にする。

$ cat mul.sh 
#!/bin/bash
echo $(($1 * $2))
$ cat mul.bats 
PATH="${BATS_TEST_DIRNAME}:$PATH"

@test 'mul test' {
  run mul.sh 2 3
  [[ $status -eq 0 ]]
  [[ $output == 6 ]]
}
$ bats mul.bats 
 ✓ mul test

1 test, 0 failures

mkcert と golang の net/http を使い、ローカル環境に HTTPS サーバを立てる

mkcert をインストール(ビルド)する

https://github.com/FiloSottile/mkcert#linux

$ sudo apt install libnss3-tools
$ git clone https://github.com/FiloSottile/mkcert && cd mkcert
$ go build -ldflags "-X main.Version=$(git describe --tags)"

mkcert で証明書とキーを作成する

$ mkcert -install
The local CA is already installed in the system trust store! 👍
The local CA is already installed in the Firefox and/or Chrome/Chromium trust store! 👍

$ mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 ::1

Created a new certificate valid for the following names 📜
 - "127.0.0.1"
 - "::1"

The certificate is at "cert.pem" and the key at "key.pem" ✅

It will expire on 18 October 2023 🗓

$ ls *.pem
cert.pem  key.pem

HTTPS サーバのサンプルコード

作成した cert.pemkey.pem のパスを http.ListenAndServeTLS に渡します。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Protocol: %s\n", r.Proto)
    })
    if err := http.ListenAndServeTLS(":5000", "cert.pem", "key.pem", nil); err != nil {
        log.Fatal(err)
    }
}

動作確認

$ curl https://127.0.0.1:5000
Protocol: HTTP/2.0

参考にさせていただいたサイト

Linux のネットワーク関連コマンド

iptables と dnsmasq も後で追記したいです。

nc

TCPUDPのデータが送受信ができるコマンド。
TCP で Listen は nc -l 12345 、Connect は nc localhost 12345 にする。
UDP にする時は -u を付ける。

tcpdump

パケットをキャプチャできるコマンド。
-t で時刻を表示しない。
-nIPアドレスの名前解決をしない。
-i でキャプチャするインタフェースを指定する。-i any だと全てのインタフェースをキャプチャする。
-A でデータをASCIIで表示する。

別のターミナルから ping 8.8.8.8 -c 2 を実行し、ICMP パケットをキャプチャする例

$ sudo tcpdump -tn -i any icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
IP 192.168.0.10 > 8.8.8.8: ICMP echo request, id 5, seq 1, length 64
IP 8.8.8.8 > 192.168.0.10: ICMP echo reply, id 5, seq 1, length 64
IP 192.168.0.10 > 8.8.8.8: ICMP echo request, id 5, seq 2, length 64
IP 8.8.8.8 > 192.168.0.10: ICMP echo reply, id 5, seq 2, length 64
^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel

UDP でポートを指定してキャプチャする例

$ sudo tcpdump -tnlA -i any "udp and port 12345"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
IP 127.0.0.1.37527 > 127.0.0.1.12345: UDP, length 4
E.. ..@.@.?A..........09....foo

IP 127.0.0.1.37527 > 127.0.0.1.12345: UDP, length 6
E..".z@.@.<O..........09...!hello

^C
2 packets captured
4 packets received by filter
0 packets dropped by kernel

lsof

プロセスが開いているファイルを表示できるコマンド。
-i でソケットだけを表示できる。-i:ポート番号 で絞り込みも可能。
-p でプロセスを指定できる。

$ lsof -i
COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      1298 vagrant    3u  IPv4  28252      0t0  TCP *:12345 (LISTEN)
nc      1339 vagrant    3u  IPv4  29509      0t0  TCP *:9999 (LISTEN)
$ lsof -i:12345
COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      1298 vagrant    3u  IPv4  28252      0t0  TCP *:12345 (LISTEN)
$ lsof -p 1339
COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
nc      1339 vagrant  cwd    DIR    8,3     4096 5638785 /home/vagrant
nc      1339 vagrant  rtd    DIR    8,3     4096       2 /
nc      1339 vagrant  txt    REG    8,3    43664 4325590 /usr/bin/nc.openbsd
nc      1339 vagrant  mem    REG    8,3  2029224 4328519 /usr/lib/x86_64-linux-gnu/libc-2.31.so
nc      1339 vagrant  mem    REG    8,3   101320 4328538 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
nc      1339 vagrant  mem    REG    8,3    96728 4328287 /usr/lib/x86_64-linux-gnu/libbsd.so.0.10.0
nc      1339 vagrant  mem    REG    8,3   191472 4328477 /usr/lib/x86_64-linux-gnu/ld-2.31.so
nc      1339 vagrant    0u   CHR  136,0      0t0       3 /dev/pts/0
nc      1339 vagrant    1u   CHR  136,0      0t0       3 /dev/pts/0
nc      1339 vagrant    2u   CHR  136,0      0t0       3 /dev/pts/0
nc      1339 vagrant    3u  IPv4  29509      0t0     TCP *:9999 (LISTEN)

openssl s_client

openssl s_client -connectSSL/TLS 接続に関する情報を表示できる。

$ echo -e 'HEAD / HTTP/1.1\r\nHost: www.hatena.ne.jp\r\n\r\n' | openssl s_client -connect www.hatena.ne.jp:443 -quiet
depth=2 C = US, O = Amazon, CN = Amazon Root CA 1
verify return:1
depth=1 C = US, O = Amazon, OU = Server CA 1B, CN = Amazon
verify return:1
depth=0 CN = www.hatena.ne.jp
verify return:1
HTTP/1.1 200 OK
Date: Tue, 13 Jul 2021 05:29:25 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 291348
Connection: keep-alive
Server: nginx
Vary: Accept-Encoding
Vary: Accept-Encoding
ETag: "47214-Bl7Cq7MeA3CEtqJhoXukBQe/L7k"
Vary: Accept-Encoding
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block

^C

「DNSがよくわかる教科書」を読んだメモ

TLD

  • TLD には ccTLD(Country code top-level domain) と gTLD(Generic top-level domain) がある
    • ccTLD: .jp や .uk などの国や地域に割り当てられる
    • gTLD: 国や地域の制限は無い。 .com や .net など。ただし .edu や .gov などには利用制限がある

スタブリゾル

  • DNSクライアントとも呼ばれる

フルリゾル

  • スタブリゾルバが指定した 問い合わせ情報(ドメイン名と種類) が自分のキャッシュにあれば、それを返す
  • 問い合わせ情報がキャッシュに無いと、権威サーバの階層構造をルートサーバから、たどり名前解決する。以下は example.jp を問い合わせする例
    1. ルートネームサーバに example.jp を聞いて、a.dns.jp に聞いてと返って来る
    2. a.dns.jp に example.jp を聞いて、ns1.example.jp に聞いてと返って来る
    3. ns1.example.jp に example.jp を聞いて、192.0.2.10 が返って来る
  • 委任情報や名前解決を一定時間キャッシュすることで以下を実現する
    • フルリゾルバ、権威サーバの負荷軽減
    • 問い合わせから応答までの時間短縮
    • ネットワークトラフィックの軽減
  • キャッシュには 問い合わせ情報(ドメイン名と種類) が存在しないことを表すネガティブキャッシュもある
  • 似ているものにフォワーダーがある

権威サーバ

  • 自分のゾーンの委任情報とドメインとアドレス間の対応関係を管理する。ネームサーバとも呼ばれる
  • 権威サーバでは階層化して委任することでドメインを管理する。委任された範囲を ゾーン と呼ぶ
  • ゾーンの設定内容を ゾーンデータ といい、 リソースレコード という単位で情報を保持する
  • 同じゾーンデータの権威サーバを複数台設置できる仕組みを ゾーン転送 という
  • プライマリサーバをリゾルバには応答しない形にして、ゾーン転送のみ利用する方法もある。プライマリサーバの負荷軽減とセキュリティ強化ができる
  • 委任した側を といい、委任された側を という。.jp なら、親がルートドメイン、子は jp ドメイン
  • 子はインターネット上にネームサーバ(ns1.example.jp など)を用意し、ゾーン内のドメイン名の対応付けと委任先(子)の情報を管理する
  • 親は子のネームサーバ情報(ns1.example.jp とその IP) を自分のネームサーバに登録する

リソースレコード

  • リソースレコードは次の要素から構成される
    • ドメイン名, TTL, クラス, タイプ, データ
    • 例: jprs.jp, 300, IN, A, 117.104.133.164

便利なコマンド

  • dig, drill, kdig

サンプルなど

  • 以下ではリソースレコードとコマンド実行例のサンプルを載せている arw.hatenablog.jp

iPad から Ubuntu の Vino に接続する

Vino を使うと、実際のデスクトップ画面に接続できます。

環境

Ubuntu の設定

設定 > 共有 > 画面共有 から Vino を有効にしてください。
Vino が起動していないときは /usr/lib/vino/vino-server で手動起動できます。

iPad の設定

VNC クライアントには VNC Viewer を使いました。
接続先ポートは 192.168.1.1::5900 のように :: で指定しないとダメでした。

参考にさせていただいたサイト