6. シェルスクリプトの作り方
(3/3)

ホーム > 目次 > シェルスクリプトの作り方(2/3) > シェルスクリプトの作り方(3/3) > システム管理(1/3)


関数

少し大きめなシェルスクリプトになりますと、複数の機能を持っています。このようなものを1つのファイルとして作りますと、次のような不具合が考えられます。

これらの対応策として、機能毎のモジュール(部品)化が考えられます。この部品がシェルスクリプトの場合は「関数」になります。

関数はシェルスクリプト中に記述することも出来ますし、関数だけを別のファイルに記述しておくことも出来ます。また、特別な使い方として、aliasコマンドで定義したコマンドは引数の指定が出来ませんが、関数の場合は出来ますので、aliasコマンドの拡張機能的に使うことも出来ます。

ここでは次の事項に付いて説明します。

変数の宣言と有効範囲

概要

注1
declareコマンドとtypesetコマンドは機能的にはほとんど同じですが、現在はdeclareコマンドが一般的に使われているようです。当講座ではtypesetコマンドの説明は行ないませんので、必要な方はmanコマンドで調べて下さい。

変数は必要なときに名前を指定して、すぐに使うことが出来ますが、この場合、データは文字データとして変数に格納されます。一方、変数は明示的に宣言してから使用することも出来ますが、この場合はいろいろな属性を与えることが出来ます。

変数の宣言はdeclareまたはtypesetコマンド注1及び、localコマンドで行います。declareコマンドには次のようなオプションがあります。

表6-10 declareコマンドのオプション一覧
オプション意味
-a配列とする
-i整数型(integer)とする
-r参照のみ可能(read only)とする
-f定義済関数の表示
-F定義済関数名の表示

配列は連続した変数領域で、同じ種類のデータを複数扱うときに使用します。つまり、1つの配列に複数のデータを格納できるということです。配列中の個々のデータを取り扱うときには何番目のデータなのかを指定します。この番号を添字と呼んでおり、0から始まる整数で指定します。

read onlyとした場合は後からその変数に値を設定することは出来ませんので、宣言時に値を与えておきます。また、read onlyの解除は「+r」オプションで再宣言を行えば可能です。

変数には有効範囲があります。つまり、シェルスクリプト中のどこでも使えるもの(グローバル変数)と、関数内だけで使えるもの(ローカル変数)です。関数内でlocalコマンドで宣言しますとローカル変数になります。それ以外の変数はグローバル変数です。また、位置パラメータはローカル変数です。

変数の有効範囲
図6-4 変数の有効範囲

上図で、(1)はシェルスクリプト内全体に有効(グローバル変数)を、(2)は関数の外側、(3)は関数内に有効(ローカル変数)を表しています。(1)に該当する変数はval00、val01、val03、val04で、(2)に該当する変数は$2で、(3)に該当する変数はva02と$1です。

形式

配列の宣言で、-iや-rオプションの指定もできます。また、localコマンドで配列の宣言も出来ますし、-iや-rオプションの指定もできます。

  1. declareコマンド。
    declare -a 配列名
    declare -a 配列名[要素数] ← データ格納数(要素数)を指定
    declare -a 配列名=(値1 値2 … 値n) ← 初期値を指定。"値"と"値"の間は空白で区切る
    
    declare -i 変数名
    declare -i 変数名=値 ← 値は整数を指定する
    
    declare -r 変数名=値 ← 初期値を指定しておくこと。後からの設定は出来ない
    
    declare -f ← 定義してある関数全ての内容表示
    declare -f 関数名 ← 指定した関数の内容のみ表示
    declare -F ← 関数名のみ表示
  2. 配列への値の設定。
    配列名[添字]=値 ← 添字は整数値。先頭の要素は0
    配列名=(値1 値2 … 値n) ← 複数の値を設定
  3. 配列の値の参照。
    ${配列名[添字]} ← 添字で指定した要素の値のみを参照
    ${配列名[*]} ← 全ての要素の値を参照
    ${#配列名[添字]} ← 添字で指定した要素の文字数を参照
    ${#配列名[*]} ← データ数を参照
  4. localコマンド。(-a、-i、-rオプションの指定もできます。)
    local 変数名
    local 変数名=値

例題

  1. 配列の使い方。
    $ declare -a array1 ← 配列の宣言
    $ array1[0]=merry ← 配列要素に値を設定
    $ array1[1]=ken
    $ array1[2]=kuma
    $ echo ${array1[1]} ← 2番目の要素の値を表示
    ken
    $ echo ${array1[*]} ← 全ての要素の値を表示
    merry ken kuma
    $ echo ${#array1[2]} ← 3番目の要素の文字数を表示
    4
    $ echo ${#array1[*]} ← データが設定されている要素の数を表示
    3
    $
  2. 整数型とRead only変数の使い方。
    $ declare -i v01=10 ← 整数型変数の宣言
    $ declare -i v02=20
    $ declare -i ans
    $
    $ char=v01+v02 ← char変数は宣言無しのため文字型
    $ echo $char
    v01+v02 ← char変数の内容
    $
    $ ans=v01+v02 ← 全て整数型変数のため、整数演算となる
    $ echo $ans
    30 ← ans変数の内容
    $
    $ declare -r v03 ← Read only変数の宣言
    $ v03=merry
    bash: v03: readonly variable ← 値の代入は出来ない
    $
    $ declare -r v04=ken ← 初期値として値を設定
    $ echo $v04
    ken
    $ v04=merry
    bash: v04: readonly variable
    $

関数宣言

概要

関数は定義(宣言)しただけでは実行しません。関数呼び出しを行って、初めて実行します。関数が終了すると、呼び出した次のコマンドに制御が移ります。また、関数呼び出しで指定した引数は関数側では位置パラメータとして受け取ります。

形式

関数宣言の形式1と形式2は機能的な相違はありませんので、使いやすいほうを使うと良いでしょう。returnを指定しないと関数の最後のコマンドを実行して終了します。

  1. 宣言の形式1
    function 関数名()
    {
      コマンドリスト
      return n ← 終了ステータスnを返し、終了する
    }
  2. 宣言の形式2
    関数名()
    {
      コマンドリスト
      return n
    }
  3. 関数呼び出し
    関数名 [引数1 引数2 … 引数n] ← 必要に応じて引数を指定できる

例題

次の例題は/etc/passwdの内容をブラウザに表(テーブル)形式で表示します。内容が難しくなってしまいましたので、先にポイントを説明します。

shiftコマンド
shiftコマンドは位置パラメータの値を左に引数で指定された数だけシフトします。引数を省略したときは1が仮定されます。位置パラメータ0にはシェルの名称或は、シェルスクリプト名が設定されていますので、シフトの対象外です。(シフトしません)
次のボタンでshiftコマンドの動作を確認できます。テキスト領域に適当な文字列を入力して、ボタンを押すと文字列がシフトします。
$0$1 $2PARA3 $4$5
TblGen関数
引数で指定された値を使ってHTMLタグの表(テーブル)注2を作成します。第1引数は処理内容を指定するフラグで、次の意味を持ちます。
h
表タグの最初と見出し行を作成します。
d
データ行(/etc/passwdの内容)を作成します。
f
表タグの最後を作成します。
第2引数以降が表のセルに設定する値です。
HtmlGen関数
引数で指定された値を使ってHTML文注3を作成します。第1引数はHTMLタグのTITLEにして、第2引数はBODY部の内容にします。

注2
次のようなHTML文を生成します。「見出し」と「内容」は複数指定できます。

<TABLE BORDER>
  <TR><TH>見出し</TH></TR>
  <TR><TD>内容</TD></TR>
</TABLE>

注3
次のようなHTML文を生成します。

<HTML><HEAD>
<TITLE>タイトル</TITLE>
</HEAD>
<BODY>
  内容
</BODY>
</HTML>

(ex60.sh)

#!/bin/bash
# 機能 : /etc/passwdをブラウザに表形式で表示する
# 作成 : メリー

function TblGen() ← 関数宣言開始
{
# 機能  :   表を生成する
  local flag
  local sel

  flag=$1 ← 処理内容を指定するフラグを変数に退避注4
  shift ← 位置パラメータを左にシフト

  case $flag in
    "h" | "H")
      echo "<TABLE BORDER>" ← 表の先頭を生成
      echo "<TR>" ← 見出し行を生成
      for sel ← 変数selに位置パラメータの値を設定して繰り返す注5
      do
        echo "<TH>$sel</TH>"
      done
      echo "</TR>";;
    "d" | "D")
      echo "<TR>" ← データ行を生成
      for sel ← 変数selに位置パラメータの値を設定して繰り返す注5
      do
        echo "<TD>$sel</TD>"
      done
      echo "</TR>";;
    "f" | "F")
      echo "</TABLE>";; ← 表の最後を生成
  esac
}

function HtmlGen() ← 関数宣言開始
{
# 機能  :   htmlを生成する
  echo "<HTML><HEAD><TITLE>" ← HTMLの先頭を生成
  echo "$1"
  echo "</TITLE></HEAD><BODY>"

  cat "$2" ← ボデイ部を生成

  echo "</BODY></HTML>" ← HTMLの最後を生成
}
 ← ここから実行開始
declare -a username ← 配列の宣言
declare -a userID
declare -a groupID
declare -a homepath
declare -i linenum
declare -i cnt

TblGen "h" "ユーザ名" "ユーザ番号" "グループ番号" \
       "ホームディレクトリ" > /tmp/table.html ← 表の見出しを生成
 ↓ 以降で/etc/passwdから必要な部分を切り出す
username=($(cut -f1 -d: /etc/passwd))
userID=($(cut -f3 -d: /etc/passwd))
groupID=($(cut -f4 -d: /etc/passwd))
homepath=($(cut -f6 -d: /etc/passwd))

linenum=${#username[*]} ← 配列に格納されているデータ数(行数)を求める
cnt=0
while (( cnt < linenum )) ← 表のデータ行を生成(行数分繰り返す)
do
  TblGen  "d" ${username[$cnt]} ${userID[$cnt]} \
      ${groupID[$cnt]} ${homepath[$cnt]} >> /tmp/table.html

  cnt=cnt+1
done
TblGen "f" >> /tmp/table.html ← 表の最後を生成

HtmlGen /etc/passwd /tmp/table.html > /tmp/passwd.html

firefox file:///tmp/passwd.html ← firefoxウェブブラウザを起動注6

rm /tmp/table.html /tmp/passwd.html ← 生成したHTMLファイルを削除
$ ex60.sh ← クリックで表示
 ← /etc/passwdの内容が表で表示される
$

注4
第1引数(位置パラメータ1)の内容は次のshiftコマンドの実行により消えてしまうため、ここで、変数に退避しておきます。

注5
shiftコマンドによりシフト済ですので、変数selには位置パラメータ2の値から順番に設定されます。

注6
ここではFirefoxでHTMLを表示するようにしていますが、Firefoxをインストールしていないような場合は別のブラウザを指定して下さい。

関数宣言の分離

概要

シェルスクリプトの機能を関数として宣言しておくと、別のシェルスクリプトでも、その関数を使いたいということがあります。その場合、エディタで関数宣言の部分をコピーしても良いのですが、関数に変更が起きたときの処置が面倒です。そこで、複数のシェルスクリプトで共通に使われるような関数は共通関数として、1つのファイルにまとめて宣言しておくと便利です。

共通関数を使用するシェルスクリプトでは、共通関数を宣言しているファイルを取り込むことにより、共通関数を使用することが出来ます。ファイルの取り込みは「sourceコマンド」を使用すると良いでしょう。

形式

sourceコマンドは引数で指定されたファイルをsourceコマンド実行プロセスと同じプロセスで実行させるためのコマンドです。

source 取り込むファイルのパス名 ← sourceの替わりに.(ドット)でもよい。引数の指定も可

例題

次の例題は/etc/groupの内容をブラウザに表形式で表示します。また、前の例題の2つの関数(TblGenとHtmlGen)を共通関数として、1つのファイル(ex62.sh)にまとめました。ex62.shは省略します。

(ex61.sh)

#!/bin/bash
# 機能 : /etc/groupをブラウザにテーブル形式で表示する
# 作成 : メリー

source ex62.sh ← 共通関数の取り込み

declare -a groupname
declare -a groupID
declare -a groupmember
declare -a group
declare -i linenum
declare -i cnt

TblGen "h" "グループ名" "グループ番号" "メンバー名" > /tmp/table.html
groupname=($(cut -f1 -d: /etc/group))
groupID=($(cut -f3 -d: /etc/group))

group=($(cat /etc/group))
linenum=$(cat /etc/group | wc -l) ← 行数算出
cnt=0
while (( cnt < linenum )) ← 行単位にグループメンバーを切り出す
do
  member=$(echo ${group[cnt]} | cut -f4 -d: ) ← 1行のグループメンバーを切り出す
  if [ -z "$member" ] ← 空か?(グループメンバーなし)
  then
    groupmember[cnt]="&nbsp;" ← グループメンバーなし(&nbsp;はhtmlのスペース)
  else
    groupmember[cnt]="$member"
  fi
  cnt=cnt+1
done

cnt=0
while (( cnt < linenum ))
do
  TblGen  "d" ${groupname[$cnt]} ${groupID[$cnt]} \
      ${groupmember[$cnt]} >> /tmp/table.html

  cnt=cnt+1
done
TblGen "f" >> /tmp/table.html

HtmlGen /etc/group /tmp/table.html > /tmp/group.html

firefox file:///tmp/group.html

rm /tmp/table.html /tmp/group.html
$ ex61.sh ← クリックで表示
 ← /etc/groupの内容が表で表示される
$

ホーム > 目次 > シェルスクリプトの作り方(2/3) > シェルスクリプトの作り方(3/3) > システム管理(1/3)