(O+P)ut

習うより慣れろ、Practice makes perfect。この言葉をモットーに、Slerで働く若手インフラエンジニアが、学んだ知識を【 (O+P)ut = OutPut 】していく場です。

【Linux】とりあえず2つだけ覚える、変数のパターンマッチ

bashでは「%」や「#」を用いた変数のパターンマッチがありますが、最近使うことが増えてきたのでメモとして書いておきます。

紹介するのは、とりあえず2種類です。

${変数#パターン}
${変数%パターン}

[ファイル名][数字].[拡張子]のようなファイルを一気に処理したい場合等に使えたりして便利です。

紹介のために、以下のような4つのファイルを用意しました。

$ ls
file1.txt  file2.txt  file3.txt  file4.txt

4つのファイルをワンライナーでいじろうとすると以下の書き方が使えます。
fileで始まるファイルを変数xに格納して、echoで画面に表示しています。

$ for x in file*; do echo $x; done ⏎
file1.txt
file2.txt
file3.txt
file4.txt

さて、まずは ${変数#パターン} です。
ざっくり言えば "パターン部分を除いたファイル名の後半を取り出したい時" に使います。
正確に書けば、"変数の内容について、最初の部分とパターンがマッチした場合に、もっとも短く一致する部分を取り除いた残りの部分を返す" 役割が ${変数#パターン} にはあります。

以下を見ていただければイメージが掴めるかと思います。

$ for x in file*; do echo ${x#file}; done ⏎
1.txt
2.txt
3.txt
4.txt

これを応用すれば、file[数字].txt ではなく text[数字].txt としたい時などに使えます。

$ for x in file*; do mv $x text${x#file}; done ⏎
$ ls ⏎
text1.txt  text2.txt  text3.txt  text4.txt

次に、 ${変数%パターン} です。

こちらもざっくり言えば "パターン部分を除いたファイル名の前半を取り出したい時" に使います。
正確に書けば、"変数の内容について、最後の部分とパターンがマッチした場合に、もっとも短く一致する部分を取り除いた残りの部分を返す" 役割が ${変数%パターン} にはあります。

こちらも以下を見てください。※上の実行後に行っているので、ファイル名はtextから始まっています。

$ for x in text*; do echo ${x%.txt}; done ⏎
text1
text2
text3
text4

これを応用すれば、拡張子を一気に変えたい場合なんかに使えそうですね。

他にも、「##」や「%%」などもあるんですが、とりあえずは上の2個を覚えて必要あらば詳しく調べるのが良いかなーと思います。

【Cygwin】作業ログを自動で残す

Cygwinでちょこちょこと作業をすることが多いのですが script コマンドを使えば、簡単に作業ログを残せます。

その作業ログの取得を、自動で行えるようにした際の備忘録です。

scriptコマンドの使い方は、ターミナルで script と打てば以下のようになるかと思います。

$ script
スクリプトを開始しました。ファイルは typescript です

ここから記録が ./typescript に始まり、終了する際は exit と打てば以下のように終了します。

$ exit
exit
スクリプトを終了しました。ファイルは typescript です

オプションや引数をつけることで、ファイルを上書きしたりファイル名を指定もできます。

問題は、これを起動時に自動で実行しようとする際です。
単純に .bashrc に

script

と書くとすごいことになります。以下が私の環境で実際に上記の記述を.bashrcに行い、起動した際の出力です...

スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です
スクリプトを開始しました。ファイルは typescript です

実はscriptコマンドは新たにシェルを立ち上げる挙動をするようですので、新たなシェルが立ち上がる→scriptコマンド実行→新たなシェルが... の無限ループになるみたいですね。

$ ps
      PID    PPID    TTY       STIME COMMAND
    13052       1    ?         01:08:49 /usr/bin/mintty
     6288    1936    pty0      01:08:56 /usr/bin/ps
     1936   13052    pty0      01:08:49 /usr/bin/bash

$ script
スクリプトを開始しました。ファイルは typescript です

$ ps
      PID    PPID   TTY      STIME COMMAND
     1288    5712  pty0      01:09:01 /usr/bin/script
    11180    4176  pty1      01:09:04 /usr/bin/ps
     5712    1936  pty0      01:09:01 /usr/bin/script
     4176    1288  pty1      01:09:02 /usr/bin/bash
    13052       1  ?         01:08:49 /usr/bin/mintty
     1936   13052  pty0      01:08:49 /usr/bin/bash

上がすごい分かりやすいと思います。pty1が立ち上がってカレントシェルが変更になります。

なので、今回は簡易的に現在のシェルの親プロセスが /usr/bin/mintty な場合にのみ script を動かすようにします。

autoscript.shというスクリプトを記述しました。*1

#!/bin/sh

ppcheck=`ps | grep $PPID | grep -v pty | awk '{print $8}'`
ttynum=`echo \`tty\` | awk '{print substr($0,(length($0)+1)-1,1)}'`
nowtime=`date +"%Y%m%d%H%M%S"`
filename="${nowtime}_${ttynum}"

if [[ ${ppcheck} = "/usr/bin/mintty" ]]; then
        script -f script_log/$filename
fi

簡単に説明しますと、ppcheckで現在のシェルの親プロセスのIDでプロセスを絞り込み、ttyが?となっている/usr/bin/minttyをppcheckとして取り込んでいます。
それを、現在のttyの最後の数字と現在の時刻とするファイル名でログをとる、という感じですね。こうしておけば、scriptで立ち上がったシェルの親プロセスは/usr/bin/minttyではなくなるので、ループは起きないといった感じです。ファイル名は、時刻だけだと同時にCygwinの窓を複数立ち上げるとやっかいかなーと思ったのでttyから値を持ってきてます。特に深い意味はありません...

これを起動時に実行するために .bashrc に以下のように記載します。

. ./autoscript.sh

カレントシェルで実行するよう記述しているのは、親プロセスが変わってしまうのを防ぐためです。

これでCygwinを立ち上げると

スクリプトを開始しました。ファイルは script_log/20170129011831_0 です

$ ls script_log/
20170129011831_0

とまぁ、こんな感じで機能しています。

とりあえずこれで運用してみて、挙動が怪しい場合にはまたその都度修正していこうと思います。
Cygwinをお使いの方で、同じようなことに興味がある方はご参考ください。

*1:直接.bashrcに記述してもいいですが、テストしやすかったのでそのままshファイルを起動するという構成にしています

マージソートの可視化

ちょうと1年前くらいに
mti.hatenablog.com
といった記事を書いたんですが、そこでは選択ソートとバブルソートを可視化しました。

今回はマージソートの可視化を行います。使用するのも前回同様 R言語で。

マージソートとは、並べ替えたい配列を再帰的に分割していき、再び併合(マージ)していくことで並び替えを実現しようとするソートアルゴリズムです。

今回は配列をx、マージソート関数をf、基準値をpivotとし、基準値は配列xの平均値とします。

pivot=ave(x)[[1]]

そして、配列xの各値が基準(pivot)より大きいか小さいかで配列をleft,rightに分割

for(k in 1:length(x))
    {
      if(x[k] <= pivot)
      {    
        left <- append(left,x[k])
        count <- count + 1
      }
      else
      {
        right <- append(right,x[k])
      }
    }

分割したleft,rightを再起的にまたfにつっこむ流れですね。*1

分割の回数で上記のマージソートを可視化したものが以下になります。
f:id:mtiit:20161103103618g:plain
一秒毎に8回目までのソートの経過を可視化しました。選択ソートやバブルソートとは全く異なっていることが分かります。


おまけとして、pivotを

pivot_t=ave(x[1:2])[[1]]

とすればどうなるでしょう。直観的には、基準が前の二つの平均値となるので少し偏ったpivotで配列を分割していくことになりそうです。
同じく8回目までのソートの経過を可視化したものが以下になります。
f:id:mtiit:20161103104419g:plain
8回だけでは並べ替えきれていない感じがします。

以上、マージソートの可視化でした。*2

*1:countは配列の結合で用いる配列の引数としてfに渡してました

*2:分割と併合を繰り返したものの経過の可視化はマージソートWikipediaにも掲載されていましたのでご参考まで

【Java】APIリクエストでファイルを取得する

APIリクエストってのはWeb APIのことですね.
HTTPをベースにしてデータをやり取りするので,一般的にはWebサイトへのアクセスを行うようにブラウザを用いて操作を行います.
例えば,今回の場合ではURLを入力してGETメソッドでアクセスする*1ことで,csvファイルをアクセスできるという状況が既にあります.

そんな中で例えば「日付でURLが分かれているファイルを一気に取得したい」であったり「csvファイルの特定の項を編集しながら保存したい」といったようなことをする必要もありますよね.自分はそのような状況だったので,Javaを使ってcsvファイルを取得する方法について記述します.*2

簡単なサンプルコードは以下になります.

URL url;
try {
	String url_s = "[アクセスするURL]";
	url = new URL(url_s);
	URLConnection conn;
	conn = url.openConnection();
	InputStream in = conn.getInputStream();
	BufferedReader br = new BufferedReader(new InputStreamReader(in));
	String line;
	while ((line = br.readLine()) != null) {
		System.out.println(line);
	}
	br.close();
} catch (MalformedURLException e) {
	e.printStackTrace();
}
catch (IOException e) {
	e.printStackTrace();
}

今回は一行ずつlineを標準出力していますが,必要に応じてファイルに保存します.
また,csvファイルの各項を取り出したいのなら

token = new StringTokenizer(line, ",");
while (token.hasMoreTokens())
{
   //処理を書く
         //token.nextToken()で項を順に取得
}

をline毎に行います.

備忘録でした!

*1:要は普通にWebブラウザを使ってアクセスする

*2:おそらく他の言語ならもっと簡単?

ソートの結果を可視化してみる

Javaで学ぶデータ構造とアルゴリズム」という杉山行浩さんの本を読んでいたら,ソートの結果を可視化して図にしていた.これは面白い!と思ったので紹介させてもらうと同時に,僕も実際にソートした結果をGifにしてみました.

まずソートの可視化方法.例えば大きさ100の配列に,未ソートの状態で実数が入っている時に,配列のインデックスをx座標,その配列の値をy座標にとるというものでした.

つまり,例えば配列の大きさを1000として0~1で一様乱数を発生させると以下のようなグラフになります.
f:id:mtiit:20151003185351p:plain

これを初期状態としてソートを行っていく際の途中経過を表示していくのが,今回言っている「ソートの可視化」です.

百聞は一見に如かず.
選択ソートで可視化をしてみます.選択ソートとは,
例えば 大きさ5の配列
2 7 5 9 1
があった時に,
一番小さな 1 を 一番左に持ってきて,元あった2と入れ替える.
1 7 5 9 2
ここで一番左は整列済みなので,次は未整列な4つの中で一番小さい 2 を 一番左(整列しているところは除いて)と入れ替える
1 2 5 9 7
これを繰り返すと残りは
1 2 5 7 9
とソートが行われる,というアルゴリズムですね..

このアルゴリズムを用いて,0~1の一様分布に従った乱数1000個の配列に対して50個整列が終わる毎に出力したものをGifにしたのが下の画像です.







f:id:mtiit:20151003195514g:plain
分かりやすい...左側から整列していく様子が一目瞭然で分かる!

ついでにもう一つ.バブルソートではどのように整列が進むのでしょうか.
バブルソートとは,同じように大きさ5の配列
2 7 5 9 1
があった時に,一番右側の「1」から自分の左隣と比較して右側の方が小さければ値を交換していく.
上の例で言えば,9 と 1 を比較すると1の方が小さいので
2 7 5 1 9 となる.次は5と1の比較で
2 7 1 5 9
2 1 7 5 9
1 2 7 5 9
となって一番左側は整列済み,となる.同じようにまた 9と5を比較して,
1 2 7 5 9
1 2 5 7 9
1 2 5 7 9 となって1,2までが整列済み,というのを最後まで繰り返すアルゴリズムですね.

ではこれを同じようにGifにしてみると,どんな風に整列が進むか想像つきますかね?
正解は下の画像のようになりました.







f:id:mtiit:20151003195457g:plain
なるほど,バブルソートは隣あった数の比較を進めながら整列が進むので選択ソートよりかは途中段階においても緩やかなソートが行われていることが見て取れます.

このように,ソートのアルゴリズムを視覚的に表示させるのは,かなりいいアイデアだとは思う*1
上で紹介した本の中にはマージソートヒープソートも載っていて,「こんな整列の仕方してるのか!」と思った.

あと,今回はRでグラフを出したりしてたんですけど

plot(hoge)
dev.copy(png,file=filename)
dev.off()

でファイルネームでplotをpngファイルで保存できるんですね*2.便利でした.

*1:もちろんこの作者が考えついたとは思っていないが,僕はこの本で初めて知ったので!

*2:たしかpdfとか他の拡張子でもいける

【Mac】mdfindでiPhoneのスクショを見つけ出す

以前,Spotlightを使ってみるで,Macではmdlsやmdfindを使ってファイルの詳細な情報を使って検索をかけたりできることを自分で確認したのですが,今回はせっかくなのでそれを使ったプログラムを作ってみた*1

作ったプログラムは,iPhoneで撮った写真をPCに保存している時にスクリーンショットのみを一気に消す処理を行うものです.なんかiPhoneアプリスクリーンショットのみを消せるアプリはあるみたいなんですけど,実際にパソコン側にデータを持ってきた後ではいちいち手作業で消していくことになると思うので.

今回はシェルスクリプトで書きました.最初はjavaで書いてたんですけど,外部プログラムを実行する

Runtime.getRuntime().exec(hoge)

hogeにダブルクォーテーションを入れるとなんか思った通りに動かなかったんですよね.
今回,スクリーンショットか否かを判断するのに
kMDItemPixelHeightとkMDItemPixelWidthを使いました.スクリーンショットの画像は全てこれが一緒だったので,ここで絞ればいいかな〜という安易な発想です.もっと明示的にこいつはスクリーンショットだ!ってのがあればそっちを使いたいので見つかればそっちに切り替えるかも.

作ったプログラムの全文は以下です.

#!/bin/sh
ARRAY=(`mdfind -onlyin ./ "kMDItemPixelHeight = '$1' && kMDItemPixelWidth = '$2$"`)

mkdir SS_`date +%Y-%m-%d`
i=0
for var in ${ARRAY[@]}
do
fext="${var##*/}"
#echo $var
mv $var ./SS_`date +%Y-%m-%d`/$fext
i=$((i+1))
done

echo $i pictures moved

簡単に動作を説明しますと,引数($1,$2)にPixelHeightとPixelWidthを入れます.これはスクリーンショット画像をmdlsで見ると分かります.もちろんFinderで画像の情報を見ても「大きさ 640x1136 」といった感じで出てきます.この情報を元にmdfindで検索をかけます.このプログラムではパスが ./ となっていることからも分かる通り,画像ファイルがあるところにこのコードを置く必要があるので注意です.
次にいきなりスクリーンショットを全部消すと間違えて消しちゃった場合にまずいのでスクリーンショットと判断した画像を移すためのディレクトリを作成しています.そしてそこにどんどん移していって最後に何枚移動させたかを表示して,おしまいです('ω')/~ スクリーンショットを消すっていいましたけど,結局はそこのフォルダに確認しにいって,もし大事な写真が混じっていたらそれはコピーしてもとのところに戻せるようにしています(笑)もし消していいならFinderからディレクトリ毎消すなり rm -r で消すなりすればOKです.

実際に自分の環境でやってみました.

hoge$ ./SS_delete.sh 1136 640
64 pictures moved

となりSS_2015-06-09というフォルダができていて,64枚の画像が移動していました.
結果なんですが,一応移動した画像は全てスクリーンショットで,高さと横を逆にした ./SS_delete.sh 640 1136 でも何枚かヒットしました.この検索方法は,スクリーンショット以外の画像が入ってきそうでまだまだ改善の余地はあるとは思いますが,とりあえずはうまいこといったので良かったです.

*1:もしかしたら車輪の再発明かも...まぁ勉強になったからいいけど

【Java】パソコン一台で通信プログラムを動かしてみる

最近,通信系のプログラムを理解しないといけない必要に迫られてるんで色々と本を読んで勉強しているのですが,一通りまとまってきたので一番とっつきやすかったプログラムを紹介します.同じような境遇の方の助けになれば!ネットワークの通信では「クライアントサーバモデル」がよく出てきます.例えばネットサーフィンなんかも,クライアントである僕たちがサーバー側にリクエストを送っているっていう構図ですよね.

今回はサーバー側とクライアント側でプログラムを二つ作りますが,通信を行う上で肝心なことは,クライアント側はサーバー側のポートをしっかり指定して接続しようとする必要があるし,サーバー側はクライアント側から接続されてもいいようにポートを開放しておく必要があるってことですかね.

クライアント側ではデータを読み取るために

Socket hoge = null;

を準備して,

hoge = new Socket("IPアドレス",(整数)ポート番号);

という風にソケットを作成します.あとはファイルからの読み込みと同じように,InputStreamでデータを受け取って...といった感じです.
第一引数でIPアドレスを,第二引数でポート番号を指定するクライアント側のプログラムのコードは以下にあるのでコピペしてコンパイルすればそのまま動くと思います.

import java.io.*;
import java.net.*;

public class Readnet {

 public static void main(String[] args) {
  byte[] buff = new byte[1024];
  Socket readsocket = null;
  InputStream instr = null;
  boolean cont = true;
  
  try {
    readsocket = new Socket(args[0],Integer.parseInt(args[1]));
    instr = readsocket.getInputStream();
  }
  catch(Exception e) {
   System.err.println("Network Error");
   System.exit(1);
  }
		
  while(cont) {
   try {
    int n = instr.read(buff);
    System.out.write(buff,0,n);
    }
   catch(Exception e) {
    cont = false;
   }
  }
   
  try {
   instr.close();
  }
  catch(Exception e) {
   System.err.println("Network Error");
   System.exit(1);
  }
 }
}

次はサーバー側ですね.実はこっちも似たように

ServerSocket hoge2;

を作って

hoge2 = new ServerSocket(ポート番号,最大接続数)

とソケットを作成します.
クライアント側の違いとしては,作成したサーバーソケットに対する接続を待ち受けるオブジェクトを作り,acceptメソッドを使用することで接続を常に受け付けるようにすることですね.

Socket sock = hoge2.accept();

こんな感じ.

サーバー側のプログラムの全体は以下です.これもそのままコピペで動くはず.単なる文字列を返してもいいんですけど,せっかくなのでDateを使って日時を返します.

import java.io.*;
import java.net.*;
import java.util.Date;

public class Netclock {
 public static void main(String args[]) {
  ServerSocket servsock = null;
  Socket sock;
  OutputStream out;
  String outstr;
  Date d;
 
  try {
   servsock = new ServerSocket(6000,100);
   while(true) {
    sock = servsock.accept();
    d = new Date();
    outstr = "\n" + d.toString() +"\n";
    out = sock.getOutputStream();
    
    for(int i=0;i<outstr.length();++i) {
     out.write((int)outstr.charAt(i));
    }
    out.write('\n');
    sock.close();
   }
  }
  catch(IOException e) {
   System.exit(1);
  }
 }
}

では実際にサーバー側のプログラムを実行します.おそらく何も表示されず待機されるはず.その後にもう一つターミナルを開いて,クライアント側のプログラムを実行します.今回は同じパソコンなので,第一引数は localhost,第二引数はサーバー側のソケットを6000にしているので6000とします.
実行結果はこんな感じです.

java Readnet localhost 6000

Sun Apr 19 17:47:26 JST 2015

すごいシンプルなんですけど,これを踏まえた上でもっと理解を深めたいです.*1

*1:Javaによるネットワークプログラミング[鶴沢偉伸],TCP/IP Javaネットワークプログラミング[小高知宏] は読破した本の中でも良本でした