プログラミング

どうもお久しぶりです。デスマ帰りの (っ´∀`)っ ゃー です。元気に生きてます。
デスマの最中でも #hbstudy や #techlion は行ってたのですが、ブログに書くだけの体力は残っていませんでした(;´Д`)
12月に入り、デスマも無事終了したので、ようやっとブログ書くことができましたw

さてさて、昨日はUSP友の会怪鳥ぷれぜんつ シェル芸人養成勉強会に行ってきました。
当日のまとめはこちら、当日のスライドはこちらになります。ぼくのブログでは、全10問のお題のうち、どう解答したかをいくつかピックアップして書くことにします。(全部書くとさすがに半日くらいかかるw)

なお、正解は1つではなく、正答例と同じ解答が得られれば、それだけ多くの解答が正解となります。ご参考までに、私の環境はCentOS 6.3で文字コードはja_JP.utf8です。

※ シェル芸とは


UNIXシェル(主にbash)のワンライナーを駆使して文字列加工を自由自在に操ることができる人をシェル芸人と呼びます。(by USP友の会会長 上田さん)

最初に感想を書きますと、tukubaiコマンドがあると確かに楽です。が、sedやawk、trを駆使して解答しても頭の体操になります。また、仕事で使うぶんには後者のほうが導入障壁は少ないはず。私のようにプログラミングスキルがない人間にとっては、新しいコマンドやツールは決して銀の弾丸ではなく、使いどころを理解できるまではおいそれと使えないかなぁ・・・と。一方、コマンド名がローマ字だったり(例えば「mojihame」とか)、何段もパイプを繋がずにコマンドライン一発で文字列整形や数値の集計ができたりするので、コマンドラインに不慣れな方にコマンドを叩いてもらうには、これもありかなとも思います。

参加者の皆さんのうち、今回は半数以上の方が猛者のようで、perlのワンライナーで解答されたり、一方でcommコマンドという知らないコマンドが出てきたりなど、新たな発見もあったりして、勉強になりました。

それではお題と私の解答を見ていきましょう。

#1 文字化けしたファイルの削除


文字化けしたファイルを生成するところから始めました。nkfコマンドが入っていなかったので、まずはyumでnkfをインストール。こちらはbaseリポジトリにあるので何も考えず「yum install nkf」でOKです。nkfがインストールされたら、以下のコマンドでワザと文字化けしたファイルを作成しました。

echo ほげ | nkf -s | xargs touch

nkfのオプション「-s」を与えると、入力した文字コードをShift-JISに変換します。これをxargsコマンド越しにtouchコマンドへ渡してあげて、文字化けした空ファイルを作ることができます。

さてさてファイルを消してみましょう。今回のお題は、カレントディレクトリに通常のASCII文字で名づけられたファイル「abc」「DEFG」もあります。これは消しちゃいけません。なので、文字化けしたファイル「のみ」を消すのが正解です。

ls [!a-zA-Z]* | xargs rm

これで、カレントディレクトリにある文字化けしたファイルのみが消えてくれました。sambaでwindowsファイル共有をしているサーバなどで役立ちそうですね。

#2 フィールド数がバラバラなファイルにある数字を足し算してみる


次のようなファイルがあったとして、これらの数字を全部足し算します。

$ cat num
1
2 3 4
5 6
7 8 9 10

恐らく参加者全員が「まずはこれらの数字を一列に並べる」から始めたでしょう。数字を一列に並べるため、私は改行をスペースに変換しました。

cat num | tr 'n' ' '

そして、exprコマンド(整数値の計算ができるコマンド)に食わせるため、スペースを「 + 」に変換してあげました。

cat num | tr 'n' ' ' | sed -e "s/ / + /g"

しかしこれでは、10の後にも加算記号がついてしまいます。なので、末尾のスペースを削除することにしました。

cat num | tr 'n' ' ' | sed -e "s/ $//g;s/ / + /g"

これで出力が「1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10」となったはずです。あとは以下のようにしてexprコマンドに食わせてあげます。

expr $(cat num | tr 'n' ' ' | sed -e "s/ $//g;s/ / + /g")

そうすると、答え「55」を求めることができました。awkを使った解答例やtukubaiコマンドを使った解答例は、上田さんのスライドを参考にしてみてください。

#3 以下のhogeファイルから、aとbについてそれぞれ一番大きな数字を求める

$ cat hoge
a 12
a 13
b 13
a 432
b 111
b 43

これは当日正解を出すことができませんでした。今もう一度復習がてら解いた答えがこれです。

for i in a b; do grep ^$i hoge | sort -k2 -n | tail -n 1; done

まず、ファイルhogeの先頭がaかbに該当するものをgrepし、sortコマンドを使って、2列目を数字順に並べ替え、それぞれ最後の1行を抜き出しています。ファイルの先頭文字列の指定はfor文のループで回しています。ここでファイルhogeの「b 43」を「b 4343」にしてみます。そして再度上記コマンドを実行すると、ちゃんとbの最大は4343であることが求められます。さらに、「b 111」を「b 111111」にしてみると、こちらも「b 111111」が最大であることがわかります。

#5 日付と曜日


1990年から2012年までの1月1日が「YYYYNNDD」形式で書かれたファイルがあったとして、何曜日が何日あるかを集計します。GNU dateでは「-f」引数が使えるので、以下のようにして直接dateコマンドに食わせて集計しました。

LANG=C date -f osyouga2 | awk '{print $1}' | sort | uniq -c

日本語だと曜日が4フィールド目になるのでこうですね。

date -f osyouga2 | awk '{print $4}' | sort | uniq -c

-fオプションが使えない場合どうしよう、と考えてみた結果、forループを使うことにしました。

for i in $(cat osyouga2); do date +%A --date=$i; done | sort | uniq -c

または

for i in $(cat osyouga2); do LANG=C date +%A --date=$i; done | sort | uniq -c

ただしこれだと曜日の並びがバラバラになってしまいます。なので、forループを入れ子にするしかなさそうです。。。

for A in 日曜日 月曜日 火曜日 水曜日 木曜日 金曜日 土曜日; do for i in $(cat osyouga2); do date +%A --date=$i; done | sort | uniq -c | grep $A; done

力技ですが、もっとエレガントな答えがあればいいなあw

#8 ファイルの比較


file2からfile1にない数字を抽出します。いずれも数字は縦に並んでいるものとします。

file1: 1 3 4 6 9
file2: 2 3 4 5 9

最初に思いついたのはdiffコマンドの結果を食わせる方法でした。

diff -y file1 file2 | egrep "|" | awk '{print $NF}'

しかし、「diff使っちゃだめ(笑)」って縛りが入ったのでこう解答しました。

cat file2 | grep -v -f file1

grepの「-v」オプションは、引数に含まれないものを標準出力に表示します。「-f」はパターンをファイルから1行ごとに読み込みます。ゆえに、これらオプションの組み合わせで同じ解答を求めることができました。

当日の会場にはさらに猛者がいて、commコマンドを使って解いていました。

comm -13 file1 file2

オプションが与えられないとcommコマンドは三列に出力を行うので、「じゃあ1列目と3列目を削っちゃえばよくね?」という至極単純な発想で、パイプを使わないぶん、巨大なファイル比較を行った場合、パイプでgrepやawkに渡すより速そうですね。

家での復習も兼ねて解いてみて気づいたのですが、正解が複数ある問題を解くというのは、本当に頭の体操になるなあ、と。そして、コマンドやオプションのボキャブラリーが多いと、イザというとき本当に役立ちます。シェル芸人としてまだまだ精進せねば。

みなさんこんにちわ。githubのアカウント作成から自作のプログラムをアップロードするまでの手順をまとめてみました。

用意するもの

• プログラム開発環境 (ここではCentOS6を前提としています)
• メールアドレス

これだけです。開発環境はroot権限のコマンドを実行できるようにしておいてください。(suコマンドでrootに昇格できるか、sudoコマンドが使える)

githubって何?

• gitのプロジェクトホスティングサービス
• 100MBまで無料で使える
• gitリポジトリを自分で作って公開できる
• WEB上から変更履歴などが参照可能
• githubで公開されている他のOSSのコードをforkして開発することが可能
• ただしバグトラッキングシステムがない
※ 出展: はてなキーワード

すべての手順を書くと長くなってしまうので、1枚のスライドにまとめてみました。それでも、1時間もあればgithubアカウント作成、リポジトリ作成、自分で作ったプログラムのアップロードまでできてしまいます。

アカウント作成方法はスライドを見てください。ここでは開発環境のサーバ側でどんな作業をするのかをまとめておきます(スライドにも書いてありますが)。

■  rpmforge リポジトリをダウンロードする
[cc lang='bash' ]wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm[/cc]

■  git パッケージをインストールする
[cc lang='bash' ]sudo yum --enablerepo=rpmforge install git[/cc]

■ SSH鍵ペアを作成する
1) すでに鍵ペアがあった場合、上書きしないように .ssh ディレクトリをバックアップする
[cc lang='bash' ]$ [ -d ${HOME}/.ssh ] && cp -pr ${HOME}/.ssh ${HOME}/DOT.ssh.$(date +%Y%m%d)[/cc]

2) ssh-keygenコマンドで鍵ペアを作成する
[cc lang='bash' ]$ ssh-keygen[/cc]
鍵ペアは、ホームディレクトリ/.ssh の下に生成されます。ホームディレクトリでlsコマンドを叩いて「そんなのねーよ」と思っても焦らないでください。「ls -la」のように、aオプションをつけると .ssh ディレクトリの存在が確認できます。sshの鍵ペアを作成したら、「id_rsa.pub」のほうをcatコマンドで開いて、マウスでクリップボードにコピーし、githubの公開鍵登録画面に貼り付けます。

成果物をアップロードしよう!

githubでアカウントを作成し、sshの公開鍵登録を行い、リポジトリを作成したあとは、開発サーバでプログラム開発してgitコマンドでアップロードしましょう。

■ 初期設定

[cc lang='bash' ]git config --global user.name "githubのユーザ名"[/cc]
[cc lang='bash' ]git config --global user.email "githubに登録したメールアドレス"[/cc]

これらは最初に1回やっておけばOKです。ただし、同じサーバで複数のgithubアカウントを切り替えて使う場合は、都度実行する必要があります。

開発サーバでリポジトリを新規作成する

例) NABEATSU というリポジトリを作成してみる

[cc lang='bash' ]mkdir NABEATSU[/cc]
[cc lang='bash' ]cd NABEATSU[/cc]
[cc lang='bash' ]git init[/cc]

リポジトリ名と開発サーバに作るディレクトリ名は必ずしも同じでなくてもよいらしいですが、後でわかりにくくなるので同じ名前にしておいたほうが無難でしょう。

READMEファイルを新規作成する

[cc lang='bash' ]touch README[/cc]
READMEファイルは、ファイル名を大文字にして作成します。この中に書き込んだテキストをローカルリポジトリにコミットし、リモートリポジトリにpushする(反映させる)までは、githubからは存在しないものとして扱われます。

READMEファイルをローカルリポジトリにコミットする

[cc lang='bash' ]git add README[/cc]
[cc lang='bash' ]git commit -m 'first commit'[/cc]
コミット後、次のページに表示するようにコミット後のメッセージをchangelogに書くよう、編集画面が出てきます。デフォルトのエディタが立ち上がるので、コミットメッセージを残しましょう。

ローカルリポジトリにgithubのリポジトリをリモートとして登録する

例) oresama アカウントで NABEATSU というリポジトリに登録する場合

[cc lang='bash' ]git remote add origin git@github.com:oresama/NABEATSU.git[/cc]
このコマンドは、リポジトリを作成して最初に実行するだけでOKです。ローカルリポジトリで何かしら編集するたびに実行する必要はありません。

ローカルリポジトリからリモートリポジトリへ修正内容を反映させる

[cc lang='bash' ]git push origin master[/cc]

ローカルリポジトリで何かしら編集し、リモートリポジトリへ反映させる都度に実行します。

自作のプログラムをアップロードしてみよう!

ここでは例として、binディレクトリの下に自作のシェルスクリプト「3.sh」を作ります。
[cc lang='bash' ]mkdir bin[/cc]
[cc lang='bash' ]cd bin[/cc]
[cc lang='bash' ]vi 3.sh[/cc]
# 成果物 3.sh を編集します

[cc lang='bash' ]cd ../[/cc]

ここまでできたら、ディレクトリの中身全部pushしてみましょう。

[cc lang='bash' ]git add *[/cc]
[cc lang='bash' ]git commit[/cc]
[cc lang='bash' ]git push -u origin master[/cc]

※ 個別のファイルをアップロードしたい場合は以下で。
[cc lang='bash' ]git add bin/3.sh[/cc]
[cc lang='bash' ]git commit[/cc]
[cc lang='bash' ]git push -u origin master[/cc]

ここまでできたら、githubの画面で確認してみてください。スライドの通りに設定したら、リポジトリ内にご自慢のプログラムがアップロードされているはずです。ね、簡単でしょう?

rubyでコマンドラインの引数に与えたサイトをダウンロードするスクリプトを作ってみた。お手本サイトのこことかここの合わせ技で、ハードコーディングされたURLではなく、引数の1番目に与えたURLを引っ張ってくる。あと、引数が空っぽだと"nil"って表示されちゃうので、エラー抑止するようにした。rubyのお手本サイトを見て自分でちょっと考えて手を加えた第一号かも。楽しい!ソース (URL01.rb) は以下。

[cc lang='ruby' ]
# coding: utf-8

unless ARGV[0].nil? then
end
if ARGV.size > 0 then
require 'open-uri'
open(ARGV[0]) do |f|
f.each do |line|
puts line
end
end
end
[/cc]

実行結果はこんな感じ。

[cc lang='text' ]
ruby URL01.rb https://nullpopopo.blogcube.info/ > index.html
[/cc]

みなさんこんにちわ。少しだけ遅くなってしまいましたが、Apacheスタートスクリプト読書会をやってきましたのでレポートします。さっそく上田会長がこんな記事こんな記事でレポートを書かれているので、全体の流れはおわかりいただけたかと思いますので、私は違う視点から書いてみたいと思います。当日の発表資料はこちらになります。

また、冒頭に @hi_saito さんによるawkでアクセスログ解析のライトニングトークもありました。

…続きを読む