ラベル bash の投稿を表示しています。 すべての投稿を表示
ラベル bash の投稿を表示しています。 すべての投稿を表示

2019年11月27日水曜日

シェル芸で dmesg に分かり易いタイムスタンプを付加してみたが・・・

ふと思い付きで、dmesg の左端のタイムスタンプを分かり易くできるのでは、と閃いた。。。
[root@hoge ~]# (date -d"$(uptime -s)" +%s;dmesg)|awk -F\[ 'BEGIN {getline b} {print strftime("%F %T",b+$2),$0}'
...
2019-11-27 21:46:52 [52976.500320] IPv6: ADDRCONF(NETDEV_UP): wlp4s0: link is not ready
2019-11-27 21:47:10 [52994.337872] [drm:intel_pipe_update_end [i915]] *ERROR* Atomic update failure on pipe B (start=92367 end=92368) time 177 us, min 1073, max 1079, scanline start 1069, end 1077
2019-11-27 21:47:11 [52995.637878] [drm:intel_pipe_update_end [i915]] *ERROR* Atomic update failure on pipe B (start=92445 end=92446) time 174 us, min 1073, max 1079, scanline start 1068, end 1079
しかしながら、そもそも dmesg のオプションに、所望の機能があるのではないかと、ヘルプを見てみたら。。。ありました。
[root@hoge ~]# dmesg -e
...
[11月27 21:46] IPv6: ADDRCONF(NETDEV_UP): wlp4s0: link is not ready
[11月27 21:47] [drm:intel_pipe_update_end [i915]] *ERROR* Atomic update failure on pipe B (start=92367 end=92368) time 177 us, min 1073, max 1079, scanline start 1069, end 1077
[  +1.300006] [drm:intel_pipe_update_end [i915]] *ERROR* Atomic update failure on pipe B (start=92445 end=92446) time 174 us, min 1073, max 1079, scanline start 1068, end 1079
知らなかった。/var/log/messages を見て、対応する行を探してました。この機会に、頭のノートにメモっておこう。

2019年11月17日日曜日

bash の [[ の中では -a は使えない

長年 bash を使っていて、気がつきませんでしたが、[[ の中では -a (AND条件指定) は使えないことを知りました。
[root@hoge ~]# A="hoge"
[root@hoge ~]# B="fuga"
[root@hoge ~]# [[ $A = ho* -a $B = fu* ]]
bash: 条件式に構文エラーがあります
bash: `-a' 周辺に構文エラーがあります
[root@hoge ~]# [[ $A = ho* && $B = fu* ]]
[root@hoge ~]# echo $?
0
このように -a は使えないが、その代わりに && が使えるようです。
[root@hoge ~]# help [[
[[ ... ]]: [[ expression ]]
    条件式のコマンドを実行します。

    条件式 EXPRESSION の評価結果に基づいて 0 または 1 を返します。
    条件式は test 組み込み関数と同じ優先順位で組み合わされます。また、
    次の演算子とも組み合わされます。

      ( EXPRESSION )    EXPRESSION の値を返します
      ! EXPRESSION              EXPRESSION が true の時 false を返します。それ
                                以外は false を返します
      EXPR1 && EXPR2    EXPR1 および EXPR2 の両方が true の時 true を返します。
        それ以外は false を返します。
      EXPR1 || EXPR2    EXPR1 および EXPR2 のいずれかが true の時 true を返し
        ます。それ以外は false を返します。

    `==' および `!=' 演算子が使用された場合、演算子の右側の文字列をパターンと
    した左側の文字列に対するパターン一致処理が行われます。
    `=~' 演算子が使用された場合、演算子の右側の文字列が正規表現として扱われま
    す。

    && および || 演算子は EXPR1 で式の値を決定するのに十分な場合は EXPR2 を
    評価しません。

    終了ステータス:
    EXPRESSION の値に基づいて 0 または 1 を返します。
以上、あたまのノートにメモ。

2019年7月21日日曜日

bash でシグナル trap 中に更にシグナルを受けたらどうなるか?

bash でシグナル trap 中に更にシグナルを受けたらどうなるか? を確認したいと思い、実験してみました。
#!/bin/bash

SELF_PID=$$

sig_HUP_handler() {
        echo caught SIGHUP
        kill -TERM $SELF_PID
}

trap "echo caught SIGTERM ; exit 1" SIGTERM
trap "sig_HUP_handler     ; exit 2" SIGHUP

kill -HUP $SELF_PID

exit 0
このスクリプトを実行して確認してみたら、終了コードが 2 でした。つまり、SIGTERM に対する trap は実行されませんでした。
[root@hoge ~]# ./trap_test.bash 
caught SIGHUP
[root@hoge ~]# echo $?
2
[root@hoge ~]# strace ./trap_test.bash 
...
kill(9607, SIGHUP)                      = 0
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=9607, si_uid=0} ---
rt_sigreturn({mask=[]})                 = 0
rt_sigprocmask(SIG_BLOCK, [HUP], [], 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [HUP], 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [HUP], 8) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0264e7a000
write(1, "caught SIGHUP\n", 14caught SIGHUP
)         = 14
kill(9607, SIGTERM)                     = 0
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=9607, si_uid=0} ---
rt_sigreturn({mask=[HUP]})              = 0
rt_sigprocmask(SIG_SETMASK, [HUP], NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
exit_group(2)                           = ?
+++ exited with 2 +++
[root@hoge ~]# 
strace をかけてみると、このように SIGTERM は確かに割り込んでいます。次に、exit 2 を行わないようにして、実験してみました。
[root@hoge ~]# diff trap_test.bash trap_test2.bash 
11c11
< trap "sig_HUP_handler     ; exit 2" SIGHUP
---
> trap "sig_HUP_handler     # exit 2" SIGHUP
[root@hoge ~]# ./trap_test2.bash 
caught SIGHUP
caught SIGTERM
[root@hoge ~]# echo $?
1
終了コードは 1 になりました。つまり、SIGHUP に対する trap 完了後に、SIGTERM に対する trap が実行されました。
なお、上記の実験環境は、CentOS7 bash-4.2.46-31.el7.x86_64 です。同じスクリプトを RHEL4 より古い環境で動かすと bash が segfault してしまいます。当時は、想定されていなかったのでしょうね。 シグナルには気をつけよう!

2019年6月22日土曜日

bash のヒアドキュメントは、一時ファイルを作成する

ディスクの空きスペースが無くなった際、手持ちの bash スクリプトから次のようなメッセージが出ていました。
myscript: cannot create temp file for here-document: No space left on device
そのスクリプトではヒアドキュメントを使っており、メッセージから察するに、bash のヒアドキュメントでは、背後で一時ファイルが作られるらしい、と認識しました。考えてみると自然な実装であり「まあそうなるわなあ」という感想。
いちおう確かめるために、そのスクリプトのエッセンスを抽出して実験してみました。
#!/bin/bash
perl <<'PERL'
exit 0
PERL
[root@hoge ~]# uname -a
Linux hoge 3.10.0-957.12.2.el7.x86_64 #1 SMP Tue May 14 21:24:32 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# rpm -q bash
bash-4.2.46-31.el7.x86_64
[root@hoge ~]# diff -c test1 test2
*** test1 2019-06-21 23:38:42.812280355 +0900
--- test2 2019-06-21 23:39:56.797172798 +0900
***************
*** 1,4 ****
  #!/bin/bash
! perl <<'PERL'
  exit 0
! PERL
--- 1,4 ----
  #!/bin/bash
! perl -e '
  exit 0
! '
[root@hoge ~]# strace -o /tmp/strace.out -f ./test1   ※ヒアドキュメントを使用した場合
[root@hoge ~]# strace -o /tmp/strace.out2 -f ./test2
[root@hoge ~]# grep /tmp/ /tmp/strace.out
86704 open("/tmp/sh-thd-1561087024", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600) = 3
86704 open("/tmp/sh-thd-1561087024", O_RDONLY) = 4
86704 unlink("/tmp/sh-thd-1561087024")  = 0
[root@hoge ~]# grep /tmp/ /tmp/strace.out2
※ヒアドキュメントを使用しない場合は出力なし
[root@hoge ~]# wc -l /tmp/strace.out*
  402 /tmp/strace.out
  393 /tmp/strace.out2
  795 合計
strace の出力から、bash のヒアドキュメントは、一時ファイルを作成すると確認できました。この例の場合は、ヒアドキュメントをやめて perl -e '...' と書き換えれば、一時ファイル作成を避けることができました。全体のシステムコール数も減りました。
せこいようですが、「塵も積もれば山となる」という場合もあるものと思います。

2019年5月6日月曜日

シェルスクリプトでファイルサイズを取得するベストな方法は?

シェルスクリプトでファイルサイズを取得するのに、今まで次のように書いていて、常套句と思っていました。
[root@hoge tmp]# uname -a
Linux hoge 3.10.0-957.5.1.el7.x86_64 #1 SMP Fri Feb 1 14:54:57 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge tmp]# ls -l /etc/services 
-rw-r--r-- 1 root root 670293 Jun  7  2013 /etc/services
[root@hoge tmp]# declare -i fsize=`wc -c /etc/services`
bash: declare: 670293 /etc/services: division by 0 (error token is "/services")
[root@hoge tmp]# echo $fsize

[root@hoge tmp]# wc -c /etc/services 
670293 /etc/services
[root@hoge tmp]# declare -i fsize=`wc -c /etc/services | gawk '{print $1}'`
[root@hoge tmp]# echo $fsize
670293
gawk まで呼び出すのがイマイチだなとは思っていたのですが、本日ふと思い立ち、調べてみたら先人の方が居られました。
https://ameblo.jp/archive-redo-blog/entry-10196055325.html
なるほど標準入力にリダイレクトすれば1コマンドで済むのですね。賢い方法と思いました。
[root@hoge tmp]# declare -i fsize=`wc -c < /etc/services`                    
[root@hoge tmp]# echo $fsize
670293
これからは、このパターンを使おうと思います。
ところで、wc というコマンド名からは、ファイルを全部読んでファイルサイズを得る動きをしそうに見えますが、そこは賢い実装になっており、-c オプション指定だと fstat(2) システムコールが利用され、ファイルを全部読み出すなどという非効率な振舞いにはならないようです。
[root@hoge tmp]# strace wc -c < /etc/services
execve("/usr/bin/wc", ["wc", "-c"], [/* 46 vars */]) = 0
brk(NULL)                               = 0x1e76000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa4cf32f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=90399, ...}) = 0
mmap(NULL, 90399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa4cf318000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340$\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2151672, ...}) = 0
mmap(NULL, 3981792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa4ced42000
mprotect(0x7fa4cef04000, 2097152, PROT_NONE) = 0
mmap(0x7fa4cf104000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c2000) = 0x7fa4cf104000
mmap(0x7fa4cf10a000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa4cf10a000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa4cf317000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa4cf315000
arch_prctl(ARCH_SET_FS, 0x7fa4cf315740) = 0
mprotect(0x7fa4cf104000, 16384, PROT_READ) = 0
mprotect(0x608000, 4096, PROT_READ)     = 0
mprotect(0x7fa4cf330000, 4096, PROT_READ) = 0
munmap(0x7fa4cf318000, 90399)           = 0
brk(NULL)                               = 0x1e76000
brk(0x1e97000)                          = 0x1e97000
brk(NULL)                               = 0x1e97000
fstat(0, {st_mode=S_IFREG|0644, st_size=670293, ...}) = 0
lseek(0, 0, SEEK_CUR)                   = 0
lseek(0, 0, SEEK_END)                   = 670293
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa4cf32e000
write(1, "670293\n", 7670293
)                 = 7
close(0)                                = 0
close(1)                                = 0
munmap(0x7fa4cf32e000, 4096)            = 0
close(2)                                = 0https://ameblo.jp/archive-redo-blog/entry-10196055325.html
exit_group(0)                           = ?
+++ exited with 0 +++
[root@hoge tmp]# 
ということで、これがベストな方法に思いましたが、他にあるでしょうかね?
もちろん Perl/Python/Ruby等 の汎用スクリプト言語を呼べば同様のことが出来るけど、軽量に行うには wc -c < file がベストなのではと思っています。

2019-07-24追記
stat -c %s というのもありますが、当然 -c %s を解釈する処理の分だけ遅いに違いないと思っていましたが、一応実験してみました。
[root@hoge ~]# perf stat stat -c %s /etc/services
670293

 Performance counter stats for 'stat -c %s /etc/services':

          0.728356      task-clock (msec)         #    0.749 CPUs utilized          
                 2      context-switches          #    0.003 M/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
               249      page-faults               #    0.342 M/sec                  
         2,528,856      cycles                    #    3.472 GHz                    
         1,292,306      instructions              #    0.51  insn per cycle         
           252,891      branches                  #  347.208 M/sec                  
            11,528      branch-misses             #    4.56% of all branches        

       0.000972916 seconds time elapsed

[root@hoge ~]# perf stat wc -c < /etc/services
670293

 Performance counter stats for 'wc -c':

          0.599273      task-clock (msec)         #    0.705 CPUs utilized          
                 2      context-switches          #    0.003 M/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
               219      page-faults               #    0.365 M/sec                  
         2,077,769      cycles                    #    3.467 GHz                    
         1,043,855      instructions              #    0.50  insn per cycle         
           202,792      branches                  #  338.397 M/sec                  
            10,808      branch-misses             #    5.33% of all branches        

       0.000849638 seconds time elapsed

2018年3月10日土曜日

双子素数の探査

双子素数に興味を持ち、ちょっと一筆。遅いですが手っ取り早くbash+factorコマンドで書いてみました。
#!/bin/bash
LANG=C
declare -i n
declare -i m
declare -a a
declare -a b
for ((n=1001;n<10000;n+=2))
do
    a=(`factor $n`)
    if [ ${a[1]} = $n ] ; then
        let m=n+2
        b=(`factor $m`)
        if [ ${b[1]} = $m ] ; then
            echo $n $m
        fi
    fi
done
4桁の双子素数を表示します。以下、実行結果です。170組もあるとは思いませんでした。意外と多かった。
[root@hoge ~]# time ./twinprimes 
1019 1021
1031 1033
1049 1051
1061 1063
1091 1093
1151 1153
1229 1231
1277 1279
1289 1291
1301 1303
1319 1321
1427 1429
1451 1453
1481 1483
1487 1489
1607 1609
1619 1621
1667 1669
1697 1699
1721 1723
1787 1789
1871 1873
1877 1879
1931 1933
1949 1951
1997 1999
2027 2029
2081 2083
2087 2089
2111 2113
2129 2131
2141 2143
2237 2239
2267 2269
2309 2311
2339 2341
2381 2383
2549 2551
2591 2593
2657 2659
2687 2689
2711 2713
2729 2731
2789 2791
2801 2803
2969 2971
2999 3001
3119 3121
3167 3169
3251 3253
3257 3259
3299 3301
3329 3331
3359 3361
3371 3373
3389 3391
3461 3463
3467 3469
3527 3529
3539 3541
3557 3559
3581 3583
3671 3673
3767 3769
3821 3823
3851 3853
3917 3919
3929 3931
4001 4003
4019 4021
4049 4051
4091 4093
4127 4129
4157 4159
4217 4219
4229 4231
4241 4243
4259 4261
4271 4273
4337 4339
4421 4423
4481 4483
4517 4519
4547 4549
4637 4639
4649 4651
4721 4723
4787 4789
4799 4801
4931 4933
4967 4969
5009 5011
5021 5023
5099 5101
5231 5233
5279 5281
5417 5419
5441 5443
5477 5479
5501 5503
5519 5521
5639 5641
5651 5653
5657 5659
5741 5743
5849 5851
5867 5869
5879 5881
6089 6091
6131 6133
6197 6199
6269 6271
6299 6301
6359 6361
6449 6451
6551 6553
6569 6571
6659 6661
6689 6691
6701 6703
6761 6763
6779 6781
6791 6793
6827 6829
6869 6871
6947 6949
6959 6961
7127 7129
7211 7213
7307 7309
7331 7333
7349 7351
7457 7459
7487 7489
7547 7549
7559 7561
7589 7591
7757 7759
7877 7879
7949 7951
8009 8011
8087 8089
8219 8221
8231 8233
8291 8293
8387 8389
8429 8431
8537 8539
8597 8599
8627 8629
8819 8821
8837 8839
8861 8863
8969 8971
8999 9001
9011 9013
9041 9043
9239 9241
9281 9283
9341 9343
9419 9421
9431 9433
9437 9439
9461 9463
9629 9631
9677 9679
9719 9721
9767 9769
9857 9859
9929 9931

real 0m4.132s
user 0m2.390s
sys 0m1.903s
[root@hoge ~]#  ./twinprimes | wc -l
170

2018年1月12日金曜日

bashのhistory設定

コマンド実行時刻を bash の history で確認したい場合があり、少し設定を行っているのですが、忘れてしまうので備忘録。
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
 . /etc/bashrc
fi

unset HISTCONTROL
HISTSIZE=20000
HISTTIMEFORMAT=" %F %T  "
このように .bashrc に設定しています。20000という数字は経験値で、10000では足りなかった経験があるため、倍の値を採用しています。それと、細かいですが、%Tのうしろは2スペースにしておかないと、見ずらいと思います。
[root@hoge ~]# history
...
 1210   2018-01-13 18:19:49  history
[root@hoge ~]# 

2017年12月15日金曜日

bashスクリプトの if 文で ==

自作 bash スクリプトの if 文で、== で書いてしまっていた箇所があり、間違いではないか(正しく動作していないのでは?)という疑念を持ち調べたところ、bash の [ ] の中では、== も受け付けてくれると知りました。でもまあ、疑惑のタネになると思うので、旧来のBシェルの記述に従って == の箇所は = に直しました。
man bash より抜粋。
...
       string1 == string2
       string1 = string2
              True if the strings are equal.  = should be used with the test command for POSIX conformance.
...
bash 組み込みの help だと、man のような記述はありませんでした。
[root@hoge ~]# help test
test: test [expr]
    Evaluate conditional expression.
...

      -n STRING
         STRING      True if string is not empty.
    
      STRING1 = STRING2
                     True if the strings are equal.
      STRING1 != STRING2
                     True if the strings are not equal.
...
なお、test コマンド(bash 組み込みではないほう)の man だと記述はありませんが、== も受け付けるようです。
...
       -z STRING
              the length of STRING is zero

       STRING1 = STRING2
              the strings are equal

       STRING1 != STRING2
...
[root@hoge ~]# /usr/bin/test "a" == "b" ; echo $?
1
[root@hoge ~]# /usr/bin/test "a" == "a" ; echo $?
0
[root@hoge ~]# /usr/bin/[ "a" == "a" ] ; echo $?
0
[root@hoge ~]# /usr/bin/[ "a" == "b" ] ; echo $?
1
[root@hoge ~]# ls -li /usr/bin/test /usr/bin/[
2296639 -rwxr-xr-x. 1 root root 41496 Nov  6  2016 /usr/bin/[
2296722 -rwxr-xr-x. 1 root root 37328 Nov  6  2016 /usr/bin/test
[root@hoge ~]# rpm -qlv coreutils | egrep "/usr/bin/(\[|test)"
-rwxr-xr-x    1 root    root                    41496 Nov  6  2016 /usr/bin/[
-rwxr-xr-x    1 root    root                    37328 Nov  6  2016 /usr/bin/test
ちなみに、/usr/bin/test と /usr/bin/[ の実体は同じかと思ってたのですが、ls -li の結果は食い違ってました。いったい何なんだろう。環境は CentOS7 。

2017年12月9日土曜日

シェルスクリプトで端末から実行された場合だけメッセージを出したい

とあるシェルスクリプトを書いていて、端末から実行された場合だけメッセージを出したいと思いました。
tty コマンドで制御端末があるかどうか確認してから、/dev/tty へ書き込めば良いものとは思いましたが、ネット上に用例がないか調べたところ、ありました。AIX ではありますが。。
https://www.ibm.com/support/knowledgecenter/ja/ssw_aix_71/com.ibm.aix.cmds5/tty.htm
これを参考に、次のようなコードを書きました。
...
if [ $? -ne 0 ] && tty -s ; then
        echo "..." >/dev/tty
fi
...
なぜ、こうしたいのか? 標準エラーではダメなのか? と思われるかもしれませんが、世の中いろいろありますね。
ちょっと、ここには書けませんが。

2017年9月18日月曜日

何日前に作成されたファイルか調べる方法

ときどきですが、何日前に作成されたファイルなのかを、知りたい場合があります。
正確に知る必要がなければ、頭の中でだいたいの計算をしたりしますが、bashとdateコマンドで次のようにして計算できるようです。備忘録。
[root@hoge ~]# ls -l anaconda-ks.cfg
-rw-------. 1 root root 1125 May  2  2015 anaconda-ks.cfg
[root@hoge ~]# declare -i d_diff=($(date -d now +%s)-$(date -r ./anaconda-ks.cfg +%s))/24/3600
[root@hoge ~]# echo $d_diff
870
[root@hoge ~]#

2017年1月27日金曜日

シェルスクリプトで時刻をミリ秒まで出したい

シェルスクリプトで、ログの時刻をミリ秒まで出したい箇所があり、date コマンドでは出せないという思い込み (UNIXの古い経験) から、perl で小さなツール (gettimeofday を呼ぶ程度) を書きました。そして、いちおう動くようになった、そのあとに、date コマンドで出せることを知りました。orz
最初のリサーチは大事ですね。
[root@hoge tmp]# date +"%F %T 0.%3N" ; ./mydate.pl ; date +"%F %T 0.%3N" ; ./mydate.pl
2017-01-27 07:53:22 0.131
2017-01-27 07:53:22 0.133
2017-01-27 07:53:22 0.133
2017-01-27 07:53:22 0.135
[root@hoge tmp]# /usr/bin/time -v date +"%F %T 0.%3N"
2017-01-27 07:53:36 0.230
 Command being timed: "date +%F %T 0.%3N"
 User time (seconds): 0.00
 System time (seconds): 0.00
 Percent of CPU this job got: 93%
 Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.00
 Average shared text size (kbytes): 0
 Average unshared data size (kbytes): 0
 Average stack size (kbytes): 0
 Average total size (kbytes): 0
 Maximum resident set size (kbytes): 724
 Average resident set size (kbytes): 0
 Major (requiring I/O) page faults: 0
 Minor (reclaiming a frame) page faults: 228
 Voluntary context switches: 1
 Involuntary context switches: 1
 Swaps: 0
 File system inputs: 0
 File system outputs: 8
 Socket messages sent: 0
 Socket messages received: 0
 Signals delivered: 0
 Page size (bytes): 4096
 Exit status: 0
[root@hoge tmp]# /usr/bin/time -v ./mydate.pl 
2017-01-27 07:53:46 0.445
 Command being timed: "./mydate.pl"
 User time (seconds): 0.00
 System time (seconds): 0.00
 Percent of CPU this job got: 100%
 Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.00
 Average shared text size (kbytes): 0
 Average unshared data size (kbytes): 0
 Average stack size (kbytes): 0
 Average total size (kbytes): 0
 Maximum resident set size (kbytes): 2300
 Average resident set size (kbytes): 0
 Major (requiring I/O) page faults: 0
 Minor (reclaiming a frame) page faults: 634
 Voluntary context switches: 1
 Involuntary context switches: 1
 Swaps: 0
 File system inputs: 0
 File system outputs: 8
 Socket messages sent: 0
 Socket messages received: 0
 Signals delivered: 0
 Page size (bytes): 4096
 Exit status: 0
[root@hoge tmp]# time date +"%F %T 0.%3N"
2017-01-27 07:56:18 0.292

real 0m0.001s
user 0m0.000s
sys 0m0.001s
[root@hoge tmp]# time ./mydate.pl 
2017-01-27 07:56:30 0.545

real 0m0.002s
user 0m0.001s
sys 0m0.001s
[root@hoge tmp]# 
このように、当然、perl で書いたら重い (と言ったって2ミリくらいだけど) ですね。手持ちの新旧VM環境を調べたところ、現在の RHEL/CentOS系 (RHEL3 以降) なら %N を使えるようです。というわけで、せっかく書いた mydate.pl は、お蔵入り。

2017-02-02追記
お手軽に性能計測と言えば、昔から time コマンドを使ってましたが、最近は perf stat が使えるので、備忘録です。
time コマンドよりも精細な計測が出来るので、今回のように処理時間が極く短いモノを測る場合は、こちらを利用すると良いようです。
[root@hoge tmp]# perf stat ./mydate.pl 
2017-02-02 03:21:50 0.747

 Performance counter stats for './mydate.pl':

          1.504866      task-clock (msec)         #    0.886 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
               592      page-faults               #    0.393 M/sec                  
         4,952,896      cycles                    #    3.291 GHz                      (33.77%)
         2,461,931      stalled-cycles-frontend   #   49.71% frontend cycles idle   
         2,461,430      stalled-cycles-backend    #  49.70% backend cycles idle     
         4,000,101      instructions              #    0.81  insn per cycle         
                                                  #    0.62  stalled cycles per insn
           760,869      branches                  #  505.606 M/sec                  
            31,136      branch-misses             #    4.09% of all branches          (88.50%)

       0.001698771 seconds time elapsed

[root@hoge tmp]# perf stat date +"%F %T 0.%3N"
2017-02-02 03:39:52 0.612

 Performance counter stats for 'date +%F %T 0.%3N':

          0.355000      task-clock (msec)         #    0.658 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
               193      page-faults               #    0.544 M/sec                  
         1,193,873      cycles                    #    3.363 GHz                    
           800,322      stalled-cycles-frontend   #   67.04% frontend cycles idle   
           636,341      stalled-cycles-backend    #  53.30% backend cycles idle     
           785,182      instructions              #    0.66  insn per cycle         
                                                  #    1.02  stalled cycles per insn
           144,848      branches                  #  408.023 M/sec                  
           branch-misses                                               

       0.000539333 seconds time elapsed

[root@hoge tmp]# 
time コマンドによる計測では、mydate.pl は、date コマンドより2倍程度の重さに見えてましたが、perf stat で計ると、4倍程度重いという結果が見てとれました。

2017年1月18日水曜日

ロングオプションの罠

とある自作 bash スクリプトにて、fallocate コマンドを使ったのですが、これまた、とあるサーバで --length オプションがエラーに。。。
[root@hoge ~]# fallocate --keep-size --length 1m /tmp/fuga
fallocate: unrecognized option '--length'
Usage: fallocate [options] <filename>

Options:
 -h, --help          this help
 -n, --keep-size     don't modify the length of the file
 -p, --punch-hole    punch holes in the file
 -o, --offset   offset of the allocation, in bytes
 -l, --length   length of the allocation, in bytes

For more information see fallocate(1).
おかしい、他のサーバでは、大丈夫なのに。。。
CentOS 6 用の最新の util-linux-ng の .src.rpm を展開して、さぐってみたら、ありました typo バグ。
[root@hoge SOURCES]# cat util-linux-ng-2.17-opts-typos.patch 
diff -up util-linux-ng-2.17.2/misc-utils/findmnt.c.kzak util-linux-ng-2.17.2/misc-utils/findmnt.c
--- util-linux-ng-2.17.2/misc-utils/findmnt.c.kzak 2016-03-08 11:39:00.996400246 +0100
+++ util-linux-ng-2.17.2/misc-utils/findmnt.c 2016-03-08 11:44:49.598921954 +0100
@@ -586,7 +586,10 @@ int main(int argc, char *argv[])
      { "output",       1, 0, 'o' },
      { "raw",          0, 0, 'r' },
      { "types",        1, 0, 't' },
-     { "fsroot",       0, 0, 'v' },
+
+     { "nofsroot",     0, 0, 'v' },
+     { "fsroot",       0, 0, 'v' }, /* RHEL6: typo, backward compatibility */
+
      { "submounts",    0, 0, 'R' },
      { "source",       1, 0, 'S' },
      { "target",       1, 0, 'T' },
diff -up util-linux-ng-2.17.2/sys-utils/fallocate.c.kzak util-linux-ng-2.17.2/sys-utils/fallocate.c
--- util-linux-ng-2.17.2/sys-utils/fallocate.c.kzak 2016-03-08 11:39:01.028400021 +0100
+++ util-linux-ng-2.17.2/sys-utils/fallocate.c 2016-03-08 11:43:28.799496864 +0100
@@ -125,7 +125,10 @@ int main(int argc, char **argv)
      { "keep-size", 0, 0, 'n' },
      { "punch-hole", 0, 0, 'p' },
      { "offset",    1, 0, 'o' },
-     { "lenght",    1, 0, 'l' },
+
+     { "length",    1, 0, 'l' },
+     { "lenght",    1, 0, 'l' },  /* RHEL6: typo, backward compatibility */
+
      { NULL,        0, 0, 0 }
  };

[root@hoge SOURCES]# cat ../SPECS/util-linux-ng.spec
...
# 1122839 - fallocate and findmnt have wrong options according to man pages
Patch114: util-linux-ng-2.17-opts-typos.patch
...
* Mon Jan 11 2016 Karel Zak  2.17.2-12.19
...
- fix #1122839 - fallocate and findmnt have wrong options according to man pages
https://bugzilla.redhat.com/show_bug.cgi?id=1122839

というわけで、エラーが出ていたサーバの util-linux-ng が古かったというオチでした。やはり、ほとんどの場合、常に最新を使うのが吉ですね。もちろんです。

今回、--keep-size オプションを使おうとして、なんとなくロングオプションを選んでしまいましたが、こういった間違いが起きてる可能性を考えると、ショートオプションを使ったほうが吉かもしれませんね。自作スクリプトは、古い環境も対象にしているので、ショートオプションに修正しました。

2015年5月27日水曜日

お手軽なベンチマークあれこれ

Linux で、マシン(CPU)の相対性能を比較したい場合に、自分で使っているベンチマークについて、備忘録として書いてみたいと思います。

(1) bashで100万回ループ
[root@hoge ~]# cat /etc/centos-release
CentOS Linux release 7.1.1503 (Core) 
[root@hoge ~]# dmidecode -s processor-version
Intel(R) Core(TM) i7-2960XM CPU @ 2.70GHz
[root@hoge ~]# echo $LANG
ja_JP.UTF-8
[root@hoge ~]# time for ((i=0;++i<1000000;))
> do
> :
> done

real 0m4.035s
user 0m3.857s
sys 0m0.184s
何もインストールする必要がなく、簡単に測定できますが、一つ注意点があります。
なんと!ロケールが影響します。なので、条件を揃えるため、常に LANG=C に設定してから計測したほうが良いかと思います。
次が、同じマシンで LANG=C にした場合の結果です。
[root@hoge ~]# LANG=C
[root@hoge ~]# echo $LANG
C
[root@hoge ~]# time for ((i=0;++i<1000000;))
> do
> :
> done

real 0m3.212s
user 0m3.045s
sys 0m0.171s
ってことは、場合によっては、スクリプトの冒頭で。。。ですね。せこい最適化の前にやることはあるでしょうけど。

(2) sysbench
[root@hoge ~]# sysbench --test=cpu run
sysbench 0.4.12:  multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 1

Doing CPU performance benchmark

Threads started!
Done.

Maximum prime number checked in CPU test: 10000


Test execution summary:
    total time:                          9.1036s
    total number of events:              10000
    total time taken by event execution: 9.1029
    per-request statistics:
         min:                                  0.88ms
         avg:                                  0.91ms
         max:                                  1.07ms
         approx.  95 percentile:               0.93ms

Threads fairness:
    events (avg/stddev):           10000.0000/0.00
    execution time (avg/stddev):   9.1029/0.00
このベンチは、EPEL に収録されているので、RHEL/CentOS なら、簡単にインストールできます。操作も簡単です。
テストの種類はいろいろと用意されていますが、--test=cpu と --test=memory をよく使います。簡単なので。

(3) 姫野ベンチ
[root@hoge ~]# ./bmt
mimax = 129 mjmax = 129 mkmax = 257
imax = 128 jmax = 128 kmax =256
 Start rehearsal measurement process.
 Measure the performance in 3 times.

 MFLOPS: 1898.971870 time(s): 0.216599 1.733593e-03

 Now, start the actual measurement process.
 The loop will be excuted in 831 times
 This will take about one minute.
 Wait for a while

 Loop executed for 831 times
 Gosa : 8.213920e-04 
 MFLOPS measured : 1959.001846 cpu : 58.159398
 Score based on Pentium III 600MHz : 23.890266
理研が公開しているベンチマークです。こちらはコンパイルが必要ですが、make 一発なので、容易だと思います。
上の結果は、C, static allocate version M のものです。
C, dynamic allocate version というのも用意されています。下が実行例です。
[root@hoge ~]# ./hime
For example: 
 Grid-size= XS (32x32x64)
     S  (64x64x128)
     M  (128x128x256)
     L  (256x256x512)
     XL (512x512x1024)

Grid-size = M

mimax = 128 mjmax = 128 mkmax = 256
imax = 127 jmax = 127 kmax =255
 Start rehearsal measurement process.
 Measure the performance in 3 times.

 MFLOPS: 322.654502 time(s): 1.249692 1.733593e-03

 Now, start the actual measurement process.
 The loop will be excuted in 144 times
 This will take about one minute.
 Wait for a while

 Loop executed for 144 times
 Gosa : 1.308934e-03 
 MFLOPS measured : 331.831996 cpu : 58.326202
 Score based on Pentium III 600MHz using Fortran 77: 4.046732

(4) memtest86+
これは、ベンチマークが主目的ではないですが、メモリ性能の確認にも使います。

2016-01-17追記
手持ちマシン等のデータを集めてみようと思います。随時追加していくつもり。
CPU (1) 100万回
ループ
(2) sysbench
CPU
(3) 姫野ベンチ
bmt
備考
x y z u v
Pentium 4 3.06GHz 10.2 31.1 415 2002年発表
CentOS6(i686)で計測
Core 2 Duo L7100 1.20GHz 9.5 (107%) 34.0 (91%) 519 (125%) 2007年発表
CentOS7(x86_64)で計測
Pentium D 950 3.40GHz 8.9 (115%) 20.6 (151%) 890 (214%) 2006年Q1
Core 2 X6800 2.93GHz 4.3 (237%) 16.0 (194%) 1025 (247%) 2006年Q3
Core 2 QX6800 2.93GHz 4.3 (237%) 16.0 (194%) 1037 (250%) 2007年Q2
Core i7 620M 2.67GHz 3.7 (276%) 8.7 (359%) 1368 (330%) 2010年Q1
Core i7 2960XM 2.70GHz 3.2 (319%) 9.1 (342%) 2516 (606%) 2011年Q4
Core i5 4670 3.40GHz 2.6 (392%) 8.1 (384%) 2945 (710%) 2013年Q2
Core i7 7500U 2.70GHz 3.4 (300%) 8.3 (374%) 4090 (985%) 2016年Q3 2019.11追記
Core i7 8565U 1.80GHz 1.6 (637%) 6.9 (450%) 7110 (1713%) 2018年Q3 2019.12追記
CentOS8(x86_64)で計測
括弧内%は、Pentium 4 3.06GHz を基準にした性能比です。
また、マルチコアは考慮されていません。
sysbench であれば、コマンドラインオプションとして --num-threads=コア数 を付与すれば、マルチコアを使ったベンチができます。そのうち計測してみたいと思ってます。

2016-02-03追記
sysbench --num-threads=T --test=cpu run を計測した値。
CPU コア数 スレッド数 sysbench
CPU
備考
x c T S v
Pentium 4 3.06GHz 1 2 25.3 2002年発表
Core 2 Duo L7100 1.20GHz 2 2 20.0 (126%) 2007年
Pentium D 950 3.40GHz 2 2 10.6 (239%) 2006年Q1
Core 2 X6800 2.93GHz 2 2 8.1 (312%) 2006年Q3
Core 2 QX6800 2.93GHz 4 4 4.1 (617%) 2007年Q2
Core i7 620M 2.67GHz 2 4 3.5 (723%) 2010年Q1
Xeon E3-1270 3.40GHz 4 8 1.4 (1807%) 2011年Q2
Core i7 2960XM 2.70GHz 4 8 1.5 (1687%) 2011年Q4
Core i5 4670 3.40GHz 4 4 2.2 (1150%) 2013年Q2
Core i7 7500U 2.70GHz 2 4 2.33 (1086%) 2016年Q3 2019.11追記
Core i7 8565U 1.80GHz 4 8 1.33 (1902%) 2018年Q3 2019.12追記
括弧内%は、Pentium 4 3.06GHz を基準にした性能比です。技術の進歩を感じますね。


■関連記事
Linux 上でメモリ帯域幅をベンチマーク


2015年3月8日日曜日

CentOS7 の bash の補完機能は便利なのだが、たまに無効化したい

CentOS 7 の bash は、CentOS 6 までと違って、デフォルトの補完機能が拡充されていて、例えば、systemctl のオプション候補を出したりするときに、大変便利です。
[root@hoge ~]# systemctl list-  ※ここまで打って、TAB を押下すればオプション候補一覧が表示される
list-dependencies  list-jobs          list-sockets       list-unit-files    list-units         
[root@hoge ~]# systemctl list-

ところが、例えば、initramfs を再作成するような場面で、、、
[root@hoge ~]# dracut -f /boot/initr  ※CentOS 6 以下だと、ここで TAB を押下すれば候補が出たんだけど
ファイル一覧を出して欲しいのですが、CentOS 7 の場合は、デフォルト設定では候補が出なくなってしまっています。
このケースに限ると、alias の回避と同様に、先頭にバックスラッシュをつければ良いようです。本日、気が付いた。
[root@hoge ~]# \dracut -f /boot/initr
initramfs-0-rescue-17561ed66271e2f6923ff3c901624b3a.img
initramfs-3.10.0-123.20.1.el7.x86_64.img
initramfs-3.10.0-123.20.1.el7.x86_64kdump.img
initrd-plymouth.img
[root@hoge ~]# \dracut -f /boot/initr
しかしながら、次のような場合は、NG のようです。
[root@hoge ~]# \dd if=/dev/uran  ※/dev/urandom を出したいが、ここで TAB を押下しても補完してくれない
どうにか出来無いもんでしょうかね。
[root@hoge ~]# \dd if= /dev/uran  ※このように = の後ろに1スペース入れると TAB 補完が動くのだが、戻るのが面倒

2015年2月15日日曜日

ZFS on Linux の snapshot 運用スクリプト例

legacy なファイルサーバ (ext3+sambaを利用) のバックアップを、rsync を使って、ZFS on Linux 上に採取しているのですが、ZFS の snapshot 機能を使うことで、例えて言うと「タイムマシン」のような効果を得ています。

具体的には、毎日1回 rsync で、ファイルサーバの内容を ZFS 上へバックアップして、その後、その ZFS 領域に対して1日1回の snapshot を採取しています。これで、間違ってファイルを消したりした場合に、snapshot を過去に辿って参照することで、間違って消してしまう以前の出来るだけ最新状態のファイルを復活させるということが可能になります。

ただ、1日1回の snapshot とはいえ、日々繰り返せば1年では 365 もの snapshot が残ることになり、そのファイルサーバの更新頻度から考えると過剰に思えました。そこで、次のようなアルゴリズムで、古い snapshot を削減するようにしました。

■わたしが考えた古い snapshot を削除するゴルゴリズム
(1) 先月の snapshot までは、1日1つを維持する。
(2) 先々月の snapshot は、奇数日だけ残して半減させる。
(3) 3ヶ月めに入ったら、1日・7日・13日・19日・25日を残して、その他を削除する。
(4) 4ヶ月めに入ったら、13日の snapshot だけを残す。
このような条件で過去の snapshot を削除することで、直近なら1日の粒度で、古くは1ヶ月に1つの粒度の snapshot を残すようにしました。

以下、そのスクリプトを掲載したいと思います。同じようなことをしたいと考える人が居るかなと思いますし、一から書くのも骨なので、ご参考まで。

まず、平凡ですが、スナップショットを採るスクリプトです。
#!/bin/bash
#
# Name: my-zfs-snapshot
#
# Authors: staka <blue3waters at gmail dot com>
#
# This file is subject to the GNU General Public License.
# It comes with NO WARRANTY.
#

TARGET_ZFS=tankX/fsvdata  ※対象を指定

TIME_STAMP=`date +%F-%H%M`

/sbin/zfs snapshot ${TARGET_ZFS}@${TIME_STAMP}

exit $?
次に、古いスナップショットを削除するスクリプトです。
#!/bin/bash
#
# Name: my-zfs-destroy-snapshot
#
# Authors: staka <blue3waters at gmail dot com>
#
# This file is subject to the GNU General Public License.
# It comes with NO WARRANTY.
#

PATH=/sbin:$PATH

TARGET_ZFS=tankX/fsvdata  ※対象を指定

B2_MONTH=`date -d "$(date +%Y-%m-15) -2 month" +%Y-%m`
B3_MONTH=`date -d "$(date +%Y-%m-15) -3 month" +%Y-%m`
B4_MONTH=`date -d "$(date +%Y-%m-15) -4 month" +%Y-%m`
B4_FLAG13=

DESTROY_LIST=

for SNAPSHOT in `zfs list -H -t snapshot -o name`
do
    DESTROY=no

    [[ $SNAPSHOT != "${TARGET_ZFS}@"* ]] && continue

    if [[ $SNAPSHOT = *"${B2_MONTH}-"* ]] ; then
        case $SNAPSHOT in
        *"${B2_MONTH}-"?[02468]-*)
            DESTROY=yes
            ;;
        esac
    fi

    if [[ $SNAPSHOT = *"${B3_MONTH}-"* ]] ; then
        case $SNAPSHOT in
        *"${B3_MONTH}-01-"*)
            ;;
        *"${B3_MONTH}-07-"*)
            ;;
        *"${B3_MONTH}-13-"*)
            ;;
        *"${B3_MONTH}-19-"*)
            ;;
        *"${B3_MONTH}-25-"*)
            ;;
        *)
            DESTROY=yes
            ;;
        esac
    fi

    if [[ $SNAPSHOT = *"${B4_MONTH}-"* ]] ; then
        case $SNAPSHOT in
        *"${B4_MONTH}-13-"*)
            B4_FLAG13=yes
            ;;
        *)
            DESTROY=yes
            ;;
        esac
    fi

    if [ $DESTROY = "yes" ] ; then
        DESTROY_LIST="$DESTROY_LIST $SNAPSHOT"
    fi
done

for SNAPSHOT in $DESTROY_LIST
do
    if [[ $SNAPSHOT = *"${B4_MONTH}-"* ]] && [ "$B4_FLAG13" != "yes" ] ; then
        continue
    fi
    zfs destroy $SNAPSHOT
done

exit 0
この2つのスクリプトを cron で1日1回実行するようにスケジュールしています。

これを書いて使い始めたのが 2013年11月 なので、1年3ヵ月ほど使っています。
この間、1度だけ、復活させたいファイル(たぶん誤操作で、いつの間にか消えていた)があり、恩恵を得られました。非常に助かりました。

このスクリプトの中で肝となる部分は、先々月(B2_MONTH)などを、どうやって求めるか?ですが、それについては、次の記事も参照ください。
■関連記事
date コマンドで先月を求める方法

2014年5月8日木曜日

bash でシェル変数が定義されているかを判定する方法は?

bash でシェル変数が(nullかどうかではなくて)定義されているかを判定したいと思って、調べたのですが、エレガントな方法がみつかりませんでした。

最初は、
if [ "${VAR:-UNDEF}" = "UNDEF" ] ; then
    ...
fi
だろうかと思ったのですが、VAR="" の時も真となってしまうので、微妙に違いました。それと、実際には無いはずとしても VAR="UNDEF" だったら、意図しない状況になってしまうのが引っかかります。処理内容によってはセキュリティホールのタネにもなるのではないか。

書籍調査/ネット検索の末に、
if ! set | grep -m 1 -q ^VAR= ; then
    ... # VARが未定義の場合に行う処理を記述
fi
とするしかなさそうなのですが、もっとエレガントな方法は無いものでしょうかね。現在のマシンでは大した処理コストではないですが、目的に対しては仰仰しいと思いました。
bash にそういった機能(isset のような)があったらいいように思いますが、需要がほとんどないし不要ということだろうか。

2014-05-10追記
匿名様(コメント参照)から情報頂き、
if [ -z "${VAR+x}" ] ; then    # VARが定義済み(nullを含む)の場合 x が返るので、-z でテストすれば OK
    ... # VARが未定義の場合に行う処理を記述
fi
と書けると知りました。少々難しい(慣れの問題?)ですが、手短に書けて良いでね。

2014-05-17追記
RHEL7.0 RC を調べていて、[ -v VAR ] が追加されているのを見つけました。
[root@hoge ~]# cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.0 (Maipo)
[root@hoge ~]# bash --version
GNU bash, バージョン 4.2.45(1)-release (x86_64-redhat-linux-gnu)
...
[root@hoge ~]# help test
...
    Other operators:
    
      -o OPTION      True if the shell option OPTION is enabled.
      -v VAR  True if the shell variable VAR is set
...
bash の CHANGES を確認してみると、バージョン 4.2 で追加されたようです。
------------------------------------------------------------------------------
This document details the changes between this version, bash-4.2-alpha,
and the previous version, bash-4.1-release.
...
3.  New Features in Bash
...
f.  test/[/[[ have a new -v variable unary operator, which returns success if
    `variable' has been set.
...
いちおう動作確認です。
[root@hoge ~]# VAR="set" ; [ -v VAR ] ; echo $?
0
[root@hoge ~]# unset VAR ; [ -v VAR ] ; echo $?
1
RHEL6/CentOS6 以下の bash では使えないので、当面は利用できないですが、メモまで。

2012年6月19日火曜日

第2水曜日にcronスケジュールしたい

第2水曜日および第3水曜日にcronスケジュールしたいと思ったのですが、調べてみるとcronで指定可能なパターンでは、スケジュールできないことを知りました。

http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=20020&forum=10

ZFS ファイルシステムの scrub 処理 (整合性チェック) を定期的に動かしたいのですが、zfs-fuse に標準で備わっている設定方法だと、毎週行うか否かの2択になっており、それはちょっと動かし過ぎと思いました。
RAIDZ と RAIDZ2 の2つのストレージプール (tank1, tank2) を作成して ZFS を利用していますが、tank1 は月1回、tank2 は2ヶ月に1回の頻度で scrub を動かしたいため、前述の URL の情報を参考に、自作スクリプトを作りました。どなたかの参考になれば幸いと思いますので、掲載します。
#!/bin/bash
#
# Name: my-zfs-fuse-scrub
#

TARGET=""
if [[ "$1" = 20??-??-?? ]] ; then
    TARGET="-d $1"
elif [ -n "$1" ] ; then
    echo "Usage: my-zfs-fuse-scrub [YYYY-MM-DD]" 1>&2
fi

MONTH=`date +%-m $TARGET`
DAY=`date +%-d $TARGET`
WEEKDAY=`LANG=C date +%a $TARGET`

get_W() {
    local D=$1
    local F L
    for W in 1 2 3 4 5
    do
        F=$((1+($W-1)*7))
        L=$((F+6))
        if let "$F <= $D && $D <= $L" ; then
            break
        fi
    done
}

test_get_W() {
    local D
    for D in `seq 1 31`
    do
        get_W $D
        printf "D=%-2d W=%d\n" $D $W
    done
}
##DEBUG## test_get_W && exit 1

get_W $DAY

if [ "$W,$WEEKDAY" = "2,Wed" ] ; then
    if [ -z "$TARGET" ] ; then
        /usr/bin/zpool scrub tank1
    else
        echo "DEBUG: zpool scrub tank1 run @ $1"
    fi
fi

if [ "$W,$WEEKDAY" = "3,Wed" ] && let "$MONTH%2 == 0" ; then
    if [ -z "$TARGET" ] ; then
        /usr/bin/zpool scrub tank2
    else
        echo "DEBUG: zpool scrub tank2 run @ $1"
    fi
fi

exit 0
これを、次のように crontab に登録することで、毎月第2水曜日 AM1:00 に tank1 の scrub を実行、偶数月の第3水曜日 AM1:00 に tank2 の scrub を実行するようにスケジューリングしました。
0 1 * * Wed /root/bin/my-zfs-fuse-scrub
なお、実行日を確認し易いように、デバッグ機能をつけてあります。
# cal 6 2012
      June 2012
Su Mo Tu We Th Fr Sa 
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

# ./my-zfs-fuse-scrub 2012-06-13
DEBUG: zpool scrub tank1 run @ 2012-06-13
# ./my-zfs-fuse-scrub 2012-06-20
DEBUG: zpool scrub tank2 run @ 2012-06-20
# ./my-zfs-fuse-scrub 2012-06-27
#
# cal 7 2012
      July 2012
Su Mo Tu We Th Fr Sa 
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

# ./my-zfs-fuse-scrub 2012-07-11
DEBUG: zpool scrub tank1 run @ 2012-07-11
# ./my-zfs-fuse-scrub 2012-07-18
#
さあこれで、まずは、今晩深夜 1:00 に tank2 への scrub がちゃんと動くはず...何か間違いを犯していなければ。。

2012-06-23追記
ログを確認したところ、うまく行っていました。
# cat /var/log/cron
...
Jun 20 01:00:01 xxxx CROND[28792]: (root) CMD (/root/bin/my-zfs-fuse-scrub)
...
# zpool status

  pool: tank1
 state: ONLINE
 scrub: none requested
config:

...

errors: No known data errors

  pool: tank2
 state: ONLINE
 scrub: scrub completed after 0h42m with 0 errors on Wed Jun 20 01:42:50 2012
config:

...

errors: No known data errors
次は、7/11 (第2水曜) に tank1 の scrub が動き、7/18 (第3水曜日 && 奇数月) には tank2 への scrub が行われない、というふうにプログラムできたはずです。

2012-07-20追記
その後のログを確認したところ、意図した通りに動作していました。
# who -b
         system boot  2012-07-03 12:14
# uptime
 08:07:13 up 14 days, 19:53,  3 users,  load average: 0.12, 0.09, 0.09
# uname -a
Linux xxxx 2.6.32-220.13.1.el6.x86_64 #1 SMP Tue Apr 17 23:56:34 BST 2012 x86_64 x86_64 x86_64 GNU/Linux
# cat /etc/redhat-release 
CentOS release 6.3 (Final)
# grep zfs-fuse /var/log/cron
Jul  3 13:26:01 xxxx run-parts(/etc/cron.weekly)[12348]: starting 98-zfs-fuse-scrub
Jul  3 13:26:01 xxxx run-parts(/etc/cron.weekly)[12355]: finished 98-zfs-fuse-scrub
Jul  4 01:00:01 xxxx CROND[41538]: (root) CMD (/root/bin/my-zfs-fuse-scrub)
Jul 10 03:10:01 xxxx run-parts(/etc/cron.weekly)[30291]: starting 98-zfs-fuse-scrub
Jul 10 03:10:01 xxxx run-parts(/etc/cron.weekly)[30298]: finished 98-zfs-fuse-scrub
Jul 11 01:00:01 xxxx CROND[4220]: (root) CMD (/root/bin/my-zfs-fuse-scrub)
Jul 17 03:37:01 xxxx run-parts(/etc/cron.weekly)[43573]: starting 98-zfs-fuse-scrub
Jul 17 03:37:01 xxxx run-parts(/etc/cron.weekly)[43580]: finished 98-zfs-fuse-scrub
Jul 18 01:00:01 xxxx CROND[15841]: (root) CMD (/root/bin/my-zfs-fuse-scrub)
# zpool status | grep scrub:
 scrub: scrub completed after 1h13m with 0 errors on Wed Jul 11 02:13:35 2012
 scrub: none requested
# zpool status
  pool: tank1
 state: ONLINE
 scrub: scrub completed after 1h13m with 0 errors on Wed Jul 11 02:13:35 2012
config:

...

errors: No known data errors

  pool: tank2
 state: ONLINE
 scrub: none requested
config:

...

errors: No known data errors

これであとは、5年くらい運用・利用できたら、コスト(投資額+構築労力)に見合うかなと思います。

2015-02-10追記
備忘録。この記事を書いた当時は zfs-fuse を使ってたが、その後、ZFS on Linux に切り替えて継続利用中。
当時新品の HDD (ちなみに全て AFT の 2.5inch 7200rpm 750G)で組んで、2年半経過しましたが、今のところ不良セクタはありません。

2012年5月19日土曜日

bashスクリプトでatexit(3)相当を行う

C 言語だと、atexit() という関数がありますが、bash にも似た機能があることを知りました。

bash スクリプトが Ctrl+C で途中中断される場合を想定し、スクリプトの処理途中で生成される一時ファイルを削除(ゴミ掃除)したい場合があります。そんな時には、trap "rm ..." SIGINT という具合に書くのが定石かと思っていました。しかし、そのような書き方だと、スクリプト全体の終了時にも rm の記述をすることになります。次のような具合に・・・
#!/bin/bash
#
# Name: test_trap_SIGINT.bash
#

TEMPFILE=$(mktemp /tmp/.${0##*/}.$RANDOM$RANDOM.XXXXXX)
trap "echo trapped ; rm -f $TEMPFILE" SIGINT

echo $TEMPFILE

# 一時ファイルを使った処理を行う ... 
sleep 10 # このサンプルでは、スリープ
#

rm -f $TEMPFILE
exit 0
このサンプルの実行イメージは次のようになります。
途中中断しない場合
# ./test_trap_SIGINT.bash 
/tmp/.test_trap_SIGINT.bash.2085610279.7jIDoS
# ls -l /tmp/.test_trap_SIGINT.bash.2085610279.7jIDoS
ls: /tmp/.test_trap_SIGINT.bash.2085610279.7jIDoS: No such file or directory

途中で Ctrl+C した場合
# ./test_trap_SIGINT.bash 
/tmp/.test_trap_SIGINT.bash.579227843.ewVGq4
trapped
# ls -l /tmp/.test_trap_SIGINT.bash.579227843.ewVGq4
ls: /tmp/.test_trap_SIGINT.bash.579227843.ewVGq4: No such file or directory
最近、このパターンでスクリプトを書いていたら、一時ファイル生成後に途中終了 (exit 1) させたい条件が複数あり、いちいち rm を記述しました。
しかしハテ?もしかして atexit(3) のような機能はないものか?
っと、help を見てみますと、、、
# help trap
trap: trap [arg] [signal_spec ...] or trap -l
    The command ARG is to be read and executed when the shell receives
    signal(s) SIGNAL_SPEC.  If ARG is absent all specified signals are
    reset to their original values.  If ARG is the null string each
    SIGNAL_SPEC is ignored by the shell and by the commands it invokes.
    If a SIGNAL_SPEC is EXIT (0) the command ARG is executed on exit from
    the shell.  If a SIGNAL_SPEC is DEBUG, ARG is executed after every
    command.  If ARG is `-p' then the trap commands associated with
    each SIGNAL_SPEC are displayed.  If no arguments are supplied or if
    only `-p' is given, trap prints the list of commands associated with
    each signal number.  Each SIGNAL_SPEC is either a signal name in 
    or a signal number.  `trap -l' prints a list of signal names and their
    corresponding numbers.  Note that a signal can be sent to the shell
    with "kill -signal $$".
あるではないですか・・・さすが bash ですね。
なので、次のように書けます。
#!/bin/bash
#
# Name: test_trap_EXIT.bash
#

TEMPFILE=$(mktemp /tmp/.${0##*/}.$RANDOM$RANDOM.XXXXXX)
trap "echo trapped ; rm -f $TEMPFILE" EXIT

echo $TEMPFILE

# 一時ファイルを使った処理を行う ... 
sleep 10 # このサンプルでは、スリープ
#

exit 0
注意点としては、もし trap "トラップ内容" SIGINT EXIT と書くと、Ctrl+C 中断の際にトラップ内容が2回実行されることになります。

2011年12月18日日曜日

ksh で扱える整数の上限

bash と tcsh について、扱える整数の上限を調べましたが、さらに ksh についても調べてみました。
#!/bin/ksh93

function test_int_limit {
        typeset i=2147483646
        echo -e "INT_MAX-1 = "$i
        let i++
        echo -e "INT_MAX   = "$i
        let i++
        echo -e "INT_MAX+1 = "$i
echo
        i=4294967294
        echo -e "ULONG_MAX-1 = "$i
        let i++
        echo -e "ULONG_MAX   = "$i
        let i++
        echo -e "ULONG_MAX+1 = "$i
echo
        i=999999999999999998
        echo "i = "$i
        let i++
        echo "i+1 = "$i
        let i++
        echo "i+2 = "$i
echo
        i=9223372036854775806
        echo -e "LLONG_MAX-1 = "$i
        let i++
        echo -e "LLONG_MAX   = "$i
        let i++
        echo -e "LLONG_MAX+1 = "$i
echo
}

test_int_limit
ksh について、何点か補足メモ、、、

(1) 関数定義する場合、function キーワードを使用して、関数名の後ろに () はつけません。
つけると構文エラーになります。
(2) ローカル変数を使うには、typeset を指定します。
bash にも typeset がありますが、あちらは、Obsolute (廃止) とされています。
(3) 関数定義は、(1) 以外にも旧来の 関数名() { ... } という記載方法もある。
しかし、その場合に typeset によるローカル変数指定が機能しません。
なので、ksh の場合は (1) の書き方を使いましょう。

テストスクリプトを実行した結果が、下記です。
# uname -a                                                                                                                                                                      
Linux my41 2.6.18-274.12.1.el5 #1 SMP Tue Nov 29 13:37:46 EST 2011 x86_64 x86_64 x86_64 GNU/Linux
# ksh93 --version                                                                                                                                                               
  version         sh (AT&T Research) 93t+ 2010-02-02
# ./test_int_limit.ksh93 
INT_MAX-1 = 2147483646
INT_MAX   = 2147483647
INT_MAX+1 = 2147483648

ULONG_MAX-1 = 4294967294
ULONG_MAX   = 4294967295
ULONG_MAX+1 = 4294967296

i = 999999999999999998
i+1 = 999999999999999999
i+2 = 1e+18

LLONG_MAX-1 = 9223372036854775806
LLONG_MAX   = 9.22337203685477581e+18
LLONG_MAX+1 = 9.22337203685477581e+18
このように、18桁までは整数、19桁以上は浮動小数点数として扱ってくれるようです。
そうです。ksh は浮動小数点数を扱えます。一方、bash では浮動小数点数を扱えません。
ごく稀に、bash でも浮動小数点数を使いたい場合があり、やむなく gawk を呼び出したりしますが、ksh ならそのようなことをしなくても済むようです。
なお、ksh の typeset には、-i オプション (整数型指定) がありますが、これを指定すると内部的に int 扱いになるようで、INT_MAX までしか格納できなくなります。

bash と tcsh の扱える整数の上限については、次の記事参照。
bash で扱える整数の上限
tcsh で扱える整数の上限

tcsh で扱える整数の上限

以前、bash で扱える整数の上限 という記事を書きましたが、tcsh についても調べてみました。
#!/bin/tcsh

@ i=2147483646
echo "INT_MAX-1 = "$i
@ i++
echo "INT_MAX   = "$i
@ i++
echo "INT_MAX+1 = "$i
echo ""
@ i=4294967294
echo "ULONG_MAX-1 = "$i
@ i++
echo "ULONG_MAX   = "$i
@ i++
echo "ULONG_MAX+1 = "$i
echo ""
@ i=9223372036854775806
echo "LLONG_MAX-1 = "$i
@ i++
echo "LLONG_MAX   = "$i
@ i++
echo "LLONG_MAX+1 = "$i
echo ""
実行結果は次の通りです。
# tcsh --version                                                                                                                                                                
tcsh 6.17.00 (Astron) 2009-07-10 (x86_64-unknown-linux) options wide,nls,dl,al,kan,rh,color,filec
# ./test_int_limit.tcsh                                                                                                                                                         
INT_MAX-1 = 2147483646
INT_MAX   = 2147483647
INT_MAX+1 = -2147483648

ULONG_MAX-1 = -2
ULONG_MAX   = -1
ULONG_MAX+1 = 0

LLONG_MAX-1 = -2
LLONG_MAX   = -1
LLONG_MAX+1 = 0
このように、現在の tcsh では INT_MAX までしか扱え無いようです (注:実験は、64bit環境で行ってます) 。もし、仕事で csh を指定されているなど、どうしても csh 系でスクリプトを書くという方は、お気をつけて。
なお、あちこちで言われているように、csh 系でスクリプトを書くのはやめたほうが良いだろうと思います。最大の難点は、関数定義できない点だと思います。

参考URL:
有害な csh プログラミング
結論: csh はプログラミングにはまったく向かないツールであり、
そのような目的に使うことは厳しく禁じられるべきである!
8. まとめ ... そもそもが欠陥品なのです。
人気ブログランキングへ にほんブログ村 IT技術ブログへ