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 -r
と readonly
は同じ動作になる。
$ 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.pem
と key.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
TCPやUDPのデータが送受信ができるコマンド。
TCP で Listen は nc -l 12345
、Connect は nc localhost 12345
にする。
UDP にする時は -u
を付ける。
tcpdump
パケットをキャプチャできるコマンド。
-t
で時刻を表示しない。
-n
でIPアドレスの名前解決をしない。
-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 -connect
で SSL/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
ip netns でブリッジを作成する
Linuxで動かしながら学ぶTCP/IPネットワーク入門 を読んでいます。 この本の中で登場した ip netns コマンドの使い方をメモしておきます。
ブリッジを作成する
「DNSがよくわかる教科書」を読んだメモ
TLD
スタブリゾルバ
- DNSクライアントとも呼ばれる
フルリゾルバ
- スタブリゾルバが指定した
問い合わせ情報(ドメイン名と種類)
が自分のキャッシュにあれば、それを返す - 問い合わせ情報がキャッシュに無いと、権威サーバの階層構造をルートサーバから、たどり名前解決する。以下は example.jp を問い合わせする例
- 委任情報や名前解決を一定時間キャッシュすることで以下を実現する
- キャッシュには
問い合わせ情報(ドメイン名と種類)
が存在しないことを表すネガティブキャッシュもある - 似ているものにフォワーダーがある
権威サーバ
- 自分のゾーンの委任情報とドメインとアドレス間の対応関係を管理する。ネームサーバとも呼ばれる
- 権威サーバでは階層化して委任することでドメインを管理する。委任された範囲を
ゾーン
と呼ぶ - ゾーンの設定内容を
ゾーンデータ
といい、リソースレコード
という単位で情報を保持する - 同じゾーンデータの権威サーバを複数台設置できる仕組みを
ゾーン転送
という - プライマリサーバをリゾルバには応答しない形にして、ゾーン転送のみ利用する方法もある。プライマリサーバの負荷軽減とセキュリティ強化ができる
- 委任した側を
親
といい、委任された側を子
という。.jp
なら、親がルートドメイン、子は jp ドメイン - 子はインターネット上にネームサーバ(ns1.example.jp など)を用意し、ゾーン内のドメイン名の対応付けと委任先(子)の情報を管理する
- 親は子のネームサーバ情報(ns1.example.jp とその IP) を自分のネームサーバに登録する
リソースレコード
便利なコマンド
- dig, drill, kdig
サンプルなど
- 以下ではリソースレコードとコマンド実行例のサンプルを載せている arw.hatenablog.jp