Shellあれこれ

a graph image

  1. tcsh
  2. bash
    • GNUプロジェクトによるsh(Bourne Shell)実装
    • born again(生まれ変わり)
    • Linuxの標準shell
    • MacOS Xでも10.3から標準shell(当初はtcshが標準shell)
  3. zsh
    • bashの拡張
    • CSVやAntの補間機能など...高機能すぎて使いきれない...
  4. 今自分はどのshellを使っているか
    > echo $0
    -bash

bashの使い方

  1. コマンド補間
    • [tab]
  2. カーソル移動
    • [←][→]
    • [Ctrl]+[A] 行頭へ
    • [Ctrl]+[E] 行末へ
    • [Ctrl]+[K] カーソル位置から行末まで削除
    • [Ctrl]+[L] 画面クリア clear コマンドと同じ
    • [Ctrl]+[Y] 削除Undo
  3. コマンドヒストリ
    • [↑][↓]
    • !! : 直前のコマンドを実行
  4. ディレクトリヒストリ
    • pushd ディレクトリ
      • ディレクトリ移動。ディレクトリ名をスタックに積む
    • popd
      • スタックからディレクトリ名を読み出す
    • 実行例
      / >pushd /usr/
      /usr ~
      
      /usr >pushd local
      /usr/local /usr ~
      
      /usr/local >pushd bin
      /usr/local/bin /usr/local /usr ~
      
      /usr/local/bin >popd
      /usr/local /usr ~
      
      /usr/local >popd
      /usr ~
      
      /usr >popd
      ~
      
      / >popd
      bash: popd: directory stack empty
      / >
    • cd - : 直前のディレクトリに戻る
  5. Job操作
                             Foreground                  Background
    
    Shell --[ > Command ] --> JOB    ---- [Ctrl+Z] ---> JOB(Sleepgin)
                           (Running) <--- [ > fg ] ----     |
                                                         [ > bg ]
                                                            ↓
    Shell --[ > Command & ] --------------------------> JOB(Runnging)
    • JOB = 実行中のプログラム群(複数のプログラムで一つの仕事をするときにそれらの固まりをJOBと呼ぶ)
    • シェルからコマンドを実行すると、JOBはフォアグラウンドで動く
      • [Ctrl+Z]を押すと、実行中のJOBは停止され、バックグラウンドに移る
      • [bg]コマンドで、バックグラウンドにある停止中のJOBをバックグラウンドで動かすことが出来る
      • [fg]コマンドで、バックグラウンドにある停止中のJOBをフォアグランドに戻す事が出来る
    • シェルからコマンドを"&"つきで実行すると、JOBはバックグラウンドで動く

bashの環境変数

プロンプト

環境変数 PS1 に次の文法でプロンプトに出したいものを定義する

\aASCII のベル文字 (07)
\d"曜日 月 日"のフォーマットによる日付け (例 "Tue May 26")
\eASCII のエスケープ文字 (033)
\h最初の"."のところまでのホスト名
\Hホスト名
\n改行
\r復帰
\sシェル名、$0 のベース名 (最後のスラッシュの後ろの部分)
\t24時間制の HH:MM:SS のフォーマットによる時間
\T12時間制の HH:MM:SS のフォーマットによる時間
\@am/pm をつけた12時間制のフォーマットによる時間
\u現ユーザーのユーザー名
\vbash のバージョン(例 2.00)
\Vbash のリリース番号、バージョンとパッチレベル (例 2.00.0)
\w現在のディレクトリ
\W現在のディレクトリのベース名
\!現在のコマンドのヒストリー番号
\#現在のコマンドのコマンド番号
\$UIDが0なら#、そうでなければ$
\nnn8進数nnnに対応する文字
\\バックスラッシュ
\[表示されない文字列の開始。端末制御シーケンスをプロンプトに埋め込む。
\]表示されない文字列の終り。
 

例:

export PS1="[\w]\$ "
 
[/opt/local/java/db-derby-10.3.3.0]$ ls
CHANGES.html			docs
KEYS				frameworks
LICENSE				frameworks.DEPRECATED.txt
NOTICE				index.html
RELEASE-NOTES.html		javadoc
bin				lib
demo				test
[/opt/local/java/db-derby-10.3.3.0]$

シェルスクリプト

基本構文

  1. abc.sh の先頭行に以下のように記述して、2行目以降にコマンドを羅列する
    #!/bin/bash
    Linuxでは、/bin/sh は /bin/bash のシンボリックリンクなので /bin/sh としても良いが、Solarisでは別物。sh と bash で、test文の振る舞いが違ったりするので /bin/bash と指定しておくのが無難
  2. abc.sh に実行権限を与える
    > chmod +x abc.sh
  3. これで普通のアプリとして実行出来る
    > ./abc.sh

変数

  1. 変数を定義するときには、$は不要
    COUNT=1
  2. 変数を参照するときには > もしくは ${ } をつける
    echo $COUNT
    echo ${COUNT}
  3. variable.sh
    #!/bin/bash
    
    VAL1=1023
    VAL2=1024
    
    echo "$VAL1 + 10     : $(( $VAL1 + 10 ))"
    echo "$VAL1 - 10     : $(( $VAL1 - 10 ))"
    echo "$VAL1 * 10     : $(( $VAL1 * 10 ))"
    echo "$VAL1 / 10     : $(( $VAL1 / 10 ))"
    echo "$VAL1 % 10     : $(( $VAL1 % 10 ))"
    echo "$VAL1 & 255    : $(( $VAL1 & 255 ))"
    echo "$VAL1 | 255    : $(( $VAL1 | 255 ))"
    echo "$VAL1 ^ 255    : $(( $VAL1 ^ 255 ))"
    echo "NOT $VAL1      : $(( !$VAL1 ))"
    echo "NOR $VAL1      : $(( ~$VAL1 ))"
    echo "$VAL1>$VAL2 ?  : $(( $VAL1 > $VAL2 ))"
    echo "$VAL1<$VAL2 ?  : $(( $VAL1 < $VAL2 ))"
    echo "$VAL1>=$VAL2 ? : $(( $VAL1 >= $VAL2 ))"
    echo "$VAL1<=$VAL2 ? : $(( $VAL1 <= $VAL2 ))"
    echo "$VAL1==$VAL2 ? : $(( $VAL1 == $VAL2 ))"
    echo "$VAL1!=$VAL2 ? : $(( $VAL1 != $VAL2 ))"
    echo "$VAL1<$VAL2 && $VAL1!=$VAL2 ? : $(( $VAL1 < $VAL2 && $VAL1 != $VAL2 ))"
    echo "$VAL1>$VAL2 && $VAL1!=$VAL2 ? : $(( $VAL1 > $VAL2 && $VAL1 != $VAL2 ))"
    echo "$VAL1<$VAL2 || $VAL1>$VAL2 ?  : $(( $VAL1 < $VAL2 || $VAL1 > $VAL2 ))"
    
    LITERATURE="ABCDEFGABCDEFGHIJKLMN"
    
    echo "ORIGINAL           : $LITERATURE"
    echo "REPLACE FIRST 'BCD': ${LITERATURE/BCD/XXX}"
    echo "REPLACE ALL 'BCD'  : ${LITERATURE//BCD/XXX}"
    echo "CUT FORMER (FIRST FIND PATTERN): ${LITERATURE#*D}"
    echo "CUT FORMER (LONGEST PATTERN)   : ${LITERATURE##*D}"
    echo "CUT LATTER (FIRST FIND PATTERN): ${LITERATURE%D*}"
    echo "CUT LATTER (LONGEST PATTERN)   : ${LITERATURE%%D*}"
  4. 実行結果
    > ./variable.sh
    1023 + 10     : 1033
    1023 - 10     : 1013
    1023 * 10     : 10230
    1023 / 10     : 102
    1023 % 10     : 3
    1023 & 255    : 255
    1023 | 255    : 1023
    1023 ^ 255    : 768
    NOT 1023      : 0
    NOR 1023      : -1024
    1023>1024 ?  : 0
    1023<1024 ?  : 1
    1023>=1024 ? : 0
    1023<=1024 ? : 1
    1023==1024 ? : 0
    1023!=1024 ? : 1
    1023<1024 && 1023!=1024 ? : 1
    1023>1024 && 1023!=1024 ? : 0
    1023<1024 || 1023>1024 ?  : 1
    ORIGINAL           : ABCDEFGABCDEFGHIJKLMN
    REPLACE FIRST 'BCD': AXXXEFGABCDEFGHIJKLMN
    REPLACE ALL 'BCD'  : AXXXEFGAXXXEFGHIJKLMN
    CUT FORMER (FIRST FIND PATTERN): EFGABCDEFGHIJKLMN
    CUT FORMER (LONGEST PATTERN)   : EFGHIJKLMN
    CUT LATTER (FIRST FIND PATTERN): ABCDEFGABC
    CUT LATTER (LONGEST PATTERN)   : ABC

コマンド引数

  1. arg.sh
    #!/bin/bash
    
    echo "TOTAL LENGTH OF ARGS : $#"
    
    CNT=1
    
    echo "ARG(0)=$0"
    
    until [ -z $1 ]
    do
      echo "ARG($CNT)=$1 (OTHER ARGS : $#)"
      CNT=$((CNT+1))
      shift
      
    done
    • $# : 引数の個数
    • $0 : 実行コマンド名
    • shiftコマンド : for( n=1 ; n<($#-1) ; n++ ){ 第n引数 <-- 第n+1引数; } $# = $# -1;
    • shiftコマンドでは、第0引数(コマンド名)は変わらないことに注意
  2. 実行結果
    > ./arg.sh arg1 arg2 arg3
    TOTAL LENGTH OF ARGS : 3
    ARG(0)=./arg.sh
    ARG(1)=arg1 (OTHER ARGS : 3)
    ARG(2)=arg2 (OTHER ARGS : 2)
    ARG(3)=arg3 (OTHER ARGS : 1)

関数

  1. function.sh
       #!/bin/bash
       
       GLOBAL_VAL=123
       
     func1(){
         echo "----------FUNC1 START----------"
    
       echo "FUNC1 ARG(0)=$0"
       echo "FUNC1 ARG(1)=$1"
       echo "FUNC1 ARG(2)=$2"
         echo "GLOBAL_VAL  =$GLOBAL_VAL"
    
         GLOBAL_VAL=321
         PRIVATE_VAL=987
    
         echo "----------FUNC1 END  ----------"
    
       return 0
       }
    
       echo "---------------SHELL START---------------"
    
     if func1 A1 A2
       then
         echo "FUNC1 RETURNED TRUE"
       else
         echo "FUNC2 RETURNED FALSE"
       fi
    
    
     echo "SHELL ARG(0)=$0"
     echo "SHELL ARG(1)=$1"
     echo "SHELL ARG(2)=$2"
    
     echo "GLOBAL VALUE =$GLOBAL_VAL"
     echo "PRIVATE VALUE=$PRIVATE_VAL"
    
       echo "--------------SHELL END  ---------------"
  2. 実行結果
    > ./function.sh S1 S2
       ---------------SHELL START---------------
       ----------FUNC1 START----------
     FUNC1 ARG(0)=./function.sh
     FUNC1 ARG(1)=A1
     FUNC1 ARG(2)=A2
       GLOBAL_VAL  =123
       ----------FUNC1 END  ----------
     FUNC1 RETURNED TRUE
     SHELL ARG(0)=./function.sh
     SHELL ARG(1)=S1
     SHELL ARG(2)=S2
     GLOBAL VALUE =321
     PRIVATE VALUE=987
    --------------SHELL END  ---------------
  3. 解説
    • 関数はこんな風に定義します。()はただの飾りで引数は入れません
    • 関数はこんな風に呼び出します。関数への引数はスペース区切りで渡します
    • ◆銑 関数への引数は、通常のシェル呼び出しと同様に受け取ります
    • 関数の返値は return コマンドで返します。これもC言語とは逆に 0 が真、1 が偽
    • А銑 シェル・関数の引数は局所変数
    • 〜 変数はどこで定義されても大域変数になる

他のシェルファイルの読み込み

  1. common.sh
    #!/bin/bash
    
    sqrt(){
      return $(($1*$1))
    }
    
    isPositive(){
      if [ $1 -gt 0 ]
      then
        return 0
      else
        return 1
      fi
    }
    
    isNegative(){
      if [ $1 -lt 0 ]
      then
        return 0
      else
        return 1
      fi
    }
  2. calc.sh
    #!/bin/sh
    
    . common.sh
    
    sqrt $1
    echo "SQRT($1)=$?"
    
    if isPositive $2
    then
      echo "$2 is Positive"
    else
      echo "$2 is not Positive"
    fi
    
    if isNegative $3
    then
      echo "$3 is Negative"
    else
      echo "$3 is not Negative"
    fi
    • "."コマンドで他のシェルファイルを読み込める。"."は、sourceコマンドのエイリアス
  3. 実行結果
    > ./calc.sh 2 3 4
    SQRT(2)=4
    3 is Positive
    4 is not Negative

終了コード(シェルの返値は8bit整数な事に注意)

  1. exit.sh
    #/bin/bash
    exit $1
  2. 実行結果
    > ./exit.sh 0
    > echo $?
    0
     
    > ./exit.sh 1
    > echo $?
    1
     
    > ./exit.sh -1
    > echo $?
    255
     
    > ./exit.sh 1023
    > echo $?
    255

if文

  1. if文の構造
    if コマンド
    then
      コマンド成功時(返値0の時)の処理
    else
      コマンド失敗時(返値0以外の時)の処理
    fi
  2. if文の後のコマンド返値によって条件分岐を行う
  3. 真偽値はC言語と逆
  4. 条件判断には AND / OR も使える
    • if コマンド1 && コマンド2
    • if コマンド1 || コマンド2
  5. 多くの場合条件判断には test コマンドを使う
  6. 諸事情有って then で何もしたくないときには、何もしないコマンド : を実行する
    if コマンド
    then
      :
    else
      コマンド失敗時(返値0以外の時)の処理
    fi

while/until文

  1. while
    while コマンド
    do
      コマンド返値が真(0)の時の処理
    done
  2. until
    until コマンド
    do
      コマンド返値が偽(0以外)の時の処理
    done
  3. while.sh
    #!/bin/bash
    
    COUNT=1
    
    ### WHILE LOOP ###
    
    while [ $COUNT -le 10 ]
    do
      echo -n "$COUNT ,"
      COUNT=$COUNT + 1
    done
    
    echo WHILE LOOP END : COUNT = $COUNT
    
    ### UNTIL LOOP ###
    
    until [ $COUNT -le 0 ]
    do
      echo -n "$COUNT ,"
      COUNT=`expr $COUNT - 1`
    done
    
    echo UNTIL LOOP END : COUNT = $COUNT
  4. 実行結果
    > ./while.sh
    1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,WHILE LOOP END : COUNT = 11
    11 ,10 ,9 ,8 ,7 ,6 ,5 ,4 ,3 ,2 ,1 ,UNTIL LOOP END : COUNT = 0
  5. 一行ずつ読み込み
    • コマンド結果
      sed -e '/s/hoge/geho/g' foo.txt | while read line;
      do
        echo $line
      done
    • ファイルからの読み込み
      while read line;
      do
        echo $line
      done < file.txt
    • スクリプト内の文章
      while read line;
      do
        echo $line
      done <<END
      hoge
      geho
      END

testコマンド

  1. コマンド引数の真偽を判断する
    • 「 test コマンド引数 」と「 test (コマンド引数) 」は等しい
    • 「 test コマンド引数 」と「 test !コマンド引数 」は真偽が逆になる
  2. [ ]コマンドがエイリアスしている
    [~]$ which [
    /bin/[
    • 「 test -f /bin/sh 」=「 [ -f /bin/sh] 」
  3. ファイルの存在を判断する
    • test -e FILE ファイルが存在するか?
    • test -d FILE ファイルが存在し、それはディレクトリか?
    • test -f FILE ファイルが存在し、それは普通のファイルか?
  4. 文字列を判断する
    • test -n STRING 0文字以上の文字列か?
    • test -z STRING 0文字の文字列か?
    • test STRING1 = STRING2
    • test STRING1 != STRING2
    • 重要: 変数を"" で囲む必要あり!
      [~]$ ./cmp.sh 
      $ARG=
      "test -n $ARG" is 0
      "test -z $ARG" is 0
      "test -n "$ARG"" is 1
      "test -z "$ARG"" is 0
      [~]$ ./cmp.sh aiueo
      $ARG=aiueo
      "test -n $ARG" is 0
      "test -z $ARG" is 1
      "test -n "$ARG"" is 0
      "test -z "$ARG"" is 1
      (0:TRUE / 1:FALSE) C言語と逆
  5. 整数値を判断する
    • test INTEGER1 -eq INTERT2 EQual
    • test INTEGER1 -ne INTERT2 Not Equal
    • test INTEGER1 -gt INTERT2 INTEGER1 is Greater Than INTEGER2
    • test INTEGER1 -ge INTERT2 INTEGER1 is Greater than or Equal to INTEGER2
    • test INTEGER1 -lt INTERT2 INTEGER1 is Less Than INTEGER2
    • test INTEGER1 -le INTERT2 INTEGER1 is Less than or Equal to INTEGER2

正規表現による比較

#!/bin/bash

WORD="I'm AHO."

if [[ $WORD =~ ^I.*[.]$ ]]
then
  echo "\"${WORD}\"は、Aではじまり.で終わります"
fi

if [[ $WORD =~ AHO ]]
then
  echo "\"${WORD}\"は、AHO に部分一致します"
fi

if [[ ! $WORD =~ BAKA ]]
then
  echo "\"${WORD}\"は、BAKA に部分一致しません"
fi

case $WORD in
  "I'm "?*)
    echo "\"${WORD}\"は、\"I'm\"なんとかです"
    ;;
  "You're "?*) 
    echo "\"${WORD}\"は、\"You're\"なんとかです"
    ;;
  *)
    echo "Not Match"
    ;;
esac
 
[~]$ ./regex.sh 
"I'm AHO."は、Aではじまり.で終わります
"I'm AHO."は、AHO に部分一致します
"I'm AHO."は、BAKA に部分一致しません
"I'm AHO."は、"I'm"なんとかです
  • if で同値比較をする "[ ]" はコマンド。test の alias 
  • if での正規表現比較は "[[ ]]"
    • "[[ ]]"は、bash の syntax なので正規表現を""で囲まない
    • if [[ $WORD =~ ^I.*[.]$ ]] は、Iで始まり.で終わるという正規表現に合致するかを検証
    • if [[ $WORD =~ "^I.*[.]$" ]] は、"^I.*[.]$"という文字列に合致するかを検証
  • case 文でもパターンマッチングはできるが、簡易的な記法しか使えない
    • * : 任意の文字列
    • ? : 1文字
    • スペースを含む場合には "" で囲む。"I'm " または I'm" "
    • *、? は、"" で囲まない。(囲ったら文字列としての *、? になる)

for文

  1. 基本機能はスペース・改行区切りの文字列を切り出す
    # !/bin/bash
    
    for str in abc def ghi jkl mno pqr stu vwx yz
    do
      echo $str
    done
  2. 実行結果
    > ./for.sh
    abc
    def
    ghi
    jkl
    mno
    pqr
    stu
    vwx
    yz
  3. 普通は、カレントディレクトリ以下のファイルに対してムニャムニャするという使い方をする
    # !/bin/bash
    
    for file in *.txt
    do
      echo "$file --> /tmp/$file"
      cp $file /tmp/$file
    done
  4. 実行結果
    > ./for.sh
    gol.txt --> /tmp/gol.txt
    log.txt --> /tmp/log.txt

case文

  1. ほぼコマンド引数の解釈にしか使わない。
    #!/bin/bash
    
    until [ -z $1 ]
    do
      case $1 in
      -v)
        shift ; VERBOSE=ON
        ;;
      -src)
        shift ; SRC=$1 ; shift
        ;;
      -dest)
        shift ; DEST=$1 ; shift
        ;;
      *)
        echo "Usage case.sh [-v] [-src] [SOURCE FILE] [-dest] [DEST_FILE]"
        exit -1
        ;;
      esac
    done
    
    echo "VERBOSE_MODE =${VERBOSE:=OFF}"
    echo "SOURCE_FILE = ${SRC:=UNDEF}"
    echo "DEST_FILE = ${DEST:=UNDEF}"
  2. 実行結果
    > ./case.sh -src abc.dat -dest def.txt
    VERBOSE_MODE =OFF
    SOURCE_FILE = abc.dat
    DEST_FILE = def.txt

デバック

> bash -x ./case.sh -src abc.dat -dest def.txt
+ '[' -z -src ']'
+ case $1 in
+ shift
+ SRC=abc.dat
+ shift
+ '[' -z -dest ']'
+ case $1 in
+ shift
+ DEST=def.txt
+ shift
+ '[' -z ']'
+ echo 'VERBOSE_MODE =OFF'
VERBOSE_MODE =OFF
+ echo 'SOURCE_FILE = abc.dat'
SOURCE_FILE = abc.dat
+ echo 'DEST_FILE = def.txt'
DEST_FILE = def.txt

Tips

パイプを使うと別プロセスになることに注意

  • PATH に、あるディレクトリ以下のライブラリを全部足したいときに、こう書いてはまったことがある
     #!/bin/bash
    
     MEM="-Xms16m -Xmx1024m -XX:MaxPermSize=256m"
     JAVA_HOME=/usr/lib/jvm/jre-1.6.0-openjdk/
     PATH=$JAVA_HOME/bin
     CLASSPATH=
     
     LIB_DIR=/opt/myApp/lib
     
     ls ${JAR_DIR} | while read line
     do
       CLASSPATH=$CLASSPATH:$line
     done
     
     echo $CLASSPATH
     java -cp $CLASSPATH $MEM com.snail.myApp
    
  • 16 行目の $CLASSPATH に、/opt/myApp/lib 以下にある JAR ファイル名は格納されない。空文字*1になる
  • パイプを使うと別プロセスになるので、6,15,16 行目の CLASSPATH と、12 行目の CLASSPATH は別物
  • こういうときには for 文を使う
     #!/bin/bash
    
     MEM="-Xms16m -Xmx1024m -XX:MaxPermSize=256m"
     JAVA_HOME=/usr/lib/jvm/jre-1.6.0-openjdk/
     PATH=$JAVA_HOME/bin
     CLASSPATH=
     
     LIB_DIR=/opt/myApp/lib
     
     for line in ${JAR_DIR}/*.jar
     do
       CLASSPATH=$CLASSPATH:$line
     done
     
     echo $CLASSPATH
     java -cp $CLASSPATH $MEM com.snail.myApp
    
  • どうしても while 文を使い時には
    ls ${JAR_DIR} >| /tmp/jars
    while read line
    do
      CLASSPATH=$CLASSPATH:$line
    done < /tmp/jars
    rm /tmp/jars
    とかもできるけど

Shell Script 内で、自分の置かれている絶対パスを得るには

  • example.sh
    #!/bin/bash
    
    HOME=$(cd $(dirname $0) && pwd)
    
    echo $HOME
    
    実行プログラム名($0) のディレクトリに cd して、pwd でその絶対ディレクトリ名を得る。$() 内で cd しているので、Shell Script のカレントディレクトリは変わらない
  • 実行結果
    [~]$ ./example.sh 
    /Users/Atsushi

Computer


*1 java -cp -Xms16m -Xmx1024m -XX:MaxPermSize?=256m com.snail.myApp

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-08-14 (火) 19:37:36 (801d)
ISBN10
ISBN13
9784061426061
Tex → Image Converter