三項演算子のような if-then-else 処理
三項演算子のような if-then-else 処理
[ testの評価式 ] && 真の場合の処理 || 偽の場合の処理
真の場合の例
$ [ "a" = "a" ] && echo "OK" || echo "NG" OK
偽の場合の例
$ [ 1 -eq 2 ] && echo "OK" || echo "NG" NG
サブシェル内部の変数を参照しようとして失敗する例とその対策
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メモ
参考情報
- Munin Guide
- Munin 2系の本家情報。
- Munin Wiki
- Instant Munin Plugin Starter
- プラグイン作成系の書籍。おいらは未読。
学習方法
- 概要把握
- Munin Guide の先頭から Munin’s Architecture まで読めばOK。
- 概要以上の学習
- 数時間で読み切れるので、Munin Guide を一通り読破しましょう。
注意点
- サードパーティーのプラグインは /usr/local/munin/lib/plugins 以下に配置するのが推奨。/usr/share/munin/plugins/ には入れちゃダメ。munin-node のアップデート等で上書きされちゃうとかいろいろ問題があります。(https://munin.readthedocs.org/en/latest/plugin/use.html#installing)
- /etc/munin/plugin-conf.d/munin-node は改変してはダメ。munin-node のアップデート等で上書きされちゃいます。同一フォルダ内のファイルがアルファベット順かつ後勝ちで読まれるので、zzz-myconf というファイルを作って入れましょう。(https://munin.readthedocs.org/en/latest/plugin/use.html#configuring)
なんでもメモ
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 をしたメモ。(実運用中のデータをテスト環境にコピーして実施)
実施前
- レコードは約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バイト)
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 の名前空間に関する問題
Android で SQLite を扱う場合、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")); } }
まとめ
今回は、データベース名(とバージョン)の一元管理を行いつつ、各データベースの実装を相互非依存にするための簡単な方法をまとめました。
今回は思い付きベースでまとめてみただけなので、もっといいアイデアがあればぜひ教えてください(^^;