sed簡単整理1 - 基本 -

簡単な変換はsedで、複雑な変換はawkというようにしていましたが、
sedawkぐらいのことができると分かったので、整理してみます。
sedperlっぽくて私にはawkの方が使いやすかったですが、
少しいじってみたら、sedの方が簡潔でよかったです。
状況次第でsedawkを使い分けたら、効率があがりそうです。

使い方

sed -e '処理内容' ファイル名
# 正規表現を使いたい場合、-rを付ける。
# 普段はリダイレクト(>)で別のファイルに出力し、原本は残すが、原本を修正したい場合は-iを付ける。

処理内容

アドレス+コマンド+コマンドパラメータ

アドレス

行番号又は文字列又は正規表現正規表現を使う時は-rオプションが必要。

1,5 : 1行目から5行目まで 
1~5 : 1行目から5行おき(1, 6, 11...)5,$ : 5行目から最後の行まで。
/apple/ : appleがある行。

コマンド

s, d, i, N, H, h, pなどがある。
各コマンドの前に!が付くと、逆の意味になる。

コマンドパラメータ

s/apple/pie/  #sコマンドのコマンドパラメータはappleとpie。
4asuper! #aコマンドのコマンドパラメータはsuper!。

sコマンド

置換。

cat << EOF > sed_test.txt
a apple haha
b apple hihi
c apple hopi
d man ama
e kkk appp
f apple mamo
g apple nami
abc apple !!
wow happy new year, duke!
EOF

sed -e '1,5s/apple/pie/g' sed_test.txt
# 1行目から5行目までのappleをpieに置換。
# 結果
# a pie haha
# b pie hihi
# c pie hopi
# ...

sed -r -e 's/happy.+year/>&</g' sed_test.txt
# &はパターンにマッチした文字列(下記の場合、happy new year)。
# 正規表現なので、-rオプションがついた。
# 結果
# ...
# wow >happy new year<, duke!

sed -e '/abc/s/apple/pie/g' sed_test.txt
# abcがある行のappleをpieに変換。
# 結果
# ...
# abc pie !!
# ...

dコマンド

削除。

sed -e '5d' sed_test.txt
#5行目を削除。

sed -e '5!d' sed_test.txt
#5行目以外を削除。

iコマンド

指定した行に文字列を挿入。

sed -e '4isuper!' sed_test.txt
# 4行目にsuper!を挿入。
# 結果
# a apple haha
# b apple hihi
# c apple hopi
# super!
# ...

aコマンド

指定した行の後ろに文字列を追加。

sed -e '4asuper!' sed_test.txt
# 4行目の後ろ(つまり、5行目)にsuper!を挿入。
# 結果
# a apple haha
# b apple hihi
# c apple hopi
# d man ama
# super!
# ...

Nコマンド

次の行を先読みする。複数の行をまとめて処理する時に利用できる。

sed -e 'N;N;s/\n/,/g' sed_test.txt
# 3行ずつ(基本1行 + N(次の1行) + N(次の1行))読み込んで、コンマでつなげる。
# 最後のgはglobal。マッチした全ての文字列を置換するという意味。これがないと1行目のみ置換される。
# 結果
# a apple haha,b apple hihi,c apple hopi
# d man ama,e kkk appp,f apple mamo
# g apple nami,abc apple !!,wow happy new year, duke!

H, h, p

ホールドスペース・パターンスペースに関連するコマンド。
sed簡単整理2に整理する予定。複雑な処理に使える。

JavaImporterの繰り返し生成時、with__noSuchProperty__ placeholderエラー

JDK 1.8.0_20で下記のJavascriptソースのようにJavaImporterインスタンスを繰り返し生成すると、
(実際のソースは違いますが、理解しやすさのため、ウェブからサンプルを持ってきました)

/* ファイル名 : test.js
    実行 : jrunscript test.js
    期待値 : 出力なし。
 */
var constant = 0.50;
var ind = 0.0;

for (var i = 0; i < 50; i++) {
    var math = new JavaImporter(java.lang.StrictMath);
    ind += 10.0;
    with (math) {
        StrictMath.exp(-constant*ind);
    }
}

for (var i = 0; i < 50; i++) {
    var math = new JavaImporter(java.lang.StrictMath);
    try {
        math.Foo();
    } catch (e) {
        if (! (e instanceof TypeError)) {
            throw e;
        }
    }
}

下記のようが例外がスローされました。
Exception in thread "main" java.lang.AssertionError: __noSuchProperty__ placeholder called
at jdk.nashorn.internal.objects.NativeJavaImporter.__noSuchProperty__(NativeJavaImporter.java:105)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:557)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
at jdk.nashorn.internal.runtime.ScriptObject.invokeNoSuchProperty(ScriptObject.java:2113)
at jdk.nashorn.internal.runtime.ScriptObject.megamorphicGet(ScriptObject.java:1805)
at jdk.nashorn.internal.scripts.Script$test.runScript(test.js:8)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:535)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:568)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:525)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:521)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:187)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:249)
at com.sun.tools.script.shell.Main.evaluateReader(Main.java:332)
at com.sun.tools.script.shell.Main.evaluateStream(Main.java:368)
at com.sun.tools.script.shell.Main.processSource(Main.java:285)
at com.sun.tools.script.shell.Main.access$100(Main.java:37)
at com.sun.tools.script.shell.Main$2.run(Main.java:200)
at com.sun.tools.script.shell.Main.main(Main.java:48)

繰り返し生成というのはJavascript側でfor文を利用した繰り返しだけでなく、
Java側でJavascriptソースを実行するjavax.script.ScriptEngineのオブジェクトを
ThreadLocalやマップなどにキャッシュして繰り返し使用することも含みます。

JDKのバグのようで、JDK 1.8.0_40から直ったらしいです。
https://bugs.java.com/view_bug.do?bug_id=8060101

試しにJDK 1.8.0_60で上記のJavascriptソースを実行したら、エラーは出ませんでした。

ダブルクリックで任意のパスワードを簡単にコピペ

Windowsでダブルクリックで任意のパスワードをコピペできる方法です。

手順は以下の通りです。rubyが必要です。

  1. Windowsのデスクトップに拡張子がcmdのファイルを一つ作成します。仮にget_passwd.cmdとします。
  2. メモ帳でget_passwd.cmdの内容を以下にし、保存します。
@echo off
ruby -e "puts (('a'..'z').to_a  + ('A'..'Z').to_a + (0..9).to_a).shuffle[0..7].join" | clip

これで終わりです。
rubyスクリプトでパスワードを生成し、パイプでclipコマンドに渡しているところが要です。

任意のパスワードが必要な時、デスクトップにあるget_passwd.cmdをダブルクリックし、
必要なところにCtrl+Vすれば、パスワードがペーストされます。

rubyを利用していますが、コマンドラインの出力が得られれば、
どの言語でもかまいません。ここではrubyを基準に説明します。

もちろん、パスワードを生成してくれるウェブサイトも利用できますが、
ブラウザでアクセスすることすら面倒で、
(多分大丈夫だけど、オンラインだから)何か不安を感じている私のような方なら、
こちらの方法が楽でいいかなと思います。

行列演算 API(ND4J)の簡単整理

Javaディープラーニングライブラリー「Deeplearning 4 j(以下、DL4J)」で行列の演算を行う方法を整理します。

今まで数冊のディープラーニングや数学の本を読んで
ディープラーニングについて少しは分かった気はしますが、
数学が苦手なので、DL4で行列に関する引数及び戻り値の解析に難がありました。
なんというか、行列に関するAPIを呼び出した時、紙に書いてみないと、
頭の中に結果行列の姿がパット思い浮かべないです。

そこでここに行列演算のAPIについて書いて自分の理解を深めると同時に参考ドキュメントにしたいと思います。

DL4Jを使う時、行列の演算を行う時、依存ライブラリーとして、ND4Jという多次元配列のライブラリーのAPIを使用します。
以下はその説明です。
CSVやDBからDL4Jが自動で行列を作ってくれるので、以下のAPIを使うのはまれだと思いますが、
基礎となる部分ですので、理解して損はないと思います。

ND4Jのメソッドの結果はINDArrayで、多次元配列です。内部はC++で実装されているようです。

行列の作成

説明

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

2番目の引数に行列を格納する多次元配列の形(shape)を指定します。
数字の数が次元の数(ここでは2つ)を表しており、要素は各次元の長さ(1次元 : 4つ, 2次元 : 2つ)です。
まず、長さ4の配列が作られ、それぞれの要素に長さ2の配列が割り当てられます。
2次元なので、まさに行・列ですね。

1番目の引数に指定された配列の要素が最後の次元(2次元)の長さずつ(ここでは2つずつ)、順に配列を埋めていきます。
ここではまず、[1, 2]と埋めて、次は[ 3, 4]、[5, 6]、[7, 8]と埋めていき、4x2の多次元配列、つまり、行列が完成します。

1番目の引数に指定される要素の数は2番目の引数に指定される要素同士の掛け算の結果と同じでなければなりません。
ここでは4 * 2 = 8ですから、8つの要素が必要です。

練習問題

以下の結果は何でしょうか。次元の数は3つです。答えは最後にあります。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, new int[]{ 3, 2, 2 });

前置行列(Transpose)

説明

縦の要素と横の要素を入れ替えた行列です。行列を表すINDArrayオブジェクトに対してtranpose()メソッドを呼び出すことで前置行列を求められます。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray tnd = nd.transpose();
/*
結果2:  結果1の縦と横が変わりました。
[[1.00, 3.00, 5.00, 7.00],
 [2.00, 4.00, 6.00, 8.00]]
*/

変形(Reshape)

説明

元の行列の形(行と列の数)を変えます。例えば、4x2行列を2x4行列に変えるなどです。
注意すべきところは変形前後の行と列の掛け算の結果が同じでなければならないということです。
例えば、4x2行列は3x3行列に変えることはできません。掛け算の結果(8 != 9)が違うためです。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

INDArray rnd = nd.reshape(2, 4); // rows:2 , columns: 4
/*
結果2:
[[1.00, 2.00, 3.00, 4.00],
 [5.00, 6.00, 7.00, 8.00]]
*/

hstack(水平積み上げ)

引数に指定された複数のINDArrayを水平に埋めた行列を返します。
サンプルを見た方が早いでしょう。

INDArray nd1 = Nd4j.create(new float[]{1, 2, 3, 4}, new int[]{ 2, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00]]
*/
INDArray nd2 = Nd4j.create(new float[]{5, 6, 7, 8}, new int[]{ 2, 2 });
/*
結果2:
[[5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray hstack = Nd4j.hstack(nd1, nd2);
/*
結果3:
[[1.00, 2.00, 5.00, 6.00],
 [3.00, 4.00, 7.00, 8.00]]
結果1の行列と結果2の行列が水平に埋められた結果になりました。
*/

hstackの引数は可変長なので、複数のINDArrayが指定できます。

linspace(行ベクトル作成)

lower(下限値)、upper(上限値)、num(長さ)をそれぞれ順にlinspaceメソッドに指定して実行すると、
lowerからupperまでの範囲からnumだけの数を要素として持つX行1列の行列(行ベクトル)が返されます。
要素はnumに合わせてlower~upperの範囲を一定の間隔(つまり、一定のstepで)で分割し、求められます。
例えば、lower: 1, upper: 10, num: 3なら、3個の要素を持つ行列になりますが、
最初は1、2番目は真ん中の5.5, 3番目は10になります。

以下にサンプルを示します。

INDArray nd1 = Nd4j.linspace(1, 10, 3);
/*
結果1:
[1.00, 5.50, 10.00]
*/

INDArray nd2 = Nd4j.linspace(1, 10, 10);
/*
結果2:
[1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00]
*/

INDArray nd3 = Nd4j.linspace(1, 10, 20);
/*
結果3:
[1.00, 1.47, 1.95, 2.42, 2.89, 3.37, 3.84, 4.32, 4.79, 5.26, 5.74, 6.21, 6.68, 7.16, 7.63, 8.11, 8.58, 9.05, 9.53, 10.00]
*/

今日はここまでです。思い出したら、vstack, diag, zeroも書きます。
恥ずかしい水準ではありますが、やはり書きながら、理解度が上がる気がします。

練習問題の解答

行列の作成

[[[1.00, 2.00],
  [3.00, 4.00]],

 [[5.00, 6.00],
  [7.00, 8.00]],

 [[9.00, 10.00],
  [11.00, 12.00]]]

2*2の行列が3つ生成された形です。

メソッド呼出時、引数にJSON(マップ)を渡せます。

Javaのテンプレートエンジン Velocityでメソッド呼出時、引数にJSONを渡せます。
色々いじってみて知りました。
方法は引数のところに{ "key1": "value1"....のように普段のJSONと同じく書けばいいです。
実際メソッドの引数に渡されるオブジェクトはMapです。
以下のサンプルを参照して下さい。

import java.io.StringWriter;
import java.util.Map;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.Context;

public class TestVelocityJson {
    // テンプレートで使うBeanクラス。
    public static class TestBean {
        public void duke(Map<String, Object> t) {
            // 適切にキャストして使用します。
            String param1 = (String) t.get("param1");
            int param2 = (int) t.get("param2");
            System.out.println("param1: " + param1);
            System.out.println("param2: " + param2);
        }
    }

    public static void main(String[] args) throws Exception {
        {
            // メソッド呼出。
            String template =
                "${testBean.duke({\"param1\" : \"v1\", \"param2\": 11})} ";
            Context vc = new VelocityContext();
            vc.put("testBean", new TestBean());
            StringWriter sw = new StringWriter();
            System.out.println("-- Test1 -- ");
            Velocity.evaluate(vc, sw, "test", template);
            /*
             * 結果
             * -- Test1 --
             * param1: v1
             * param2: 11
             */
        }
        {
            // マクロからメソッド呼出。
            String template =
                "#macro(dukeTest $p) " +
                "  $testBean.duke($p) " +
                "#end " +
                "#dukeTest({\"param1\" : \"v2\", \"param2\": 22})";

            Context vc = new VelocityContext();
            vc.put("testBean", new TestBean());
            StringWriter sw = new StringWriter();
            System.out.println("-- Test2 -- ");
            Velocity.evaluate(vc, sw, "test", template);
            /*
             * 結果
             * -- Test2 --
             * param1: v2
             * param2: 22
             */
        }
    }
}

違うユーザのリモートリポジトリをPull・Push時、エラー対処

状況

まず、Eclipseのバージョンは古い(Mars, 2015年版)です。
EclipseのPackage Explorer上に複数のプロジェクトがあり、
それぞれのプロジェクトが属するGitHubのリモートリポジトリのGitHubユーザが違う場合、
(個人用と会社用を分けて使うなど)
PullやPushなどのリモート操作時、以下のエラー(Invalid remote: origin)が発生します。
f:id:jeongman7:20170701184341j:plain

原因

対処方法からの推測ですが、全てのプロジェクトが属するリモートリポジトリのGitHubユーザ情報が
最後に設定したリモートリポジトリのGitHubユーザ情報に上書きされるからだと思います。
今のところ、それぞれのプロジェクトに違うGitHubユーザ情報を設定できる方法はなさそうです。
最新バージョンのEclipseなら、可能かも知れないですね。

対処方法

不便ですが、今のところ、これが最善です。
Git Repositoriesビュー > 作業対象のプロジェクトが属するリポジトリ > Remotes > originを選択し、
右ボタン > Configure Push > Changeすると、Destination Git Repositoryダイアログが表示されます。
ここでAuthenticationにあるUser・Passwordに該当GitHubユーザ情報を入力し、
Store in Secure Storeにチェックし、Finish > Saveします(以下の画像)。
f:id:jeongman7:20170701185718j:plain
以上でPush・Pullする時、エラーは出なくなります。
但し、作業対象のプロジェクトが変わるたびに行う必要があります。
近いうちにEclipseのバージョンを変えてやってみます。

awkについて簡単整理

Linuxのテキスト処理ツールであるawkについて簡単に整理します。
awkはポイントさえ掴めば分かりやすいと思います。

awkは何者?

テキストデータから行を読み込み、文字列操作を行うLinuxのツールです。
特定条件にマッチする行から列を抽出し、ある処理を実行するといったバッチ処理に向いています。
個人的にはsedより文法(C like)が分かりやすい印象です。
ただ、簡単な処理の場合、sedより記述量が多くなるので、場合によって使い分けた方がいいでしょう。

コマンド

# テストファイル生成
echo a 1 >> test.txt
echo b 2 >> test.txt

# awk '処理内容' テキストファイル
awk '{ print $0 }' test.txt
# 結果)
# a 1
# b 2

# テキストファイルの代わりにパイプで入力
cat test.txt | awk '{ print $1 }'
# 結果)
# a
# b

# 処理内容をファイル(awk)に入れて実行。
echo '
    /a/{
        print "bingo! -> ", $0
    }
' > test.awk
awk -f test.awk test.txt
# 結果)
# bingo! ->  a 1

プログラム構造

# テキスト行を読み込む前に一回のみ、実行されるブロック。初期化などを記述します。
BEGIN {
    # コメントです。
    # ... 処理内容 ...
}

# パターンに一致する行ごとに実行されるブロック。
/パターン/ {
    # ... 処理内容 ...

    # 変数はブロック内で宣言なしで使用できる。
    # グローバルスコープなので、前の行で使用した変数を次の行でも参照・更新できる。
    cnt = cnt + 1
    
    # for, whileなどの繰り返し文も使用可能です。
    for (i = 0; i < NF; i++) {
        ...
    }
}

# 論理条件(例 : a == 1 || NF == 3)に一致する行ごとに実行されるブロック。
論理条件 {
    # ... 処理内容 ...
}

# 全ての行ごとに実行されるブロック。
{ 
    # ... 処理内容 ...
}

# 最後の行の処理が終わって一回のみ、実行されるブロック。
END {
    # ... 処理内容 ...
}

行内変数

  • $0 : 現在の行の文字列
  • $1~$x : 現在の行でx番目の列の文字列

他にも色々あります。参考のリンクを参照して下さい。

組込変数

  • NR : 現在の行の番号
  • NF : 現在の行の列数
  • FS : 入力時の区切り文字。デフォルトは半角スペース。BEGINブロックでFS = "¥t"のように設定できる。
  • OFS : 出力時の区切り文字。デフォルトは半角スペース。BEGINブロックでOFS = "¥t"のように設定できる。

他にも色々あります。参考のリンクを参照して下さい。

組込コマンド

  • print : 文字列を出力する
# 1番目の列を出力する。
cat test.txt | awk '{ print $1 }'
# 結果: 
# a
# b

# 1番目の列と2番目の列をOFS(出力時の区切り文字)で区切って出力する。
cat test.txt | awk '{ print $1, $2 }'
# 結果: 
# a 1
# b 2

# 1番目の列と2番目の列を/で区切って出力する。
cat test.txt | awk '{ print $1 "/" $2 }'
# 結果: 
# a/1
# b/2

他にも色々なコマンドがあります。参考のリンクを参照して下さい。

サンプル

キューにたまっているメールのうち、宛先メールアドレスのドメインがtest.testに該当するメールのキューID及びメールアドレスを抽出するサンプル。

# 以下はmailqコマンドで出力したキュー内容の例)
# メール一件当たり4行です。
# -Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------          <--- ヘッダー
# ABCDEF1234     7681 Fri Jun 23 18:59:27  hogehoge@test.test                <--- x行目
#           (connect to dukelab.test[192.168.0.15]:25: Connection refused)   <--- x+1行目
#                                         darekasan@test.test                <--- x+2行目
#                                                                            <--- x+3行目
# ... 省略 ...
# x行目の1列目がキューID(ABCDEF1234)です。
# x+2行目の1列目が宛先メールアドレス(darekasan@test.test)です。
# x+3行目はただの改行です。

echo '
# x行目。
# NF == 7(x行目は7つの列で構成)のように列数で判断するのもいいでしょう。
NR % 4 == 2 {
    # x行目なら、キューIDを抽出する。
    queue_id = $1
}
# x+2行目に宛先メールアドレスがある。
# NF == 1(x+2行目は1つの列で構成)のように列数で判断するのもいいでしょう。
NR % 4 == 0 && /@test.test/{
    # キューID メールアドレスを出力
    print queue_id OFS $1
}' > extract_qids.awk
mailq | awk -f extract_qids.awk
# 以下のように変数に入れてfor文で回したら、バッチ処理も可能でしょう。
# queue_ids=`mailq | awk -f extract_qids.awk`
rm extract_qids.awk

# 結果例
# ABCDEF1234 darekasan@test.test
# ... 省略 ...