2016年1月23日土曜日

gawk で文字列 "01" の比較でハマった

長年(20年以上) AWK を使っていて、わかってるつもりでしたが、文字列が暗黙に数値として扱われる際の落とし穴にハマりましたので、備忘録です。以前にも、このパターンに悩まされた記憶があるんですが、忘れたころにふたたびバグってしまいました。
BEGIN {
    s="01 02"
    split(s, ss)
}
{
    for (i in ss) {
        if ($1 == ss[i]) {
            printf "%s == %s\n", $1, ss[i]
        } else {
            printf "%s != %s\n", $1, ss[i]
        }
    }
}
[root@hoge tmp]# echo 01 | gawk -f test_string_01.awk 
01 == 01
01 != 02
[root@hoge tmp]# echo 1 | gawk -f test_string_01.awk 
1 == 01
1 != 02
$1 が 1 の場合には、if ($1 == ss[i]) において ss[i] が数値として扱われ、文字列の "01" が暗黙に数値の 1 として比較が実行されるのだと思います。次のように修正(C言語のキャストっぽく修正)することで、意図した動作になりました。
BEGIN {
    s="01 02"
    split(s, ss)
}
{
    for (i in ss) {
        if ($1 == ""ss[i]) {
            printf "%s == %s\n", $1, ss[i]
        } else {
            printf "%s != %s\n", $1, ss[i]
        }
    }
}
[root@hoge tmp]# diff -u test_string_01.awk test2_string_01.awk 
--- test_string_01.awk 2016-01-23 20:06:54.537647508 +0900
+++ test2_string_01.awk 2016-01-23 20:25:23.543167642 +0900
@@ -4,7 +4,7 @@
 }
 {
     for (i in ss) {
-        if ($1 == ss[i]) {
+        if ($1 == ""ss[i]) {
             printf "%s == %s\n", $1, ss[i]
         } else {
             printf "%s != %s\n", $1, ss[i]
[root@hoge tmp]# echo 01 | gawk -f test2_string_01.awk 
01 == 01
01 != 02
[root@hoge tmp]# echo 1 | gawk -f test2_string_01.awk 
1 != 01
1 != 02
ちなみに、別実装(nawk, mawk)でも、同様の結果でした。
[root@hoge tmp]# echo 01 | nawk -f test_string_01.awk 
01 != 02
01 == 01
[root@hoge tmp]# echo 1 | nawk -f test_string_01.awk 
1 != 02
1 == 01
[root@hoge tmp]# echo 01 | mawk -f test_string_01.awk 
01 != 02
01 == 01
[root@hoge tmp]# echo 1 | mawk -f test_string_01.awk 
1 != 02
1 == 01
nawk, mawk では、for (i in ss) の順序が、gawk とは違うようですが、今回の問題の比較部分は同様に意図しない動作になります。そして、同じ修正で、意図した動作になります。
[root@hoge tmp]# echo 1 | mawk -f test2_string_01.awk 
1 != 02
1 != 01
[root@hoge tmp]# echo 1 | nawk -f test2_string_01.awk 
1 != 02
1 != 01

こういうことも、たまにはあるけども、いやー AWK って本当にいいもんですね。って思います。
この 20年 どんだけお世話になったことか。費用対効果(学習コストに対する成果)が抜群じゃないかなと。手短に手早く書いて、あっという間に結果を得られることがしばしばです。

2016年1月16日土曜日

perlでサブルーチンを動的に定義しようとしてハマりました

perlでモジュールの存在有無により、サブルーチンを動的に定義しようと考えました。具体的には次のような内容です。
#!/usr/bin/perl

BEGIN {
    $hires = "Time::HiRes qw(clock_gettime CLOCK_MONOTONIC)" ;
    unless (eval "use $hires ; 1") {
        print STDERR "WARNING: couldn't use $hires\n" ;
        undef $hires ;
    }
}

if (defined($hires)) {
    sub fine_uptime {
        return scalar clock_gettime(CLOCK_MONOTONIC) ;
    }
} else {
    open(UPTIME, "< /proc/uptime") ;
    sub fine_uptime {
        seek(UPTIME, 0, SEEK_SET) ;
        my $t ;
        ($t, undef) = split(" ", <UPTIME>) ;
        return $t ;
    }
}

print fine_uptime(), "\n" ;
しかし、このスクリプトは意図したようには動作しませんでした。
[root@hoge ~]# uname -a
Linux hoge 3.10.0-327.4.4.el7.x86_64 #1 SMP Tue Jan 5 16:07:00 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# rpm -q perl
perl-5.16.3-286.el7.x86_64
[root@hoge ~]# rpm -q perl-Time-HiRes
perl-Time-HiRes-1.9725-3.el7.x86_64
[root@hoge ~]# ./test_clock_gettime.pl 

[root@hoge ~]# 
このように何も出力されません。どうやら、else ブロック内の sub fine_uptime が生きてしまうようで、一方で open() が実行されないため、何も出力されないという結果になるようです。そこで久々に「Perl クックブック VOLUME 1」を読んでみたら、無名サブルーチンを型グロブへ代入すれば意図した動作となることが分かりました。具体的には次のように書き換えました。
#!/usr/bin/perl

BEGIN {
    $hires = "Time::HiRes qw(clock_gettime CLOCK_MONOTONIC)" ;
    unless (eval "use $hires ; 1") {
        print STDERR "WARNING: couldn't use $hires\n" ;
        undef $hires ;
    }
}

if (defined($hires)) {
    *fine_uptime = sub {
        return scalar clock_gettime(CLOCK_MONOTONIC) ;
    } ;
} else {
    open(UPTIME, "< /proc/uptime") ;
    *fine_uptime = sub {
        seek(UPTIME, 0, SEEK_SET) ;
        my $t ;
        ($t, undef) = split(" ", <UPTIME>) ;
        return $t ;
    } ;
}

print fine_uptime(), "\n" ;
[root@hoge ~]# diff -u test_clock_gettime.pl test_clock_gettime2.pl 
--- test_clock_gettime.pl 2016-01-16 17:37:48.436060959 +0900
+++ test_clock_gettime2.pl 2016-01-16 17:41:02.495271960 +0900
@@ -9,17 +9,17 @@
 }
 
 if (defined($hires)) {
-    sub fine_uptime {
+    *fine_uptime = sub {
         return scalar clock_gettime(CLOCK_MONOTONIC) ;
-    }
+    } ;
 } else {
     open(UPTIME, "< /proc/uptime") ;
-    sub fine_uptime {
+    *fine_uptime = sub {
         seek(UPTIME, 0, SEEK_SET) ;
         my $t ;
         ($t, undef) = split(" ", ) ;
         return $t ;
-    }
+    } ;
 }
 
 print fine_uptime(), "\n" ;
[root@hoge ~]# ./test_clock_gettime2.pl 
1780.807779516
[root@hoge ~]# ./test_clock_gettime2.pl ; cat /proc/uptime 
1791.497339723
1791.49 6891.38
つい最近、Bash スクリプトで、条件に応じて if else 文で関数を再定義する処理を書いていたため、perl でも同様に書こうとしてハマってしまったのでした。このような目に遭っても、なんでだか「Perl って、おもしろいな」と感じました。ラクダ本の関連部分(型グロブ)を読んでみよう!

2019-06-21追記
ラクダ本の「4.7 グローバル宣言」に、「サブルーチン宣言とフォーマット宣言は、グローバル宣言である。(中略)ifのように実行時に評価される条件文の中に宣言を置いてコンパイラから隠すことによって、条件に応じてサブルーチンやフォーマットを宣言する、ということはできない。」とありました。いまさらですが、なるほどでした。これにハマったのか。

2016年1月6日水曜日

CentOS 7.2 で Btrfs RAID1 の df 出力が素直になっていた件

以前、このブログに書いたのですが、Btrfs RAID1 の場合、df がレポートする Size が2倍に見えていました(RHEL7.1 / CentOS 7.1 まで)。
[root@hoge ~]# uname -a
Linux hoge 3.10.0-229.14.1.el7.x86_64 #1 SMP Tue Sep 15 15:05:51 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# btrfs fi show --mounted 
Label: 'btrfs201405'  uuid: 32387714-ab8f-4b84-8648-e99ff10a4082
 Total devices 2 FS bytes used 14.08GiB
 devid    1 size 97.66GiB used 15.03GiB path /dev/sdc3
 devid    2 size 97.66GiB used 15.01GiB path /dev/sdb3

btrfs-progs v3.19.1
[root@hoge ~]# df -hT /mnt_temp
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sdc3      btrfs  196G   29G  166G  15% /mnt_temp
これ、運用上は、トラブルの元になる(例:空きが2倍に見えるため、収まると思って大容量ファイルをコピーしてディスクが溢れるとか)ことがあり、実に分かりにくい仕様だと思っていたのですが、CentOS 7.2 にアップデートしたら、素直な表示に変わってました。
[root@hoge ~]# uname -a
Linux hoge 3.10.0-327.3.1.el7.x86_64 #1 SMP Wed Dec 9 14:09:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# btrfs fi show --mounted
Label: 'btrfs201405'  uuid: 32387714-ab8f-4b84-8648-e99ff10a4082
 Total devices 2 FS bytes used 14.08GiB
 devid    1 size 97.66GiB used 15.03GiB path /dev/sdc3
 devid    2 size 97.66GiB used 15.01GiB path /dev/sdb3

btrfs-progs v3.19.1
[root@hoge ~]# df -hT /mnt_temp
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sdc3      btrfs   98G   15G   83G  15% /mnt_temp
kernel-3.10.0-327.3.1.el7.x86_64 の changelog から、次の修正に対応するようです。
[root@hoge ~]# rpm -q --changelog kernel-3.10.0-327.3.1.el7.x86_64 | less
...

* Tue Apr 07 2015 Rafael Aquini  [3.10.0-237.el7]
...
- [fs] btrfs: fix wrong accounting of raid1 data profile in statfs (Eric Sandeen) [1205873]
この英文をキーワードにして、コミュニティのログを探すと、パッチイメージが見てとれます。なーるほど。
https://lkml.org/lkml/2015/1/8/544
http://www.gossamer-threads.com/lists/linux/kernel/2079761
kernel-3.10.0-229.el7.x86_64 から kernel-3.10.0-327.3.1.el7.x86_64 の間の btrfs 関係の changelog は 250 以上(ちなみに XFS は同程度、ext4 は 45 件)もあり、レッドハットは、かなり頑張っているという感触が読み取れます。もう一息じゃないのか?と思えますが、RHEL 7.2 では Btrfs は、まだ Technology Preview です。レッドハットは良識があるなと感じます。
人気ブログランキングへ にほんブログ村 IT技術ブログへ