2013/01/01
2012/12/27
指定された日の曜日を取得する
先日開催された 第2回チキチキ!シェル芸人養成勉強会 の課題の中で自分の中で話題になった事。
指定された日の曜日を取得したい場合、 gnu 版の date (1) がインストールされている場合は 以下のコマンドで取得可能(0 が日曜日を示す)。
$ date -d YYYYMMDD '+%w' 0gnu 版の date (1) がインストールされていない場合でも、 FreeBSD 版の date (1) は独自拡張されているので 以下のコマンドで取得可能だと言う事を教わった(0 が日曜日を示す)。
$ date -j YYYYMMDDHHMM '+%w' 0
但しどちらも posix に準拠していない独自拡張な機能を利用しているため
環境依存となってしまうので、
環境に依存しない方法での解決方法を考えてみた。
日付から曜日を取得するためには ツェラーの公式
という計算式があるので、
posix に準拠したシェルの機能だけでその計算式を実装してみる。
$ y=YYYY; m=MM; d=DD $ test $m -lt 3 && y=$(($y - 1)) && m=$(($m + 12)) $ echo $(($(($y + $y/4 - $y/100 + $y/400 + $(($((13 * $m + 8))/5)) + $d)) % 7))年と月の値によって調整が必要なのでワンライナーにはならないのが残念だけど、 基本的には単純な四則演算なのでシェルの機能のみで実装が可能だ。
これで gnu 版の date (1) がインストールされていない環境でも 任意の日付の曜日が簡単に取得できる!。
2012/12/26
dog (1)
以前、某雑誌のシェルスクリプト大喜利に投稿したネタ。
こんな感じのお題に対する回答。
cat コマンドの作者が遺言を遺しました。
「私の人生における最大の公開は、 dog コマンドを作り忘れてしまったことだ。 あぁ誰か作ってくれぇー。ガクッ」
さぁ、一体どんなコマンド?
お題がネタっぽいので回答もネタっぽくしてみた。
more (1) を拡張して less (1) が、 yacc (1) を拡張して bison (1) が作られた様に、 dog (1) は cat (1) が拡張されたものという発想で http、https そして ftp といった schema に対応した cat (1) というスクリプトにしてみた。
内容はとても単純なので cat → dog という発想だけが勝負のスクリプトである。
雑誌では割愛されてしまったネタとしてのマニュアルも掲載してみる。
というか、実はマニュアルの方が書きたかったという…(笑
1#!/bin/sh 2# 3# NAME 4# dog -- concatenate and print files 5# 6# SYNOPSIS 7# dog [cat options] [file ...] 8# 9# DESCRIPTION 10# Dog is a file concatenate and print command in the style of cat(1). 11# It should be upwardly compatible with original cat. 12# 13# The dog utility reads files sequentially, writing them to the standard 14# output. The file operands are processed in command-line order. If file 15# has protocol schema such as 'http://', 'https://' or 'ftp://', dog utility 16# gets the file from remote system and display it, otherwise dog assumes 17# local file. The dog utility uses wget(1), curl(1), or lynx(1) to retrieve 18# the file from remote system. If dog utility could not find these commands 19# from standard command path, disable remote file display. 20# All option of dog is same as cat(1) except '-h'. 21# 22# EXIT STATUS 23# The dog utility exits 0 on success, and >0 if an error occurs. 24# 25# SEE ALSO 26# Rob Pike, "UNIX Style, or cat -v Considered Harmful", USENIX Summer 27# Conference Proceedings, 1983. 28# 29# STANDARDS 30# The dog utility is *NOT* complient with the IEEE Std 31# 1003.2-1992 (``POSIX.2'') specification, even now and forever. 32# 33 34curl="-s -o -" 35wget="-q -O -" 36lynx="-source" 37 38# get command name and path 39for i in wget curl lynx 40do 41 for j in /bin /usr/bin /usr/local/bin /opt/bin /opt/local/bin 42 do 43 if [ -x ${j}/${i} ] 44 then 45 command="${j}/${i} `eval echo '$'${i}`" 46 break 2 47 fi 48 done 49done 50 51# args 52while [ ${1} != "" ] 53do 54 case ${1} in 55 -h ) 56 sed -n '2,/^$/s/^#//p' ${0} 57 exit 1 58 ;; 59 -- ) 60 shift 61 break 62 ;; 63 -* ) 64 opt="${opt} $1" 65 ;; 66 * ) 67 break 68 ;; 69 esac 70 shift 71done 72 73for i in $* 74do 75 if expr ${i} : "https*://" > /dev/null 76 then 77 if [ -n "${command}" ] 78 then 79 ${command} ${i} | cat ${opt} 80 else 81 echo "${0##*/}: cannot get remote file: ${i}" 1>&2 82 fi 83 elif expr ${i} : "ftp*://" > /dev/null 84 then 85 case ${command} in 86 *wget*|*curl* ) 87 ${command} ${i} | cat ${opt} 88 ;; 89 * ) 90 echo "${0##*/}: cannot get remote file: ${i}" 1>&2 91 ;; 92 esac 93 else 94 cat ${opt} ${i} 95 fi 96done
今回のシェルスクリプト大喜利にも投稿したので、 某雑誌が発売されたら紹介する予定。
2012/12/14
Mountain Lion
iPhone を導入した事を切っ掛けに Mountain Lion 化してみた。
見た目や使い勝手は使い慣れた Snow Leopard の方が好きなのだが、
iPhone との iCloud による完全連携の魅力には逆らえない。
世間では既に言い尽くされている事なのだろうが、
「確かに非常に便利で素晴らしい」、というのが第一印象。
例えば iPhone で撮影した画像は自動的に iPhoto に取りこまれていたり、
Mac で編集した連絡先がいつのまにか iPhone でも変更されていたり、
更には複数台の Mac 間で Mail.app のフィルタ設定まで同期されている。
今まで Dropbox や WebDAV とアイデアを駆使して何とか実現してきた事が、
ID を登録するだけでいとも簡単に実現できてしまうのは凄い事だ。
2012/11/30
IPv4 アドレスがネットワークに属しているか調べる
ネットマスクとはその名の通り IPv4 アドレスのうち、
どこまでのビットがネットワークアドレスなのかを示しているので、
IPv4 アドレスとネットマスクの論理和はネットワークアドレスとなる。
そのため、取得した論理和とネットワークアドレスが等しい場合は
IPv4 アドレスはネットワークに属す事になる。
C で実装する場合はこんな感じかな。
1int checkAddr(const char *network, const char *netmask, const char *ipaddr) 2{ 3 4 struct in_addr net, 5 mask, 6 addr; 7 8 if(inet_aton(network, &net) && 9 inet_aton(netmask, &mask) && 10 inet_aton(ipaddr", &addr)) 11 return((addr.s_addr & mask.s_addr) == (net.s_addr & mask.s_addr)); 12 else 13 return(0); 14 15}
ちなみに仕事で実装した java のコード。
1boolean checkAddr(String network, String netmask, String ipaddr) 2{ 3 4 try { 5 int n = 0, 6 m = 0, 7 a = 0; 8 byte[] net = InetAddress.getByName(network).getAddress(), 9 mask = InetAddress.getByName(netmask).getAddress(), 10 addr = InetAddress.getByName(ipaddr).getAddress(); 11 12 for(int i=0; i<4; i++) 13 n |= ((int)net[i] & 0xff) << (8 * (3 - i)); 14 for(int i=0; i<4; i++) 15 m |= ((int)mask[i] & 0xff) << (8 * (3 - i)); 16 for(int i=0; i<4; i++) 17 a |= ((int)addr[i] & 0xff) << (8 * (3 - i)); 18 19 return((a & m) == (n & m)); 20 21 } catch (Throwable e){ 22 return false; 23 } 24 25}java はそれほど詳しくないのでコーディングが冗長だけど、 取りあえず動作している(気がする)。
2012/11/02
CentOS 6.3 インストール直後にする作業
仕事では Linux サーバ、特に CentOS の導入~初期設定の機会が多いので、 全ての CentOS 6.3 サーバの導入時に必ず行っている作業をメモする。
インストール作業は CentOS 6.3 の DVD から起動して行う。
CentOS 6 からはテキストベースのインストーラの場合、
パーティションの設定やファイルシステムの指定、
初期インストールされるパッケージの追加・削除など指定できないので、
GUI を利用した通常のインストールを実行する。
インストールが終了したら初期環境設定を行う。
以下の作業は CentOS 6.3 を導入したマシンでほぼ必ず実施する作業。
- 自分のユーザを作製する
-
# adduser アカウント # password アカウント
- 自分のアカウントを wheel グループに追加する
-
sudo (1) 可能な様に自分のアカウントを
wheel グループに追加する。
# (rm /etc/group; sed 's/^wheel.*/&,アカウント/g' > /etc/group) < /etc/group
- sudo(1) の設定を変更する
-
sudo(1) しても元の環境変数を保持する様設定
Defaults env_reset を Defaults !env_reset に変更 # Defaults env_keep += "HOME" のコメント記号 `#' を削除
wheel グループのアカウントのみ sudo (1) 可能にする# %wheel ALL=(ALL) ALL のコメント記号 `#' を削除
# visudo
- termcap を使用するアプリケーション対応
-
# ln -s /usr/lib/libtinfo.so /usr/lib/libtermcap.so.2
- selinux を無効にする
-
# (rm /etc/sysconfig/selinux; sed 's/^SELINUX=.*/SELINUX=disabled/' > /etc/sysconfig/selinux) < /etc/sysconfig/selinux# ex -s /etc/syconfig/selinux << EOF > /^SELINUX=/s/.*/SELINUX=disabled > wq > EOF2012/11/09 追記
CentOS の /etc/sysconfig/selinux は /etc/selinux/config へのシンボリックリンクで 実際は /etc/selinux/config を参照しているのだが、 rm (1) と sed (1) を使った方法だと /etc/sysconfig/selinux が通常ファイルになって 書き換わってしまい、元の /etc/selinux/config が 変更されていないために selinux が無効にならない という問題を指摘して頂いたので編集方法を修正しました。
- システム更新
-
# yum update
- 最低限必要なソフトウェアのインストール
-
システムを運用する上でこれだけは必要だとうソフトウェアを導入する。
# yum install openssh-clients libtermcap-devel tcpdump # yum install ntpdate telnet wget tcpdump unzip mlocate rsync bind-utils
- 開発環境のインストール
-
ソフトウェアを開発する場合は導入する。
通常はサーバでは開発しないので導入は不要な筈…
# yum install make gcc bison flex
- ファイアウォールの設定
-
グローバル環境に晒されるサーバであれば
最低限のファイアウォールは設定する必要がある。
基本的には以下のポリシーで必要に応じて追加・削除する。
#!/bin/sh iptables=/sbin/iptables # 全て初期化 ${iptables} -F # デフォルトルール ${iptables} -P INPUT DROP ${iptables} -P OUTPUT ACCEPT ${iptables} -P FORWARD DROP # ループバックを有効にする ${iptables} -A INPUT -i lo -p all -j ACCEPT # 接続済みのパケットを受け付ける ${iptables} -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT # http, httpsはどこからでもアクセス可 ${iptables} -A INPUT -p tcp --dport 80 -j ACCEPT ${iptables} -A INPUT -p tcp --dport 443 -j ACCEPT ${iptables} -A INPUT -p tcp --dport 8080 -j ACCEPT ${iptables} -A INPUT -p tcp --dport 9080 -j ACCEPT # dns パケットを許可 ${iptables} -A INPUT -p tcp --sport 53 -j ACCEPT ${iptables} -A INPUT -p udp --sport 53 -j ACCEPT ${iptables} -A INPUT -p tcp --dport 53 -j ACCEPT ${iptables} -A INPUT -p udp --dport 53 -j ACCEPT # ntp パケットを許可 ${iptables} -A INPUT -p tcp --dport 123 -j ACCEPT ${iptables} -A INPUT -p udp --dport 123 -j ACCEPT # そのほか必要なルールを記載 : :
上記設定を /tmp/firewall.sh に格納した場合、 ファイアウォールを有効にして設定をセーブする。# sh /tmp/firewall.sh # /etc/init.d/iptables save iptables: ファイアウォールのルールを /etc/sysconfig/iptableに保存中: [ OK ]
- 起動サービスの設定
-
起動サービスを設定する。
基本的には atd crond iptables network ntpd rsyslog sshd のみを有効にする
# for i in `chkconfig --list | awk '/:on/{ print $1 }'` > do > case $i in > atd|crond|iptables|network|ntpd|rsyslog|sshd ) > ;; > * ) > chkconfig $i off > chkconfig --del $i > ;; > esac > done
ここまで設定して再起動する。
MTA や httpd など固有のサービスは個々に設定する。
2012/10/31
hbstudy まとめ
先日参加した hbstudy #38 ハンズオンの
課題 の幾つかを
シェルのみ 縛りで回答してみる。
流石にシェルのみで解く場合はワンライナーは無理なので、
普通のシェルスクリプトの型式で記述している。
- 問題1: ユーザの抽出
-
/etc/passwd から、ユーザ名を抽出したリストを作って下さい
1IFS=: 2while read id other 3do 4 echo ${id} 5done </etc/passwd
IFS を : に変更して /etc/passwd を 直接フィールド分割して変数 u に read している。
- 問題2: ユーザの抽出
-
/etc/passwd から、次を調べて下さい。
ログインシェルが bash のユーザと sh のユーザどちらが多い?
1IFS=: 2while read id p u g e d shell 3do 4 case ${shell} in 5 */sh ) 6 sh=$((${sh} + 1));; 7 */bash ) 8 bash=$((${bash} + 1));; 9 esac 10done < /etc/passwd 11echo "bash: " ${bash} 12echo "sh: " ${sh}
問題:1 同様に IFS を : に変更して /etc/passwd を直接フィールド分割して 変数 u、shell に read し、 変数 sh と bash に出現回数をカウントしている。
- 問題5: FizzBuzz
-
1,2,3,4,5…と数えていって…
- 3 の倍数だったら数字の代わりに「Fizz」
- 5 の倍数だったら数字の代わりに「Buzz」
- 15 の倍数だったら数字の代わりに「FizzBuzz」
1i=1 2while [ ${i} -lt ${1:-100} ] 3do 4 if [ $((${i} % 15)) -eq 0 ] 5 then 6 echo -n "FizzBuzz," 7 elif [ $((${i} % 5)) -eq 0 ] 8 then 9 echo -n "Buzz," 10 elif [ $((${i} % 3)) -eq 0 ] 11 then 12 echo -n "Fizz," 13 else 14 echo -n "${i}," 15 fi 16 i=$((${i} + 1)) 17done 18echo
素直に 1 から $1 (指定されていない場合は 100) までを $((…)) にて % 演算子で剰余計算して出力している
- 問題8: CPU 使用率
-
top の出力からどのユーザが
何% CPU を使用しているか集計して下さい。
1exec 3<&0 0<< EOF 2`ps aux` 3EOF 4 5while read u p c other 6do 7 if [ "${u}" != "USER" ] 8 then 9 case ${users} in 10 *${u}* ) 11 ;; 12 * ) 13 users="${users} ${u}" 14 ;; 15 esac 16 eval "$u=\$((\$$u + ${c%%.*}))" 17 fi 18done 19 20for i in ${users} 21do 22 echo "${i} : `eval echo '$'$i`" 23done
CPU 使用率は小数点以下切り捨てで計算している。
出現したユーザID を users 変数に格納しておく。
eval と $((…)) を利用して、 ユーザID を変数名とした変数に CPU 利用率を集計している。
最後に for ループで users に格納された ユーザID とユーザIDを変数名とした変数に格納されている 集計値を表示している。
FreeBSD では top -b -n 1 が利用不可なので ps aux の出力を集計する
全てのスクリプトは FreeBSD 上の /bin/sh で動作確認済み。
手元に Linux 環境が存在しないので Linux での動作は未確認だけど、
posix 機能だけしか使用していない(筈)なので Linux でも動作すると思う。
2012/10/30
2012/08/27
2012/08/12
行き当たりばったり
折角の新車なのでドライブでも…と早朝 5 時に起床して出発。
新東名の駿河湾沼津 SA のリサとガスパールのカフェに行ってみたいので、
とりあえず本栖湖を目的地に設定し朝 6 時に出発する。
が、しかし既に東名高速は渋滞している様で、
VICS 信号を受信したナビは鶴川方面を通過して
高尾山の辺りから中央高速経由のルートを指示してくる。
ナビに従って走り続けると殆ど渋滞もしていない中央高速から
朝 9 時には河口湖に到着してしまう。
折角河口湖に早い時間に到着したので当初の目的をすっかり変更し
白鳥のボートなどに乗りつつ河口湖を満喫してると空腹になってきたので、
以前何度か行った山中湖のレストランに向かう事にする。
山中湖に向かうと丁度ランチタイムに無事到着。
別荘地の森の中にある隠れ家の様な燻製レストランの
とっても気持ち良いテラスで美味しいランチを楽しんでいると
気持ちはすっかり泊まりたいモードに…
お盆休みの始まる週末に空き室なんかある宿がある訳ないだろうと思いつつ
レストランのご主人に聞いてみた所、
偶然空いているペンションを紹介していただけたので計らずも宿が決定。
宿が決まったので安心して 3 人乗り自転車で山中湖を堪能したり、
温泉にのんびりつかったりと山中湖も満喫。
夕食はアメリカン風のコース料理だったのだが、
スターターからセンス良い味付けで最後まで大満足。
翌日は当初の目的通り本栖湖を経由して新東名高速へ。
念願のリサとガスパールカフェに立ち寄って軽く食事をして自宅へ。
行き当たりばったりで日帰りドライブの予定で出かけた筈なのに、 殆ど渋滞らしい渋滞にも遭遇せずに とっても充実した一泊の家族旅行が楽しめました。
家族一緒だと とっても楽しいね☆