サブシェル内部の変数を参照しようとして失敗する例とその対策

6つのランダム値を作成し、その中の最大値を得るというスクリプトの例

失敗例
#!/bin/bash

# このシェルでランダムな値を6つ作成して、パイプラインで while 文に渡し、
# while 文内部で最大数を取得する。
printf "%s\n" ${RANDOM}{,,,,,} |
  while read num
  do
    [ $num -gt ${biggest:=0} ] && biggest=$num
  done

# 最大数を表示しようとするが、パイプラインの先はサブシェル内で実行されるため、
# サブシェル内で格納された結果をこのシェルで参照することはできない。
printf "The largest number is: %d\n" "$biggest"

結果

The largest number is: 0
リダイレクトを利用した例
#!/bin/bash

# サブシェルでランダムな値を6つ作成して、このシェルの while 文にデータをリダイレクトする。
while read num
 do
 [ $num -gt ${biggest:=0} ] && biggest=$num
 done < <(printf "%s\n" ${RANDOM}{,,,,,})

# while 文はこのシェルで実行されるので、結果をこのシェルで参照することができる。
printf "The largest number is: %d\n" "$biggest"

結果例

The largest number is: 26815
リダイレクトを利用しない例
#!/bin/bash

for num in ${RANDOM}{,,,,,}
 do
 [ $num -gt ${biggest:=0} ] && biggest=$num
 done

printf "The largest number is: %d\n" "$biggest"

結果例

The largest number is: 32654

Muninメモ

参考情報

学習方法

注意点

なんでもメモ

2015-03-08

critical と warning を同時に扱う場合の munin のメール送信用コマンド

warning と critical でメール送信を分けると、両方が混在した場合に2通メールが来て煩わしいので、力技で1通にまとめてみた。

munin.conf

contact.alert.command mail -s "[`[ -n "${var:cfields}" ] && echo CRITICAL``[ -n "${var:wfields}" -a -n "${var:cfields}" ] && echo /``[ -n "${var:wfields}" ] && echo WARNING``[ ! -n "${var:wfields}" -a ! -n "${var:cfields}" ] && echo INFO`] ${var:host} -> ${var:graph_title}`[ -n "${var:wfields}" -o -n "${var:cfields}" ] && echo ' -> '``[ -n "${var:wfields}" -a -n "${var:cfields}" ] && echo [CRITICAL]:``[ -n "${var:cfields}" ] && echo ${loop<,>:cfields ${var:label}=${var:value}}``[ -n "${var:wfields}" -a -n "${var:cfields}" ] && echo ' '``[ -n "${var:wfields}" -a -n "${var:cfields}" ] && echo [WARNING]:``[ -n "${var:wfields}" ] && echo ${loop<,>:wfields ${var:label}=${var:value}}`" "me@example.com"
contact.alert.always_send  warning critical

WARNINGのみの場合のメールのタイトル例

[WARNING] host.name -> Load average -> load=0.06

CRITICALのみの場合のメールのタイトル例

[CRITICAL] host.name -> Disk usage in percent -> /=7.28

WARNINGとCRITICALの両方を含む場合のメールのタイトル例

[CRITICAL/WARNING] host.name -> Memory usage -> [CRITICAL]:vmalloc_used=3477504.00,apps=81174528.00 [WARNING]:page_tables=5312512.0

閾値を超えていない場合のメールタイトル例(OKの場合など)

[INFO] host.name -> Disk usage in percent

2015-03-05

telnet コマンドに echo コマンドで値を渡して出力結果を得る方法。

今回は、telnet で 80 番ポートを叩いて、404エラーが返ってくるのを確認したいという場合を想定してみた。

手動で叩く場合

手動の場合は以下のようになる。

telnet localhost 80 2>/dev/null
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Thu, 05 Mar 2015 13:41:26 GMT
Connection: close

コマンド経由でうまくいかない例

telnet に普通に echo で値を渡しても、telnet の入力として扱われない。404エラーの表示を期待しているのだが表示されない。

$ echo -e "GET / HTTP/1.0\n\n" | telnet localhost 80 2>/dev/null
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

コマンド経由でうまくいく例

期待通りに404エラーが表示される。

{ echo -e "GET / HTTP/1.0\n\n"; sleep 1; } | telnet localhost 80 2>/dev/null
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Thu, 05 Mar 2015 13:28:34 GMT
Connection: close

コマンド経由でtelnetを叩き、404が返ってきた場合には "OK"、そうでない場合には "NG" を返す例

$ [ "`{ echo -e "GET / HTTP/1.0\n\n"; sleep 1; } | telnet localhost 80 2>/dev/null | grep -o "HTTP/1.1 404 Not Found"`" = "HTTP/1.1 404 Not Found" ] && echo "OK" || echo "NG"
OK

※ 2>/dev/null しているのは、標準エラー出力から出力される "Connection closed by foreign host." 等の文字列を無視するため。

2015-03-02

MySQL の "Warning: Using a password on the command line interface can be insecure" 対応。

概要

スクリプトから mysql を呼び出す際に、コマンドライン上にパスワードを直接入れている箇所があると、mysql がこのログを吐く。たしかに、セキュリティ上よろしくないですよね。また、ログが結構汚染されるのはいただけないので、対処しました。

対応方法

任意の場所に、以下のような接続情報提供用の my.cnf ファイルを作成する。所有者をスクリプトの実行ユーザーに、モードを 600 にすることにより、他のユーザーからアクセスされないようにする。

[client]
user = <MySQL接続時のユーザー名>
password = <MySQL接続時のパスワード>
host = <localhostとか>

スクリプトからコマンドを呼び出す際に、以下のオプションを追加する。

--defaults-extra-file=/foo/bar/my.cnf

考察

mysql側が標準的な手法を提供してくれているのだから、従っておいたほうがいろいろと幸せになるような気がする今日この頃。

2015-03-01

とあるテーブルに OPTIMIZE TABLE をしたメモ。(実運用中のデータをテスト環境にコピーして実施)

  • DBMS : MySQL 5.6.23
  • TYPE : INNODB
  • innodb_buffer_pool_size : そんなに大きくない。実験対象のテーブルサイズよりも小さい。

実施前

  • レコードは約3,000万行(30,466,260)
  • とある集計にかかった時間は約5分(4分59秒)
  • ibdファイルサイズは約3GB(3,162,505,216バイト)

実施

  • 実施時間は約20分(18分54秒)

実施後

  • とある集計にかかった時間は約1分(1分11秒)
  • ibdファイルサイズは約1.4GB(1,426,063,360バイト)

備考

mysqlcheck を使うと、一括でのオプティマイズが楽にできる。
以下例:

$ mysqlcheck -uroot -p -o --all-databases

2015-03-01

新環境に既存の MySQL データを移行し、java から MySQL を叩いた際に以下のエラーに遭遇。
※stacktrace の細部は割愛。

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

Caused by: java.net.ConnectException: Connection refused

原因は、my.cnf の内容を刷新している際に、skip-networking が混入してしまったこと。

Android開発用gradle環境 on debian

Windows 環境で NDK を扱うのはめんどくさいので、debian 上に gradle 環境を作成してみました。

OSのインストール

OSのインストールは X60sとDebian - Be an Idealistic Realist 等を参考に適当にやりましょう。

javaのインストール

java7のインストールを行う。java6は公式アップデート終了。java8との組み合わせは枯れていないので避けたほうが無難かも。

gradle1.12のインストール

Gradle Plugin を利用する場合、gradle のバージョンが制約されるので、以下で確認しておきます。

ダウンロードファイルは以下から選択しましょう。

実行例:

# 作業フォルダに移動
cd /tmp

# ダウンロード&配置
wget https://services.gradle.org/distributions/gradle-1.12-bin.zip
unzip gradle-1.12-bin.zip
mkdir -p /opt/gradle
mv gradle-1.12 /opt/gradle/gradle-1.12

# ゴミ掃除
rm gradle-1.12-bin.zip

# パス設定
echo '
# for gradle
GRADLE_HOME=/opt/gradle/gradle-1.12
PATH=$HOME/bin:$GRADLE_HOME/bin:$PATH
export GRADLE_HOME
export PATH' >> /etc/profile

# 動作確認
. /etc/profile
gradle -version

android-sdkのインストール

http://developer.android.com/sdk/index.html#Other からインストール対象を選択してインストールします。

実行例:

# 作業フォルダに移動
cd /tmp

# ダウンロード
wget http://dl.google.com/android/android-sdk_r24.0.2-linux.tgz

# チェックサム確認
if test "`sha1sum android-sdk_r24.0.2-linux.tgz`" = "b6fd75e8b06b0028c2427e6da7d8a09d8f956a86  android-sdk_r24.0.2-linux.tgz"; then
  echo "Check Sum OK"
else
  echo "Check Sum ERROR"
fi

# 展開
tar zxf android-sdk_r24.0.2-linux.tgz
mv android-sdk-linux /opt/android-sdk

# 環境変数設定
echo '
# for android-sdk
ANDROID_HOME=/opt/android-sdk
export ANDROID_HOME' >> /etc/profile

# 動作確認
. /etc/profile
echo $ANDROID_HOME

# ゴミ掃除
rm android-sdk_r24.0.2-linux.tgz
  • 必要なパッケージの入手(GUI版)

以下を実行して Android SDK Manager を起動し、必要なパッケージを入手する。

/opt/android-sdk/tools/android

Android NDK のインストール

Android NDK 本家 http://developer.android.com/tools/sdk/ndk/index.html の通りに実行すればOK。

実行例:

# 作業フォルダに移動
cd /tmp

# ダウンロード
wget http://dl.google.com/android/ndk/android-ndk-r10d-linux-x86_64.bin

# チェックサム確認
if test "`md5sum android-ndk-r10d-linux-x86_64.bin`" = "263b83071e6bca15f67898548d8d236e  android-ndk-r10d-linux-x86_64.bin"; then
  echo "Check Sum OK"
else
  echo "Check Sum ERROR"
fi

# 展開
7z x android-ndk-r10d-linux-x86_64.bin
mkdir -p /opt/android-ndk
mv android-ndk-r10d /opt/android-ndk/

# 環境変数設定
echo '
# for android-ndk
ANDROID_NDK_HOME=/opt/android-ndk/android-ndk-r10d/
PATH=$HOME/bin:$ANDROID_NDK_HOME:$PATH
export ANDROID_NDK_HOME
export PATH' >> /etc/profile

# 動作確認
. /etc/profile
ndk-build -version

gitのインストール

git で開発してる場合は入れておきましょう。

# インストール
apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev

# 動作確認
git --version

先祖クラスのコンストラクタでインスタンスを生成する方法

先祖クラスのコンストラクタインスタンスを生成するサンプルプログラム

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class ConstructorForSerializationExample {
    public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Constructor cons = ConstructorForSerializationFactory.newConstructorForSerialization (Child.class, Parent.class);

        Child child = (Child)cons.newInstance ();
        System.out.println ("child.name = " + child.name);

        Field field = Child.class.getDeclaredField ("name");
        field.setAccessible (true);
        field.set (child, "Vakabon");
        System.out.println ("child.name = " + child.name);
    }

    public static class Parent {
    }

    public static class Child extends Parent {
        private final String name;

        public Child (String name) {
            this.name = name;
        }
    }
}

先祖クラスのコンストラクタインスタンスを生成する部分

import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;
import java.security.AccessController;

public class ConstructorForSerializationFactory {
    private static final ReflectionFactory reflFactory = (ReflectionFactory)AccessController.doPrivileged (new ReflectionFactory.GetReflectionFactoryAction ());

    public static Constructor newConstructorForSerialization (Class<?> classToBeInstantiated, Class<?> targetAncestorClass, Class<?>... paramTypes) throws NoSuchMethodException {
        return reflFactory.newConstructorForSerialization (classToBeInstantiated, targetAncestorClass.getDeclaredConstructor (paramTypes));
    }
}

How to manage SQLite's database file names

SQLite名前空間に関する問題

AndroidSQLite を扱う場合、SQLiteOpenHelper を利用するのが一般的だと思いますが、その際にデータベースファイル名を指定する必要があります。*1*2

データベースファイル名は同一アプリケーション内部で共有されるため、アドホックに管理すると、意図しない名前衝突が起こる可能性があります。*3

今回は、この名前衝突を回避する方法を模索してみようと思います。

名前衝突を回避する方法の模索

データベースをアプリケーションで1つにする方法

1アプリ1データベースという方式にすれば、アプリ内部でのDB名の衝突は起こりません。この方法は、データ構造が単純かつ、データの管理をひとりで行うような場合はアリかもしれません。しかし、時間の経過とともにデータ構造が複雑化されていくことも十分に考えられるため、分割統治する仕組みがあったほうが無難かもしれません。

データベースファイル名を集中管理し、データベース自体は個別管理する方法

データベースファイル名を集中管理し、データベース自体は個別管理する方法では、データベースファイル名の一元管理と、複数のデータベースの相互非依存な個別管理が可能になります。

どちらの方法を採用するか?

特にDBを分ける明確な理由がない間は単一DBで運用し、必要が生じたときにDBを分割するという方法がよさそうです。DBを分割してしまうと、追加の複雑さを導入してしまうというデメリットもありますが、それ以上に、異なるDB間でテーブルのjoinができなくなるという点が致命的。開発初期は相互に無関係のデータだと思っていても、後になってjoinする必要性が出てくるということはありうるし、それは未来予知能力がない限りわからんですよね。

実現方法

enum によるデータベース名の集中管理

データベース名の集中管理を行うための enum を作成します。enum として扱うことにより、実装の分散を防ぎます。

public enum SQLiteDatabaseIdentifier {
    TEST_DATABASE(1);

    private final int version;

    SQLiteDatabaseIdentifier(int version) {
        this.version = version;
    }

    public int version() {
        return version;
    }
}

SQLiteDatabaseIdentifier を扱う SQLiteOpenHelper の作成

SQLiteDatabaseIdentifier を扱うことを強制された SQLiteOpenHelper を作成します。これを利用することで、データベースファイル名の一元管理と複数のデータベースの相互非依存な個別管理が可能になります。

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import static android.database.sqlite.SQLiteDatabase.CursorFactory;

public abstract class SQLiteDatabaseIdentifierAwareSQLiteOpenHelper extends SQLiteOpenHelper {
    public SQLiteDatabaseIdentifierAwareSQLiteOpenHelper(Context context, SQLiteDatabaseIdentifier id, CursorFactory factory) {
        super(context, id.name(), factory, id.version());
    }
}

SQLiteDatabaseIdentifierAwareSQLiteOpenHelper の実装例

package com.objectfanatics.example;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class DbHelper extends SQLiteDatabaseIdentifierAwareSQLiteOpenHelper {
    private static final SQLiteDatabaseIdentifier sqLiteDatabaseIdentifier = SQLiteDatabaseIdentifier.TEST_DATABASE;

    public DbHelper(Context context) {
        super(context, sqLiteDatabaseIdentifier, null);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        String sql1 = "create table User (name text primary key)";
        sqLiteDatabase.execSQL(sql1);

        String sql2 = "insert into User (name) values ('John Smith')";
        sqLiteDatabase.execSQL(sql2);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i2) {
    }

    public String getName() {
        Cursor c = getReadableDatabase().rawQuery("select name from User", null);
        c.moveToFirst();
        return c.getString(c.getColumnIndex("name"));
    }
}

まとめ

今回は、データベース名(とバージョン)の一元管理を行いつつ、各データベースの実装を相互非依存にするための簡単な方法をまとめました。

今回は思い付きベースでまとめてみただけなので、もっといいアイデアがあればぜひ教えてください(^^;

*1:in-memory database を利用しない限りは

*2:SQLiteOpenHelper を使わなくても SQLite を使う限りはデータベースファイル名は必要ですよね。

*3:名前衝突が行わなかったとしても名前が分散している状態というのは精神衛生上よろしくないですよね。衝突してもコンパイル時に検出できないという恐怖も常に付きまとうし、、、。