2012/12/27

指定された日の曜日を取得する

先日開催された 第2回チキチキ!シェル芸人養成勉強会 の課題の中で自分の中で話題になった事。

指定された日の曜日を取得したい場合、 gnu 版の date (1) がインストールされている場合は 以下のコマンドで取得可能(0 が日曜日を示す)。

$ date -d YYYYMMDD '+%w'
0
    
gnu 版の 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) が拡張されたものという発想で httphttps そして 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/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
> EOF
            

2012/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 を 直接フィールド分割して変数 uread している。
問題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 を直接フィールド分割して 変数 ushellread し、 変数 shbash に出現回数をカウントしている。
問題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/07/27

シェルスクリプトで便利な小技

シェルスクリプトを作成するときに知っておくと便利な小技集。

exec > ファイル
以降の標準出力を全て ファイル に出力するので この設定をしておくと簡単な実行ログが取得出来る。
同様に exec 2> ファイル とすると 標準エラー出力が全て ファイル に出力されるので エラーログが取得できる。
当然 exec > ファイル 2>&1 とすれば 標準出力も標準エラー出力も取得できる。
ファイル/dev/null を指定すれば スクリプト実行中の出力は全て抑止されるので、 cron (8) から実行される場合などでは便利な場合もある。
  1#!/bin/sh
  2
  3exec 2> ${TMP:-/tmp}/myname.log
  4    :
            
set -e
スクリプト実行時に制御文以外でエラーが発生した場合に スクリプトを終了させる。
スクリプト中で実行すべきコマンドを typo した場合などで、 以降の処理が実行されると困る場合などに特に役立つ。
  1#!/bin/sh
  2    :
  3echo "call myfunc ..."
  4mtfunc                                          # 関数名を typo している
  5echo "end myfunc ..."
  6    :
            
この様なコードの場合 set -e されていると、 mtfunc がエラーになった時点でスクリプトが エラー終了するので以降の処理が実行されない。
当然 ifwhile&&|| などでコマンドの結果が評価される場合は エラーにはならない。
set -u
スクリプト中で値が設定されていない変数を参照した場合に エラーメッセージを表示してスクリプトを終了させる。
シェル変数や環境変数を typo した場合など、 変数に値が設定されていない事で発生する問題が回避できる。
  1#!/bin/sh
  2tempdir=/var/tmp/
  3mynam=myname
  4    :
  5rm -rf ${tmpdir}/${myname}                      # 変数名を typo している
  6    :
            
この様なコードの場合、 変数名 ${tempdir}${mynam} を それぞれ typo しているので、 結果として rm -rf / に展開されてしまうが、 set -u されていると実行が防げる。
set -n
こちらはどちらかと言うとスクリプトの開発時に有効な設定で、 コマンドは実行されないのでスクリプトの文法チェックに最適。
set -x
こちらも開発時に有効な設定で、 コマンドが実行される前にコマンドを stderr に出力する。
上の exec と組み合わせると 詳細な実行ログが取得できるのでデバッグに便利
  1#!/bin/sh
  2
  3exec 2> ${TMP:-/tmp}/debug.log
  4set -x
  5    :
            

2012/06/27

shebang

スクリプトの先頭行に書かれている #!/bin/sh の事を shebang (シェバング) と呼ぶ。
古くはプログラムが実行された際にローダがファイルの先頭 2 バイトを参照して ファイルの形式を調査した事に由来しているらしく、 例えば FreeBSD や Linux の最近の実行可能ファイルであれば 7f45(16) になっている (その後 4c46(16) と続いているので、 ファイルをダンプすると ELF と読める)。
ファイル先頭のこの値の事を magic number とも呼び、 初期の file (1) コマンドはこの magic number を読み取って ファイルの種類を報告していた (今の file (1) も基本は magic number を利用している)。

shebang は通常はスクリプトを実行するインタプリタのパスを記述するのだが、 実は実行可能なプログラムであればインタプリタ以外も記述できるので、 あるプログラムの設定ファイルの先頭にプログラム名自身を書いておくと その設定でプログラムが実行できて実は便利である。

例えば sshd/usr/local/etc/sshd/mysshd.conf を設定ファイルとして起動したい場合、通常はこの様に起動する。

# /usr/sbin/sshd -f /usr/local/etc/sshd/mysshd.conf
    
ところが設定ファイル /usr/local/etc/sshd/mysshd.conf の 先頭の shebang に #!/usr/sbin/sshd -f を追加して /usr/local/etc/sshd/mysshd.conf に実行権限を与えると、 設定ファイルを実行する事で sshd が起動できるので便利だ。
# cat /usr/local/etc/sshd/mysshd.conf
Port        20022
Protocol    2
    :
# ex -s !$ <<- EOF
ex -s /usr/local/etc/sshd/mysshd.conf <<- EOF
>  0i
>  #!/usr/sbin/sshd -f
>  .
>  w!
>  EOF
# head !$
head /usr/local/etc/sshd/mysshd.conf
#!/usr/sbin/sshd -f
Port        20022
Protocol    2
    :
# chmod 755 !$
chmod 755 /usr/local/etc/sshd/mysshd.conf
# !$
/usr/local/etc/sshd/mysshd.conf
# netstat -anf inet | egrep '^Active|^Proto|\.20022'
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  *.20022                *.*                    LISTEN
    
実行例で利用している "!$" は GNU bash (1) の コマンドライン履歴機能で直前に実行したコマンドの最後の引数に展開される。
GNU bash (1) には他にも 色々な履歴の参照機能 があるので、 覚えておくとコマンドの入力が格段に便利になる。

2012/06/21

シェルスクリプトでの排他処理

業務系のシステムでシェルスクリプトを使用している場合など、 多重起動の防止などで排他制御が必要な場合が多々ある。
その様な場合に役に立つ(と思われる) シンボリックリンクを利用したシェルスクリプトでの排他制御方法。
ただし、あくまでもシェルスクリプトによる処理なので、 厳密な意味での排他制御にはなり得ないために タイミングによってはどうしても多重起動してしまう危険性がある事は 考慮しておく必要がある。

この処理では自分自身のプロセス ID をロックファイルとして シンボリックリンクして利用しているので、 排他制御を実施したプロセス ID が簡単に確認できる様にするとともに、 何らかの理由で不正にロックファイルが残ってしまった場合に プロセス ID を確認する事で簡単にリカバリ可能としている。
なお、プロセス確認のために /proc を参照しているので、 Linux など /proc が存在する システムのみで利用可能となっているが、 ps (1) などを利用してプロセスを特定すれば /proc が無いシステムでも利用可能だろう。

  1# ロックファイル
  2lockfile=${TMP:-/tmp}/${0##*/}
  3
  4# 多重起動防止のために symbolic link を作成する
  5# link が作成できたら trap を設定し終了時に link を自動削除する
  6# synbolic link が存在しても /proc/${PID} がない場合は
  7# ロックファイルが不正に残っているので削除して処理を続行する
  8while true
  9do
 10    if ln -s $$ ${lockfile} 2> /dev/null
 11    then
 12        # ロック取得できた
 13        break
 14    else
 15        # ロックファイルが既に存在している場合
 16        # ロックファイルから作成元の PID を取得する
 17        if [ -d /proc/`ls -l ${lockfile} | sed 's!.* !!g'` ]
 18        then
 19            # プロセスが存在する場合
 20            echo "${0##*/}: exist another instance" 1>&2
 21            exit 1
 22        else
 23            # プロセスが存在しない場合はロックファイルを削除してリトライ
 24            rm -f ${lockfile}
 25        fi
 26    fi
 27done
 28
 29# 終わる時は必ず lockfile を削除する
 30trap 'rm -f ${lockfile}; exit' 0 1 2 3 11 15
 31
 32# 実際の処理
 33    :
    

2012/06/20

コーディングスタイル ネタ編

twitter で話題になったので shell スクリプトにしてみた。
一応 posix 準拠の shell で動作すると思うんだけど、 $((…)) を使ったビット演算は果たして posix 準拠なのか微妙
あくまでもネタという事で笑って見て貰えれば嬉しい。

  1#!/bin/sh
  2# All rights reserved, copyright (C) 2012, Mitzyuki IMAIZUMI
  3#
  4
  5#
  6# 10進数を2進数に変換して表示
  7#
  8printb()
  9{
 10
 11    local   i str num
 12    
 13    i=0
 14    num=${1}
 15
 16    while [ $i -lt 32 ]
 17    do
 18        str="$((num & 1))${str}"
 19        num=$((num >> 1))
 20        i=$((i + 1))
 21    done
 22
 23    echo "${str}  (${1})"
 24
 25}
 26
 27#
 28# usage メッセージ表示
 29#
 30usage()
 31{
 32
 33    echo "Usage: ${1} [-v][-h|-o] num [... num]" 1>&2
 34    
 35    return 255
 36
 37}
 38
 39#
 40# ヘッダ表示
 41#
 42header()
 43{
 44
 45    local   i
 46
 47    i=0
 48
 49    while [ ${i} -lt 4 ]
 50    do
 51        printf "       *"
 52        i=$((i + 1))
 53    done
 54
 55    echo
 56
 57}
 58
 59#
 60# メイン処理
 61#
 62
 63# オプション解析
 64if args=`getopt voh $*`
 65then
 66    set -- ${args}
 67    for i
 68    do
 69        case "${i}" in
 70            -v )
 71                verbose=true;;
 72            -o )
 73                prefix="0";;
 74            -h )
 75                prefix="0x";;
 76            --  )
 77                shift
 78                break;;
 79        esac
 80        shift
 81    done
 82else
 83    usage ${0##/}
 84    exit
 85fi
 86
 87# ヘッダ出力
 88${verbose:-false} && header
 89
 90# 2進数変換
 91for i
 92do
 93    printb `printf %d ${prefix}${i}`                # 変換出力
 94done
    
どうだ、某会長(謎

ちなみに bc (1) を利用すると簡単に実現できちゃいますが、 プログラミング自体を楽しむという事で…

$ echo "ibase=10; obase=2; 10" | bc
1010
$ echo "ibase=16; obase=2; 20" | bc
100000
    

2012/06/19

コーディングスタイル

ちょっと間が空いてしまったが、 自分のコーディングスタイルを晒すために 簡単なプログラムを書いてみた。
プログラムは与えられた数値を2進数にして表示するだけの 簡単なプログラムなのであまり参考にはならないと思うが、 コーディングスタイルで取り上げた要素がほぼ網羅されていると思う。
条件演算子は無理矢理使ったので良い子は真似しない様に

  1/*
  2 * All rights reserved, copyright (C) 2012, Mitzyuki IMAIZUMI
  3 *
  4 * $Id: printb.c,v 1.1 2012/06/18 10:57:54 mitz Exp $
  5 */
  6#include    <stdio.h>
  7#include    <stdlib.h>
  8#include    <string.h>
  9#include    <unistd.h>
 10
 11/*
 12 * 10進数を2進数変換に変換して表示
 13 */
 14void    printb(int  num)
 15{
 16
 17    int     x = num;
 18    char    str[sizeof(int)*8+1],
 19            *ptr;
 20
 21    memset(str, '\0', sizeof(str));
 22
 23    for(ptr=str+sizeof(str)-2; ptr>=str; x>>=1)
 24        *ptr-- = '0' + (x & 1);
 25
 26    printf("%s  (%d)\n", str, num);
 27
 28}
 29
 30/*
 31 * usage メッセージ表示
 32 */
 33int     usage(const char    *myname)
 34{
 35
 36    fprintf(stderr, "Usage: %s [-v][-h|-o] num [num ...]\n", myname);
 37
 38    return(255);
 39
 40}
 41
 42/*
 43 * ヘッダ表示
 44 */
 45void    header(void)
 46{
 47
 48    size_t  i;
 49
 50    for(i=0; i<sizeof(int); i++)
 51        printf("       *");
 52    printf("\n");
 53
 54}
 55
 56/*
 57 * メイン処理
 58 */
 59int     main(int argc, char **argv)
 60{
 61
 62    int     verbose = 0,
 63            base = 0;
 64    char    ch,
 65            *myname;
 66
 67    /* basename $0 取得 */
 68    myname = (myname = strrchr(*argv, '/')) ? myname++ : *argv;
 69
 70    /* オプション解析 */
 71    while((ch = getopt(argc, argv, "voh")) != EOF)
 72        switch(ch){
 73            case 'v':
 74                verbose = 1;
 75                break;
 76
 77            case 'o':
 78                base = 8;
 79                break;
 80
 81            case 'h':
 82                base = 16;
 83                break;
 84
 85            default:
 86                exit(usage(myname));
 87        }
 88
 89    argc -= optind;
 90    argv += optind;
 91
 92    /* ヘッダ出力 */
 93    if(verbose)
 94        header();
 95
 96    /* 2進数変換 */
 97    while(*argv)
 98        printb(strtol(*argv++, NULL, base));
 99
100    exit(0);
101
102}
    


Copyright © 2008-2020 Mitzyuki IMAIZUMI. All rights reserved.