ここはシェルスクリプト (bash) について書く。

for ループ
while ループ
ping
if
case
TEST
シェル関数
ヒアドキュメント
引き数


for ループ
for 変数 in 変数に入れたい物 ; do
  処理
done

変数: 適当なものを入れて良いが大抵 f, i などがくる。$を付けてはいけない。
      (処理の部分では $ をつける)
変数に入れたい物:
     ワイルドカード使用可能。
     $@,$*,$1,$2 などの他に *.jpg などを持って来ても良い。
変数にいれたい物が、展開されたものが一つづつ処理の部分に来て、変数に入れたい物がなくなったら終了。

位置パラメーター
$1: 第一引数
$2: 第2引数
$0: 起動したシェルスクリプト自身のファイル名


キーワードパラメーター
$? 直前に実行したコマンドの戻り値
$# 引数の数
$$ シェルのプロセスid
$! 最期に実行したバックグランドプロセスのプロセスid
${val} 変数 val の内容。$val と略せる
${val1:-val2} 変数 val1 の内容が空(null)の時に、変数 val2 の内容が入る
$@ コマンドライン引数で指定した全引数がそれぞれ""でクオートされて、スペースで区切られたものが並ぶ。
$* コマンドライン引数で指定した全引数が、環境変数 IFS (初期値はスペース)で区切られて、全体が""で括られたものが来る。
command arg1 arg2... では、"arg1 arg2..."となる。
これをそのまま for ループに入れると全体が1つのワードになるのでループが1回で終ってしまうらしい。
IFS=',' ; echo "$*" とすると区切り文字を簡単に変えられるので便利(らしい)
tr, sed いらず?
$@と$*では、$@の方が使う機会は多いみたい。

特例:
  in 変数に入れたい物が省略されると in "$@" が勝手に補完される、らしい。
  こんな使い方しか思い付かない。
  $ vi t.t
   > #!/bin/sh
   > for f ; do
   >  echo $f;
   > done
  $. ./t.t *
  カレントディレクトリのファイルを表示

for ループの例
例1    (カレントディレクトリの *.JPG を表示)
for f in *.JPG ; do
 echo $f ;
done

$ for f in *.JPG ; do echo $f ; done      の様にコマンドラインから1行でも可

例2    (カレントの *.JPG を "*.JPG  *.jpg" ってなぐあいに表示)
for f in *.JPG ; do         
  echo $f ${f%.JPG}.jpg ;    ${f%.JPG}.jpg はシェルのパターン照合演算子
done

例3
(*.JPG を *.jpg にリネーム)
($ mv *.JPG *.jpg ではエラーになってしまう)
for f in *.JPG ; do
 mv $f ${f%.JPG}.jpg ;        
done
   または
$ for f in *.JPG ; do echo $f ${f%.JPG}.jpg ; done

例4
for f in * ; do              1 -> 100
 mv $f $f"00" ;              2 -> 200
done                         3 -> 300    にリネーム

for f in * ; do              1 -> 001
 mv $f "00"$f ;              2 -> 002
done                         3 -> 003   にリネーム

for ループで 1 から 10 までの数字を表示。
for i in [1..10] ; do
 echo $i ;
done
で、いいと思ったんだけど [1..10]がそのまま表示されてしまう。
[] を外したり ... にしたりクオートしたり試したけどうまくいかなかった。
結局 ruby で対処した。 -> ruby 小物 を見てね、ってことになるね。

その後、 Linux magazine '01/01 を見返していたらグッドなやり方が書いてあった。
for i in 1 2 3 4 5 ; do   グッドでもなんでもなく、ただ数字を並べるだけ。
 echo $i
done

もうちっと、ましなやり方。
  for i in `seq 5` ; do
   echo $i
  done
seq は GNU シェルユーティリティに入っていて、ただ数字を表示するだけのコマンド。
1つきざみだけじゃなくて、2つずつとか、浮動小数点とかも出来る。

最近、市販の音楽 CD を買うとコンポに入れずに PC に入れるようになってきた。
コンポが使えないわけじゃないんだけど、なんとなくコンポの蓋を開閉するのがめんどくさい。
それに PC の HD はまだまだ余っているんだから使わなくっちゃね。
cdda2wav で音楽 CD からリップして、~/bin/wav2mp3(中身は/usr/bin/gogo)で mp3 にエンコードする。
そんな時にめんどくさいのが、曲名リスト。
ファイルネームを変えるのもめんどくさいので、 audio.txt としてその中に曲名を書いて行く。
  1:hogehoge
  2:hagehage
  3:...
ってな具合に書いて行く。

で、ここの 1: 2: をいちいち手で入れないでスクリプトで一気に入れたい。
上の for ループで 1 から 10 までの数字を表示 がそれをしたかったトコ。
この時はとりあえず ruby でやっちゃったけど、これで bash でも出来ることになった。
っていうか、後になって Linux magazine に載っている事に気が付いた。
$ cd ~/lib/mp3/artiste/alubam/
$ for i in 1 2 3 ; do echo $i: ; done > audio.txt
$ vi audio.txt
 1:
 2:
 3:
となっているはず。

あとは、1 2 3 の数字を好きなだけ増やすだけ。
この数字の順番を逆にすると、当然出力結果も 3: 2: 1: となってしまうので気を付けましょう。

 あと、いちいち、ちまちま手で入力するのがかったるーーーい、って時は GNU の作ってくれた seq というコマンドを使う。(と書いてあった)
$ which seq
  > /usr/bin/seq

上の例と同じ結果にするのは、数字の部分に seq を入れるだけ。
$ cd ~/lib/mp3/artiste/alubam/
$ for i in `seq 3` ; do echo $i: ; done > audio.txt
$ vi audio.txt
 1:
 2:
 3:
となる(はず)

while ループ
while [条件コマンド] ; do
  コマンド
done
1から3まで表示。
#!/bin/sh
i=3
while [ "$i" -le 3 ] ; do  条件部分は""でくくらないといけない。
 echo $i
 i=`expr $i + 1`       /usr/bin/expr は計算結果を返すので、
done                   $i + 1 の結果が i に代入される。
                     
ネットワーク通じているかな?
#!/bin/sh
while true ; do
 if ping -c 1 202.247.130.5 ; then
   break;
 fi
 echo "packet not reached"
done
echo "packet reached"

if
val="yes"
if [ "$val" = "yes" ] ; then
  echo 'value is yes'
elif [ "$val" = "YES" ] ; then
  echo 'value is YES'
else
  echo 'value is not yes'
fi

case
val="yes"
"$val" in
YES|yes)
  echo "value is yes"
  ;;
*)
  echo "value is not yes"
esac

testコマンド
文字列比較
  [ a = b ]   aとbが同じ文字列なら0を返す
  [ a != b ]  aとbが違う文字列なら0を返す。
数値比較
  [ a -eq b ]  a と bが同じ数値なら0を返す。
  [ a -ne b ]  a と bが同じ数値でないなら0をかえす。
  [ a -lt b ]  a < b なら0を返す。  (less than)
  [ a -le b ]  a <= b なら0を返す。  (less equal)
  [ a -gt b ]  a > b なら0を返す。  (greater than)
  [ a -ge b ]  a >= b なら0を返す。  (greater equal)

シェル関数
スクリプト内での処理をまとめてモジュール化したもので、その関数は通常のコマンドと同じく引数を付けて実行することも出来る。
が、シェル関数で得られた結果を関数値として呼び出し元に返す事は出来ない。
また、スクリプト自体の代替物としてコマンドラインからもシェル関数を実行できる。初めからメモリに保存されているので実行も早い(らしい)。
コマンドラインを制御しているシェルの環境変数を利用することも可能(シェルスクリプトではサブシェル内では呼び出し元の環境変数をそのまま利用することは出来ない)

定義の書式
function 関数名
{
 関数で実行したいコマンド
}
とするものと、
関数名()
{
 関数で実行したいコマンド
}
ここで気を付けなければいけないのは、コマンドと同名のシェル関数を定義した場合は、シェル関数の方がコマンドよりも優先される事と、シェル関数を呼び出す(実行する)前に関数の定義を行うこと。

#!/bin/sh
# echo.hoge
function hoge
{
 echo hoge
}


hoge

として、コマンドラインから実行すると
$ ./echo.hoge
> hoge    となる。

引数を付けて実行
#!/bin/sh
# say
function say
{
 echo "$1: [$2]"
}
$ say hoge hage
> hoge: [hage]    が表示される。

コマンドラインでシェル関数を定義する方法
$ function cd.specs
> {
> cd /usr/src/redhat/SPECS
> }
$ cd.specs
$ pwd
> /usr/src/redhat/SPECS
 通常、シェルスクリプトで cd コマンドを実行してもスクリプトが終了するとカレントディレクトリは変更されていない。
これは、シェルスクリプトがサブシェルという、もう一つのシェルを起動し、その中でディレクトリが変更されるため。
そのためにコマンドラインを制御しているシェルのカレントディレクトリは全く影響を受けない。
しかし、上記のようにコマンドラインを制御しているシェルで定義を行う事でカレントのシェルに影響を与え、ディレクトリを変更することが出来る。
更に、シェル関数でコマンドと同名の関数を定義するとシェル関数の方が優先されるので、既存コマンドのラッパーにもなる。
$ function cd
> {
>   builtin cd "$1"
> pwd
> }
$ cd work/
> /home/hoge/work     ってな事もできる。
ここで、そのまま cd "$1" としないのは定義中のシェル関数 cd が再帰的に実行されるために、組込みコマンド builtin で明示的に組込みコマンドの方の cd を実行している(らしい)

こんなことも出来る。
$ function ps
>{
> command ps l
> }
$ ps
(psの出力結果、細かいから書かない)
通常のコマンドのラッパーには組込みコマンドの command を使うのがいい(らしい)。
unset -f ps
これでシェル関数を削除できる。

addir
 ccd に使うためのディレクトリリスト ~/tmp/ccd.list 作成用のシェル関数。
相対パスで指定したディレクトリでも絶対パスに直す。
$addir /usr/src/redhat/
$addir ../../dev    と、使う。
function addir
{
 local dir       # ローカル変数dirの宣言
# dir にリスト追加するディレクトリを絶対パスで代入
 dir = `if cd "${1:-.}" >2 /dev/null ; then
          pwd
        fi`
# :- はシェルの置換演算子で、対象となるシェル変数(この場合は$1)の内容が空文字列の場合に直後の文字列(この場合は .)で展開した結果に置換する。
# cd の結果を >2 /dev/null しているのは、指定したディレクトリが存在しなかった場合に cd が以上終了し続く pwd がスキップされて、変数dirに No such file or directory のエラーメッセージが入らないようにするため。
# で、cd した後に pwd をするとカレントディレクトリ(指定されたディレクトリ)が絶対パスで表示されるので、その内容を変数dirに代入している。
 if { -z "dir" } ; then
# もし dir の内容が空文字立った場合にエラーメッセージを出して、終了ステート1で終了する。
   echo "addir: $1 not found" > /dev/stderr
   return 1;
 fi
# 最後に絶対パスになったディレクトリを ~/tmp/ccd.listに追加。
# そのときにリストの内容をソートしている。
# sort オプションの -u は行単位でのソート。 -o は入力ファイルと出力ファイルが同じだったときにその内容を一時ファイルにいれてからソートする。これで、cdd.listの内容が失われるのを防ぐ。
# また、出力ファイルの前に指定した - はパイプ経由で入力されるコマンド(この場合はecho)の出力を、他のファイルと同様に扱う事を意味し、ソートの際にパイプ経由で入力された内容が無視されることを防いでいる。
 echo "$dir" | sort -u -o ~/tmp/ccd.list - ~/tmp/ccd.list
}
function ccd
{

 locale dir found
 dir = "${1:-$HOME}"
 case "$dir" in
   .|..|./*|../*|/* )
     ;;
   * )
    if found = `egrep "$dir" ~/tmp/ccd.list | head -1` ; then
       dir = $found
    fi
    ;;
  esac
  echo "--> $dir"
  cd "$dir"
}

これら2つのシェル関数を適当なファイル(例えば~/work/my.test/ccdfunc)に保存して、
$ source ~/work/my.test/ccdfunc
 または
$ . ~/work/my.test/ccdfunc
とすれば、adddir と ccd がコマンドラインから使用できるようになる。

$ addir /usr/src/redhat/SPECS
$ ccd SPECS
> --> /usr/src/redhat/SPECS
となり、カレントディレクトリが変更される。
以上、雑誌の記事丸写し。

ヒアドキュメント
ヒアドキュメントとは...よくわからん。
コマンド実行後に入力するパスワードなんかをスクリプトの中に入れられるらしい。
例:
#!/bin/sh
# test t.t
ftp -n ftp.ascii-linux.com << EOR_FTP    ヒアドキュメントでftpを実行
user user-id password                    ユーザー名とパスワード
ascii                                    転送モードはアスキー
prompt                                   非対話モード
put ${UPFILE} jump.html                  作成したHTMLファイルを転送
quit                                     終了
EOF_FTP                                  ヒアドキュメントも終了

$ ./t.t
とすると、fpt 接続時のパスワードの入力が省けるらしい。

#!/bin/sh
# hia-doc
DATE=`date`
cat <<END
<HTML>
 <HEAD><TITLE>example</TITLE></HEAD>
  <BODY>
   date: $DATE
  </BODY>
</HEML>
END

$./hia-doc
><HTML>
> <HEAD><TITLE>example</TITLE></HEAD>
>  <BODY>
>  date:  Sun Jul 22 00:58:08 JST 2001
>  </BODY>
></HTML>
と表示される。

$ cat <<END > newfile
> 1.t
> 2.t
> 3.t
>
> 4.t
> END
とすると、
$ cat newfile
1.t
2.t
3.t
4.t
といった、内容のファイルを作る事も出来る。
はじめ echo を使ってうまく行かなくて、びびっちゃった。

引き数

cf:
/sbin/service
 #!/bin/sh

 VERSION="`basename $0` ver. 0.91" 
 basename はファイル名からディレクトリと拡張子を取り去る
    
 USAGE="Usage: `basename $0` < option > | --status-all | \ 
 [ service_name [ command | --full-restart ] ]" 
 SERVICE= 
 SERVICEDIR="/etc/init.d" 
 
 if [ $# -eq 0 ]; then
   echo $"${USAGE}" >&2 
   exit 1 
 fi
 $#はbash の特別な引き数で、コマンド自身を除いた、
 引き数自身の数が入る
 だから、ここでは引き数が0だった場合の処理
   
 while [ $# -gt 0 ] 
 引き数の数が0より多かった場合の処理
 do 
  case "${1}" in 
  第一引き数の文字そのものを判定
    --help | -h | --h* ) 
       echo $"${USAGE}" >&2 
       exit 0 
       ;; 
    --version | -V ) 
       echo $"${VERSION}" >&2 
       exit 0 
       ;; 
    *) 
  ヘルプ、バージョン以外の本来の処理に入る 
  以下、カット
  
戻る