2019/02/15
時間の範囲を指定してログを抽出する
syslog や apache のログファイルなどから時間の範囲を指定してログを抽出したい場合、
ログファイルの時間表記をそのまま比較するのは面倒なので一度 epoch に置き換えて比較すると楽だ。
現在時刻から epoch は date (1) のフォーマット指定 '+%s' を利用する事で取得できるが、
指定した時刻から epoch を取得する汎用的な手法は現状では存在しないので今回は GNU date の '-d' オプションを利用する。
例として combined 形式の apache のログの時間範囲を抽出してみる。
combined 形式の apache のログは以下のフォーマットとなっているので、時刻は空白区切りの4番目のフィールドに格納されている。
そこで、ログの行毎に4番目フィールドの時刻を epoch に変換して比較する事で指定された時間内のログを抽出できる。
192.168.15.134 - - [14/Feb/2019:03:35:54 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" 192.168.99.23 - - [14/Feb/2019:07:20:35 +0900] "GET / HTTP/1.1" 200 1920 "https://www.bsdhack.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36; 360Spider" 192.168.157.241 - - [14/Feb/2019:09:28:45 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" 192.168.15.143 - - [14/Feb/2019:09:58:44 +0900] "GET /bsdhack.css HTTP/1.1" 200 3246 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" 192.168.93.5 - - [14/Feb/2019:12:03:47 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1"
ただし、標準的な apache のログに格納されている時刻情報はそのままでは date (1) の '-d' オプションでは変換できない。
$ date -d "14/Feb/2019:09:28:45" date: invalid date `14/Feb/2019:09:28:45'そこで、 まずは date (1) で変換できる様な形式に変換してから変換する。 そのためには日付けの区切り文字 '/' と、日付け部と時間部を分割している ':' をスペースに置換すれば良い。
$ date -d "14 Feb 2019 09:28:45" Thu Feb 14 09:28:45 JST 2019全体の流れとしてはログファイルから1行ずつ読み込み、時刻フィールドを epoch に変換して基準時刻と比較する処理を行う。 今回は全ての処理を awk (1) を利用して実装してみる。
1#!/bin/sh 2# $1: 開始時間 -- date(1) が認識できる形式 3# $2: 終了時間 -- date(1) が認識できる形式 4# $3: ログファイル 5 6# 開始時間 7start=$(date '+%s' -d "${1}") 8# 終了時間 9end=$(date '+%s' -d "${2}") 10# ログ抽出 11awk -v "start=${start}" -v "end=${end}" '{ 12 # 時刻フィールドを date(1) が認識できる形式に変換 13 gsub(/[][/]/, " ", $4) 14 sub(/:/, " ", $4) 15 # ログの各行について時刻を epoch に変換する 16 # $4 にはスペースが含まれているので全体をクォートする 17 cmd = sprintf("date +%%s -d '"'%s'"'", $4); 18 # date(1) を実行して epoch を変数 s に取得 19 cmd | getline s; 20 # コマンド実行でオープンされたディスクリプタをクローズ 21 close(cmd); 22 # 行が範囲内なら出力する 23 if(start <= s && end >= s) 24 print $0 25}' ${3}
これで開始時間から終了時間の間のログを抽出できる。
行毎に時間の変換処理を実行するので実行に時間はかかってしまうのが現状の問題点。
2019/01/31
とあるサイトが変更になった時になるべく確実に変更通知を受け取りたい
なるべく確実に変更通知を受け取るためには、普段携行している iPhone への通知が一番良さそうに思う。
iPhone への通知で一番最初に思い浮かぶのは PUSH通知 (APNS) なのだが、
APNS を利用するためには Apple への Developer 登録などが必要で、そう簡単に送る事ができないと思う
(Developer 登録などが不要で簡単に PUSH 通知を送る方法があれば是非教えて下さい)。
そこで iPhone で受信しているアドレスにメールを送信し、
更に普段からチェックしている slack で自分宛にダイレクトメッセージを送信する事にした。
iPhone の設定によりメールを受信した時や slack でダイレクトメッセージが投稿された時の通知を有効にする事で、
変更通知がなるべく確実に受け取れる事になると思う。
そのために以下のスクリプトを Linux などが稼働しているサーバ設置して cron などにより自動実行する。
1#!/bin/sh 2 3# site 4url="https://www.example.com" 5old=${HOME}/.old.html 6new=${HOME}/.new.html 7# for mail 8addr="mail@exapmle.net" 9subject="[SITE CHANGEED]" 10body="SITE CHANGED: ${url}" 11# for slack 12slack="https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYY/ZZZZZZZZZZZZZZZZZZZZZZZZ" 13name="name" # post name 14channel="#channel" # post channel 15 16curl -s ${url} > ${new} 17test -f ${old} && 18cmp -s ${old} ${new} || echo "${body}" | mail -s "${subject}" ${addr} && 19curl -sLX POST --data-urlencode "payload={\"channel\": \"${channel}\", \"username\": \"${name}\", \"text\": \"${body}\"}" ${slack} 20 21mv $new $oldslack でチャンネルではなくダイレクトメッセージを送信する場合は channel の値を適切に変更する。
web を検索すると `channel: @名前` とすれば良いとの記事を多く見かけるが、少なくとも最近の slack ではエラーとなってしまう。
その場合、slack の web 画面の左側に表示されている `ダイレクトメッセージ` をクリックした時に遷移するページの URL の最後の部分 (https://XXX.slack.com/messages/YYYYY/ の YYYYY の部分) を指定するとダイレクトメッセージが送信できる。
2017/11/29
迷惑メール対策
最近、自宅に届く迷惑メールの量が急増しているのでドメイン単位で受信を拒否するスクリプトを作成した。
自宅メールサーバ環境では mh (1) を利用しているので、迷惑メールは spam フォルダに格納する事を想定している。
メールの自動振り分け処理も mh に付属の slocal (1) コマンドを利用しているので、
迷惑メイルの送信元ドメインを ${HOME}/.maildelivery に格納する様にしている。
1domains=/etc/postfix/generics-domains # 自分のドメインを格納しているファイル 2spamdir=$HOME/.Mail/spam # 迷惑メールを格納しているディレクトリ (1メール1ファイル) 3delivery=$HOME/.maildelivery # maildelivery ファイル 4tmpdir=/tmp/spam.$$ # 一時ファイル 5reject="/etc/postfix/reject" # postfix リジェクトファイル 6count=0 7 8if [ $(ls -1 ${spamdir} | wc -l) -gt 0 ] 9then 10 # spam ディレクトリのメールファイルから From を取得 11 for i in $(sed -n '/^From/ s/.*<.*@\(.*\)>.*/\1/gp' ${spamdir}/* | sort | uniq) 12 do 13 # From が自分のドメインに詐称されていない送信ドメインを maildelivery ファイルに "destroy" として追加 14 grep -q ${i} ${domains} || grep -q "\"@${i}\"" ${delivery} || { printf "from\t\"@%s\"\t\t\tdestroy\tA\t-\n" $i; count=$((count + 1)); } 15 done >> ${delivery} 16 17 # 迷惑メールがある場合 18 if [ ${count} -gt 0 ] 19 then 20 # 迷惑メールの学習 21 mkdir -p ${tmpdir} 22 cp -r ${spamdir} ${tmpdir} 23 sa-learn --spam ${tmpdir} 24 rm -r ${tmpdir} 25 fi 26 27 # 迷惑メール削除 28 rmm all +spam 29 30 # reject ファイル作成 31 awk '{ 32 if($1 == "from" && $3 == "destroy" && $2 !~ "@\"$"){ 33 gsub("[\"@]", "", $2) 34 if ($2 ~ /.+\..+/) 35 printf "%s DISCARD\n", $2; 36 } 37 }' ${delivery} > ${reject} 38 # HASH 形式に変換 39 postmap ${reject} 40 # 再読み込み 41 service postfix reload 42fi
メールを処理する時に迷惑メールは spam フォルダに仕分ける必要はあるが、このスクリプトを crontab に仕込んでおけば mh (1) の spam フォルダに格納されているメールの送信元ドメインからのメールは拒否出来る様になる。
reject ファイルが肥大してしまうのが難点なのだが迷惑メールは目に見えて減ったので重宝している。
2017/11/06
SSL 証明書の更新チェック
最近は Let's Encrypt を利用する機会が多くなっているので SSL 証明書の更新は基本的に自動で実行しているが、
通常の SSL 証明書の場合は有効期間のチェックが必要なので定期的にチェックするスクリプトを作成した。
このスクリプトは SSL 証明書から有効期限を取得し現在の日付と比較してチェックを行い、
- 有効期間が 30 日以内の場合は 1 日に 1 度
- 有効期間が 10 日以内の場合は実行するたび
ubuntu サーバでの動作を前提としたスクリプトなので SSL 証明書ファイルは /etc/apache2/sites-enables/0000-default-ssl.conf から取得しているが、 SSL 証明書ファイルを直接指定する事で汎用的に動作させる事が可能となっている。
slack に投稿するためには予め slack にアクセスし Webhook URL を取得する必要がある。
1#!/bin/sh 2# SSL 証明書の有効期限をチェックして slack に通知する 3# 1日数回の実施 (10:00 / 13:00 / 16:00 / 18:00) を想定している 4 5host=${1:-`hostname`} 6cert=$(awk '{ gsub(/#.*/, ""); if($1 == "SSLCertificateFile") print $2; }' /etc/apache2/sites-enabled/000-default-ssl.conf) 7check=/tmp/sslcheck-send 8 9# slack の API フック指定 10target='https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 11user="${host} SSLチェック" 12 13# リミット 14soft=$((30 * 24 * 60 * 60)) 15hard=$((10 * 24 * 60 * 60)) 16 17send_slack() 18{ 19 20 icon=${1} 21 shift 22 23 curl -X POST -H 'Content-type: application/json' --data '{"text": "'"${*}"'", "icon_emoji": "'"${icon}"'", "username": "'"${user}"'" }' ${target} > /dev/null 2>&1 24 25} 26 27# 証明書から有効日を取得 28limit=$(date -d "$(openssl x509 -noout -text -in ${cert} | sed -n '/Not After/s/.* : \(.*\).*/\1/p')" '+%Y/%m/%d %H:%M:%S') 29limit_s=$(date -d "${limit}" '+%s') 30today=$(date -d "00:00:00" '+%Y/%m/%d %H:%M:%S') 31today_s=$(date -d "${today}" '+%s') 32 33current_s=$((${limit_s} - ${today_s})) 34current=$((current_s / (24 * 60 * 60))) 35 36if [ ${current_s} -lt 0 ] 37then 38 # 証明書期限切れ 39 send_slack ":red_circle:" "*ホスト${host}のSSL証明書の期限が${limit}で終了しています。大至急更新作業をして下さい。*" 40 echo ${update} > ${check} 41elif [ ${current_s} -le ${hard} ] 42then 43 # 証明書期限まで残り 10 日以内 44 send_slack ":bangbang:" "_ホスト${host}のSSL証明書の期限が${limit}までです (残り${current}日)。至急更新作業をして下さい。_" 45elif [ ${current_s} -le ${soft} ] 46then 47 # 証明書期限まで残り 30 日以内 (1日1回通知) 48 update=$(date '+%m%d') 49 if [ -f ${check} -a "$(cat ${check})" -eq ${update} ] 50 then 51 : 52 else 53 send_slack ":warning:" "ホスト${host}のSSL証明書の期限が${limit}まです (残り${current}日)。更新作業をして下さい。" 54 echo ${update} > ${check} 55 fi 56fi
このスクリプトを crontab などで一日数回自動的に実行する様に設定すると、定期的に SSL 証明書の有効期限が確認できる。
slack を利用していない場合、send_slack の部分を変更する事でメール送信などに変更する事ができる。
メール送信する場合のサンプルも以下に示す。
この例では POSIX 準拠のコマンド以外に nkf(1) と base64(1) が必要となっている。
1send_mail() 2{ 3 4 sendmail="/usr/sbin/sendmail -i -t" 5 from="送信元メイルアドレス" 6 to="受信先メイルアドレス" 7 subject=$(echo "サブジェクト" | nkf -WM) 8 body=$(echo ${*} | sed -e "s/;/\n/g" -e "/^\$/s/\$/\r/" -e "/[^\r]\$/s/\$/\r/" | base64) 9 10 cat << EOF | ${sendmail} 11From: ${from} 12To: ${to} 13Subject: ${subject} 14 15${body} 16EOF 17 18}
2017/09/07
AAC を MP3 に変換する
Mac では通常だとオーディオファイルは AAC 形式か ALAC 形式で保存していて、ファイルの拡張子は通常 "m4a" を利用している。
これらのファイルを一般的な mp3 形式に変換する場合、特に Mac では iTunes などの GUI アプリケーションを利用するのが普通なのだが、
多数のファイルを一括で変換する場合など GUI アプリケーションだと操作が面倒になる。
そこで、 AAC 形式のオーディオファイルを mp3 形式に変換するスクリプトを作ってみた。
オーディオファイルを mp3 形式に変換するコマンドはいくつも存在するが、今回は Mac で導入が簡単な lame を利用する。
lame は AAC 形式や ALAC 形式には対応していないので、こちらも Mac で導入が簡単な faad を利用して AAC / ALAC 形式のオーディオファイルを
一度 wav 形式に変換してから lame を利用して mp3 形式に変換する。
利用の前提として faad と lame が必要なので、それぞれを MacPorts や Homebrew によりインストールする。
$ sudo port install faad2 lame : : ---> Fetching archive for faad2 ---> Attempting to fetch faad2-2.7_0.darwin_16.x86_64.tbz2 from http://kmq.jp.packages.macports.org/faad2 ---> Attempting to fetch faad2-2.7_0.darwin_16.x86_64.tbz2.rmd160 from http://kmq.jp.packages.macports.org/faad2 ---> Installing faad2 @2.7_0 ---> Activating faad2 @2.7_0 ---> Cleaning faad2 ---> Updating database of binaries ---> Scanning binaries for linking errors ---> No broken files found.
次に PATH が通ったディレクトリに以下のスクリプトを設置して適切な実行権限を付与しておく。
このスクリプトでは AAC / ALC 形式と mp3 形式が混在している事を想定しており、
mp3 ファイルの場合はそのままコピーする様にしている。
1#!/bin/sh 2# mp3 ファイルを格納するためのディレクトリ 3top="${HOME}/mp3" 4 5find . | while read line 6do 7 if [ -d "${line}" ] 8 then 9 mkdir -p "${top}/${line}" 10 else 11 dst=${top}/$(echo "${line}" | sed 's/m4a/mp3/') 12 if [ "${line##*.}" = "m4a" ] 13 then 14 wav=${top}/$(echo "${line}" | sed 's/m4a/wav/') 15 faad -q "${line}" -o "${wav}" 16 lame --quiet -h -b 192 "${wav}" "${dst}" 17 rm "${wav}" 18 else 19 cp "${line}" "${dst}" 20 fi 21 fi 22done準備ができたらオーディオファイルが格納されているディレクトリでスクリプトを実行すれば mp3 形式のオーディオファイルが作成される。
以下の例ではファイル名を convert.sh として ${HOME}/bin 以下に設置している。
$ vi $HOME/bin/convert.sh $ chmod 755 $HOME/bin/convert.sh $ cd $HOME/Music/iTunes/iTunes\ Music $ convert.sh
2016/11/21
画像の回転スクリプト
blog などに添付する画像を回転するためのスクリプト。
回転処理は ImageMagick の rotate (1) コマンドで行うのだが、
一部のブラウザでは exif 情報に含まれる Orientation の値で画像を回転して表示するので、
回転した後で exif の Orientation の情報を修正している。
exif 情報の取得と設定は Perl モジュール Image-ExifTool を利用している。
getor: exif 情報から Orientation を取得する。
1#!/usr/bin/perl -w 2use Image::ExifTool ':Public'; 3my $file = shift or die "Please specify filename"; 4 5$exifTool = new Image::ExifTool; 6$exifTool->ExtractInfo($file); 7 8print $exifTool->GetValue('Orientation') . "\n";
setor: Orientation に水平 Horizontal (normal) を設定する。
1#!/usr/bin/perl -w 2use Image::ExifTool ':Public'; 3my $file = shift or die "Please specify filename"; 4 5$exifTool = new Image::ExifTool; 6$exifTool->SetNewValue('Orientation', 'Horizontal (normal)'); 7$exifTool->WriteInfo($file);
引数で指定されたファイルを回転して Orientation を設定する。
1#!/bin/sh 2for i in $* 3do 4 n=$(getor $i | awk '/^Rotate/{ print $2 }') 5 if [ ${n:-0} -gt 0 ] 6 then 7 convert -rotate $n $i /tmp/_$$.jpg && 8 mv /tmp/_$$.jpg $i 9 setor $i 10 fi 11done
2016/11/22 追記
以前作成した Exif 情報から GPS の緯度/経度情報を削除する を元に 全てシェルスクリプトで画像を回転するスクリプトを作ってみた。dd (1) を利用してバイナリファイルの入出力を行い、 Exif 情報の取得と更新を実施している。
1#!/bin/sh 2# 画像を回転し Exif 情報の 'Orientation' を設定する 3# 4 5# 6# Endian を考慮して 2 バイト数字を取得する 7# 8# $1: ファイル 9# $2: オフセット 10# 11get2Byte() 12{ 13 14 dd ${conv} if="${1}" bs=1 skip=$((${skip} + ${2})) count=2 2> /dev/null | od -d | awk '/^0000000/{ print $2 }' 15 16} 17 18# 19# 任意の長さの文字列を取得する 20# 21# $1: ファイル 22# $2: オフセット 23# $3: 文字列長 24# 25getString() 26{ 27 28 dd if="${1}" bs=1 skip=$((${skip} + ${2})) count=${3} 2> /dev/null 29 30} 31 32# 33# 0th IFD データの解析処理 34# 35# $1: ファイル 36# $2: 0th IFD のタグ数 37# 38ifd() 39{ 40 41 local i offset 42 43 i=0 44 45 while [ ${i} -lt ${2} ] 46 do 47 # 0th IFD のタグ情報 48 offset=$((${3} + 22 + (${i} * 12))) 49 # Orientation の場合はオフセットを表示して終了 50 if [ $(get2Byte "${1}" ${offset}) -eq 274 ] 51 then 52 echo ${offset} 53 break 54 fi 55 i=$((i + 1)) 56 done 57 58} 59 60# 61# Orientation 情報の取得 62# 63# $1: ファイル 64# $2: Orientation IFD のオフセット 65# 66getOrient() 67{ 68 69 echo $(get2Byte "${1}" $((${2} + 8))) 70 71} 72 73# 74# Orientation 情報のセット 75# 76# $1: ファイル 77# $2: Orientation IFD のオフセット 78# 79setOrient() 80{ 81 82 echo -ne "\00\01" | dd conv=notrunc of="${1}" bs=1 seek=$((${2} + 8 + ${skip})) 2> /dev/null 83 84} 85 86# 87# ファイルフォーマットから skip するバイト数を取得 88# 89# $1: ファイル 90# 91getSkip() 92{ 93 94 if [ "$(getString "${1}" 6 4)" = "Exif" ] 95 then 96 # Exif 情報格納 97 echo 0 98 elif [ "$(getString "${1}" 24 4)" = "Exif" ] 99 then 100 # JFIF 情報格納 101 echo 18 102 else 103 # Exif 情報なし 104 echo -1 105 fi 106 107} 108 109# 110# convert(1) のオプション取得 111# 112# $1: ファイル 113# 114get_conv() 115{ 116 117 offset=$(ifd "${1}" $(get2Byte "${1}" 20)) 118 if [ ${offset:-0} -gt 0 ] 119 then 120 case $(getOrient "${1}" ${offset}) in 121 2 ) opt="-flip";; # 上下反転 122 3 ) opt="-rotate 180";; # 180度回転 123 4 ) opt="-flop";; # 左右反転 124 5 ) opt="-flop -rotate 90";; # 上下反転+270度回転 125 6 ) opt="-rotate 270";; # 90度回転 126 7 ) opt="-flip rotate 270";; # 上下反転+90度回転 127 8 ) opt="-rotate 90";; # 270度回転 128 esac 129 echo ${opt} 130 fi 131 132} 133 134# メイン処理 135for i in $* 136do 137 skip=$(getSkip "${1}") 138 if [ ${skip} -ge 0 ] 139 then 140 # Endian 141 test $(getString "${1}" 12 2) = "MM" && conv="conv=swab" 142 opt=$(get_conv $i) 143 if [ -n "${opt}" ] 144 then 145 # 画像を回転 146 convert ${opt} "${i}" /tmp/_$$.jpg && mv /tmp/_$$.jpg "${i}" 147 # conviert(1) により TFIF 情報が付加される 148 skip=$(getSkip "${i}") 149 setOrient "${i}" $(ifd "${i}" $(get2Byte "${i}" 20)) 150 fi 151 fi 152done
2016/11/23 追記
convert (1) だけで出来る事が判明
$ convert -auto-orient 入力ファイル.jpg 出力ファイル.jpg
更に identify (1) を利用すると Exif 情報も取得できるので
ImageMagic が導入されている場合は以下のスクリプトで完結する。
1#!/bin/sh 2for i in $* 3do 4 test "`identify -verbose $i | awk '/exif:Orientation/{ print $2 }'`" != 1 && 5 convert -auto-orient $i /tmp/_$$.jpg && 6 mv /tmp/_$$.jpg $i 7done
2016/05/11
行抜き出しの速度
某所で話題になったのでテキストファイルの行抜き出し速度を検証してみた。
検証用のデータは若干サイズ的に不足があるのだが、
以下のシェル芸で 24M のランダムなテキストデータを作成した。
$ for i in `seq 50000`; do printf "%05d:%s\n" $i `LC_CTYPE=C tr -c -d 'a-zA-Z0-9' < /dev/urandom | dd bs=501 count=1 2> /dev/null`; done > base $ ls -l base total 49616 -rw-r--r-- 1 mitz staff 25400000 5 11 12:15 base作業は全て iMac 上で実施した。
$ uname -a Darwin coredump.local 12.6.0 Darwin Kernel Version 12.6.0: Wed Mar 18 16:23:48 PDT 2015; root:xnu-2050.48.19~1/RELEASE_X86_64 x86_64
検証は以下の手段による行の抜き出しの100回繰り返しを10回実行した時の平均た時間を計測した。
- tail(1) と head(1) の組み合わせ (入力ファイルを tail(1) の引数として直接指定)
- tail(1) と head(1) の組み合わせ (入力ファイルを cat(1) を利用してパイプ経由で tail(1) に通知)
- head(1) と tail(1) の組み合わせ (入力ファイルを head(1) の引数として直接指定)
- head(1) と tail(1) の組み合わせ (入力ファイルを cat(1) を利用してパイプ経由で head(1) に通知)
- sed 単体 (入力ファイルを sed(1) の引数として直接指定)
- sed 単体 (入力ファイルを cat(1) を利用してパイプ経由で sed(1) に通知)
- awk 単体 (入力ファイルを awk(1) の引数として直接指定)
- awk 単体 (入力ファイルを cat(1) を利用してパイプ経由で awk(1) に通知)
- sed 単体で検出後即時終了 (入力ファイルを sed(1) の引数として直接指定)
- sed 単体で検出後即時終了 (入力ファイルを cat(1) を利用してパイプ経由で sed(1) に通知)
- awk 単体で検出後即時終了 (入力ファイルを awk(1) の引数として直接指定)
- awk 単体で検出後即時終了 (入力ファイルを cat(1) を利用してパイプ経由で awk(1) に通知)
結果は以下の通り。
自分の予想に反して head(1) と tail(1) の組み合わせが一番早くて驚き。
OS X の標準コマンドなので GNU 拡張された head(1) や tail(1) ではない筈なので、
そもそもオリジナルの head(1) の実装が良いのだろうか。
追記
twitter 及び当ブログのコメントで指摘を頂いたので sed(1) と awk(1) で 行を検出した場合は終了する場合の時間も計測してみました。head(1) と tail(1) をパイプで繋いだ場合も、 2番目のコマンドで1行出力すると SIGPIPE が発生して 1番目のコマンドも終了しているのだろうか?
| tail -n 4036 base | head -1 |
|
||||||
| cat base | tail -n 4036 | head -1 |
|
||||||
| head -n 4036 base | tail -1 |
|
||||||
| cat base | head -n 4036 | tail -1 |
|
||||||
| sed -n '4036p' base |
|
||||||
| cat base | sed -n '4036p' |
|
||||||
| awk '{ if(NR==4036) print $0 }' base |
|
||||||
| cat base | awk '{ if(NR==4036) print $0 }' |
|
||||||
| sed -n '4036{p;q;}' base |
|
||||||
| cat base | sed -n '4036{p;q;}' |
|
||||||
| awk '{ if(NR==4036){ print $0; exit;} }' base |
|
||||||
| cat base | awk '{ if(NR==4036){ print $0; exit;} }' |
|
2016/04/06
文字列の読み方を表示する
自動生成したパスワードなどランダムな文字列をメイルなどで他人に伝える場合、
フォントによっては判別しずらい文字がある。
それらの判別しづらい文字を正確に伝えるために、
数字とアルファベットの読みを表示する簡単なプログラム。
標準入力から読み込んだ英数字の読みを表示し英数字以外は無視する。
1#!/bin/sh 2 3awk 'BEGIN{ 4 p["0"] = "ぜろ"; 5 p["1"] = "いち"; 6 p["2"] = "に"; 7 p["3"] = "さん"; 8 p["4"] = "よん"; 9 p["5"] = "ご"; 10 p["6"] = "ろく"; 11 p["7"] = "しち"; 12 p["8"] = "はち"; 13 p["9"] = "きゅう"; 14 p["a"] = "エー"; 15 p["b"] = "ビー"; 16 p["c"] = "シー"; 17 p["d"] = "ディー"; 18 p["e"] = "イー"; 19 p["f"] = "エフ"; 20 p["g"] = "ジー"; 21 p["h"] = "エイチ"; 22 p["i"] = "アイ"; 23 p["j"] = "ジェイ"; 24 p["k"] = "ケー"; 25 p["l"] = "エル"; 26 p["m"] = "エム"; 27 p["n"] = "エヌ"; 28 p["o"] = "オー"; 29 p["p"] = "ピー"; 30 p["q"] = "キュー"; 31 p["r"] = "アール"; 32 p["s"] = "エス"; 33 p["t"] = "ティー"; 34 p["u"] = "ユー"; 35 p["v"] = "ブイ"; 36 p["w"] = "ダブリュー"; 37 p["x"] = "エックス"; 38 p["y"] = "ワイ"; 39 p["z"] = "ゼット"; 40 } 41 { 42 n = split(tolower($0), words, ""); 43 for(i=1; i<=n; i++){ 44 v = v g p[words[i]]; 45 g = " " 46 } 47 printf "%s (%s)\n", $0, v; 48 }'
nkf (1) や kakashi (1) などで出来ないかとちょっとだけ探ったが、
出来ない様だったので適当にでっち上げた。
他にもっと良いものがあれば是非教えて下さい。
読みの部分を「アルファ」「ブラボー」「チャーリー」等とすれば フォネティックコードにも変換できる。
2015/10/01
Shuca (朱夏) - 日本語対応のサマライザ
MOONGIFTで紹介されていた
Shuca という日本語に対応したサマライザを試してみた。
サマライザとは文章を解析し重要な部分だけを抜き出してくれる要約エンジンの事で、
この処理が自動で実施できると非常に便利になりそうなので
早速 CentOS 6.6 に Shucha を導入してみる。
Shuca は予め分かち書きや構文解析された文章を入力ソースとして受け付ける。 そのため、別途、形態素解析システムの JUMAN や 日本語構文・格・照応解析システムの KNP が必要となるので それらのツールも同時にインストールする。
- Shuca のインストール
-
Shuca 本体は Python で記述されており GitHub でソースが公開されている。
基本的にインストール作業は不要で GitHub からダウンロードしたファイルを任意のディレクトリに展開すれば良い。
今回は /usr/local 以下に全てのファイルを展開し、 実行可能ファイルは /usr/local/bin 以下に設置した。
$ wget https://github.com/hitoshin/shuca/archive/master.zip $ unzip master.zip $ sudo mkdir -p /usr/local/{bin,libexec,dic} $ sudo cp -p shuca-master/lib/* /usr/local/bin/. $ sudo cp -p shuca-master/dic/* /usr/local/dic/. $ sudo cp -p shuca-master/libexec/* /usr/local/libexec/.
インストールが終了したら同梱されているサンプルデータで動作を確認する。$ Shuca.py < shuca-master/dat/sample.knp.txt JR東海は4月16日、山梨リニア実験線で同日に行ったL0系の高速有人走行試験において、590キロメートル毎時を記録したと発表した。 2003年12月2日に同社のMLX01形が記録した鉄道の世界最高速度、581キロメートル毎時を11年4ヶ月ぶりに9キロメートル毎時更新する形となった。 JR東海は、「今後も開業に向けさまざまな試験を行っていく」とコメントしている。
- JUMAN のインストール
-
JUMAN は京都大学 大学院情報学研究科の黒崎・河原研究室が開発した日本語形態素解析システムで、
日本語の文章を形態素ごとに分かち書きし品詞などの情報を付加している。
$ wget http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/juman/juman-7.01.tar.bz2 $ tar xvf juman-7.01.tar.bz2 $ cd juman-7.01 $ ./configure --prefix=/usr/local $ make $ sudo make install
- KNP のインストール。
-
KNP も京都大学 大学院情報学研究科の黒崎・河原研究室が開発した日本語構文・格・照応解析システムで、
JUMAN で解析された結果から文節および基本句間の係り受け関係、格関係、照応関係を出力する。
KNP は zlib を利用しているので、予め zlib のインクルードファイル、ライブラリ等をインストールしておく。$ sudo yum -y install zlib-devel $ wget http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/knp/knp-4.14.tar.bz2 $ tar xvf knp-4.14.tar.bz2 $ cd knp-4.14 $ ./configure --prefix=/usr/local --with-juman-prefix=/usr/local $ make $ sudo make install
- 動作の確認
-
これらがインストールできたら Shuca に同梱されているデータで動作の検証を実施する。
$ juman < shuca-master/dat/sample.snt.txt | knp # S-ID:1 KNP:4.14-CF1.1 DATE:2016/03/04 SCORE:-63.73058 JR──┐ 東海は──┐ 4──┐ │ 月──┐ │ 16──┐ │ 日、──┤ 山梨──┐ │ リニア──┐ │ 実験──┐ │ 線で──┐ │ 同日に──┐ │ │ 行った──┐ │ │ L0──┐ │ │ │ 系の──┤ │ │ 高速──┐ │ │ │ 有人──┐ │ │ │ 走行──┤ │ │ 試験に──┐ │ │ おいて、──┤ │ 590──┐ │ │ キロメートル──┤ │ │ 毎時を──┤ │ 記録したと──┤ 発表した。 EOS : : :
ここまで動作が確認できたら php を利用して簡単な動作検証環境を作成する。
Shcha に附属されていたサンプルデータから KNP の実行時オプションは
"-simple -normal" だと推察してみた。
この php スクリプトは入力された URL からコンテンツを取得して
jUMAN で形態素解析を実施してKNP で構文解析を実施した上で
Shcha を実行して要約を取得する。
下準備として改行コードの統一、HTML タグの除去、空行や空白のサプレス、
更に KNP の制限により半角文字を全角文字に変換している。
あくまでも動作サンプルのためのスクリプトなので、
エラー処理や html entity の処理など実施していない。
1<html> 2 <head> 3 <title> 4 shuca 要約 5 </title> 6 </head> 7 <body> 8 <center> 9<?php 10 mb_internal_encoding("UTF-8"); 11 12 define("juman", "/usr/local/bin/juman"); 13 define("knp", "/usr/local/bin/knp -simple -normal -cf-cache"); 14 define("shuca", "/usr/local/bin/Shuca.py -l 500 "); 15 16 if(strlen(($url = $_REQUEST["url"]))){ 17 print "$url<br><br>"; 18 19 $start = gettime(); 20 21 $content = 22 mb_convert_kana( /* 半角文字を全角に変換 */ 23 preg_replace("/ +/", " ", /* 複数のスペースを1個に置換 */ 24 preg_replace("/^ *\n/m", "", /* 空行を削除 */ 25 strip_tags( /* html タグを削除 */ 26 preg_replace('!<style.*?>.*?</style.*?>!is', '', /* <style> タグとその内容を削除 */ 27 preg_replace('!<script.*?>.*?</script.*?>!is', '', /* <script> タグとその内容を削除 */ 28 str_replace("\r", "", /* CR を削除 */ 29 file_get_contents($url))))))), "ASKV"); 30 31 $buf = ""; 32 $desc = array( 33 0 => array("pipe", "rb"), /* stdin: pipe */ 34 1 => array("pipe", "wb"), /* stdout: pipe */ 35 2 => array("file", "/dev/null", "w"), /* stderr: /dev/null */ 36 ); 37 38 if($pp = proc_open(sprintf("%s | %s | %s", juman, knp, shuca), $desc, $pipe)){ 39 fwrite($pipe[0], $content); 40 fclose($pipe[0]); 41 42 while(!feof($pipe[1])) 43 $buf .= nl2br(fread($pipe[1], 1024)); 44 fclose($pipe[1]); 45 46 proc_close($pp); 47 48 print <<< EOF 49 {$buf} 50EOF 51 ; 52 printf("処理時間: %f 秒", gettime() - $start); 53 } 54 } 55 else{ 56 print <<< EOF 57 <form> 58 <input type='text' name='url' size='50'> 59 <input type='submit' value='要約'> 60 </form> 61EOF 62 ; 63 } 64 65function gettime() 66{ 67 68 $t = gettimeofday(); 69 return((float)($t["sec"] + $t["usec"] / 1000000.0)); 70 71} 72 73 74?> 75 </center> 76 </body> 77</html>
2015/09/03
国別フィルタの自動生成ツール (CentOS 版)
以前 FreeBSD 版の 国別フィルタの自動生成ツール を紹介したのだが、
最近 CentOS でもサーバを運用しているので CentOS 版を作成したので公開する事にした。
機能は FreeBSD 版同様 APNIC から IPアドレスの割当リストを取得して、
IPアドレスを CIDR 型式に修正した上で
iptables (8) のコマンドラインパラメタを自動生成している。
今回は /etc/init.d/iptables を実行してフィルタを初期化した上で、
state RELATED,ESTABLISHED が含まれる行の次の行にルールの追加までを実行する。
1#!/usr/bin/perl 2# KRフィルタ 3use Socket; 4 5# 拒否する国コード 6@country = ('KR', 'CN'); 7# IPアドレス一覧取得URL 8$url = "http://ftp.apnic.net/stats/apnic/delegated-apnic-latest"; 9 10# iptables 初期化 11system("/etc/init.d/iptables restart"); 12 13# 挿入位置を取得 (接続済みパケットは許可する設定の次に挿入) 14if(open(IN, "iptables -L INPUT --line-number|")){ 15 while(<IN>){ 16 if(/^(\d+).*RELATED,ESTABLISHED.*/){ 17 $rule = $1 + 1; 18 last; 19 } 20 } 21 close(IN); 22} 23 24# 初期処理 25foreach $i (@country){ 26 $country{$i} = 1; 27} 28 29if(open(IN, "wget -q -O - $url|")){ 30 while(<IN>){ 31 if(/^apnic\|(..)\|ipv4\|(\d+.\d+.\d+.\d)\|(\d+)/){ 32 if($country{$1}){ 33 $table{inet_aton($2)} = $3; 34 } 35 } 36 } 37 close(IN); 38 39 # IPアドレス一覧を CIDR 型式に変換 40 foreach $net (sort keys %table){ 41 $addr = unpack('N', $net); 42 $num = $table{$net}; 43 while($num == $num[0] && ($addr ^ $addr[0]) == $num){ 44 shift @addr; 45 shift @num; 46 $addr &= ~$num; 47 $num <<= 1; 48 } 49 unshift(@addr, $addr); 50 unshift(@num, $num); 51 } 52 53 # iptables 実行 54 while (@addr){ 55 for($num = pop(@num), $mask = 32; $num > 1; $num >>= 1, $mask--){} 56 $filt = inet_ntoa(pack('N', pop(@addr))) . "/$mask"; 57 system("iptables -I INPUT $rule -s $filt -j DROP"); 58 $rule++; 59 } 60} 61 620;


