シェルスクリプトに関する覚え書き

〜Bournシェル(sh)のスクリプトに関するメモ〜

cshは使うな

Bournシェル(sh)にはできて、cshにはできない事が結構ある。
はまる迄は便利だと思っていたが、はまった後、脱出までに費やした時間と労力を考えると…
リダイレクションとファイル操作ではまる事が多い。機能が貧弱というか…

何故 Bashじゃダメなの?

あなたが、この先の人生でLinux系しか使わないならBashでも構わないしBashに特化しても構わない。
しかし、世の中には怪しい環境が多いのです。
solaris なんかは shしか入ってないとこが結構あります。
組込系などでは BusyBox という ash しか入ってないパッケージが多いです。(VMware vShpere も BusyBox を利用してます)
そんな環境でも、先頭に#!/bin/sh なら問題なく動作します。(これはPOSIX標準シェルがshであることも影響してます。)

コマンドの基本

unixのコマンドは標準入力から受け取ったデータを処理し、結果を標準出力に吐くのが基本です。
awk, bzip2, commpress, grep, sed なども 標準入力 → 加工 → 標準出力 の流れが基本です。
コマンドオプション(コマンドパラメタ)は、処理内容を指示するものであってデータそのものを指定するものではありません。
例外となる echo と cat が頻繁に使われるのが謎ですけど

tee コマンド

例として tee コマンドを挙げます。
標準入力から入力した文字列を、コマンドオプション(パラメタ)で指定されたファイルと標準出力に吐くコマンドです。
パラメタ指定がおかしいとか、権限がなくてファイルが開けないといったエラーが発生した場合は標準エラー出力にエラーを吐きます。

1

teeコマンドの具体的な使用例

tail -f access.log | grep error | tee error.log | grep fatal > fatal.log これは、以下の様に書ける。 #!/bin/sh # パイプ(|)の後ろの改行は、継続行として扱われる。 tail -f access.log | # 継続行を丸々コメント行にできる grep error | # エラー行だけ error.logに出力 tee error.log | # さらに fatal error だけ fatal.logに出力 grep fatal > fatal.log

スクリプトの基本形

前述のコマンドの基本に沿って作成します。
スクリプト内部でファイルを開くのは極力避けます。
while read line … EOFが来るまでループ。 do $line 〜〜 … 1行分の処理を行う。 echo データ出力 … 標準出力に結果を出力する。 echo エラー出力 1>&2 … エラーメッセージは標準エラーに出力する。 done

文法

特殊文字

以下の文字はエスケープする必要がある
;  & ( ) | ^ < > ? * [ ] $ ` " ' { } [TAB] [SPACE]

※ シングルクォートとダブルクォートの違い
  • シングルクォート(')
    すべて普通の文字として扱う
  • ダブルクォート(")
    $ ` \ 以外を普通の文字として扱う
    (変数置換とコマンド置換が行われる)

引数
基本形:

$1 $2 $3 ... $9

複数指定:

$*と$@の使い分けが難しい(判断が微妙)

  • $* 引数全部
    "$*" → "$1 $2 $3 $4 … $9"
  • $@ 引数全部
    "$@" → "$1" "$2" "$3" "$4" … "$9"
    ※引数にスペースが含まれていると分割されるので注意が必要
  • $# 引数の個数
    引数がない場合は0
  • shift
    引数を1個ずらす

変数
export 変数名

この宣言を行わないと子プロセスで参照できない

特殊な変数

$? :最後に実行したコマンドのexit値。
$$ :シェル自身のプロセスID 。
$! :シェルが最後に起動したバックグラウンドプロセスのプロセスID。
$0 :スクリプトの名前(プログラム名)
$- :シェルの起動オプション

ワイルドカード

『Bournシェル(sh)の展開は正規表現ではない』

任意の1文字
任意の複数文字
[a-f],[adf]
指定の1文字
[!a-fjkl]
指定以外の1文字

ワイルドカード展開の抑止
echo "*"
echo '*'
echo \*

リダイレクト

ls >outputfile 2>&1
ls >>outputfile 2>&1

$ command >file 2>&1

 標準出力と標準エラー出力ともに file に出力される。

$ command 2>&1 >file

 標準出力が file に出力され、標準エラー出力は標準出力(この場合は端末)に出力される。

リダイレクトの意味

プログラムはファイルディスクリプタ0から入力し、ファイルディスクリプタ1および2に出力する。
2>&1 という記述は、ファイルディスクリプタ1の指す内容(ファイルの実体)をファイルディスクリプタ2に代入する事を意味する。

コマンドの連結(バックグラウンド・プロセスの起動)
  |  A|B   Aの標準出力をBの標準入力にリダイレクトして並列実行。   &  A&B   Aをバックグラウンドで起動。(Bは省略可能)   && A&&B  Aの終了を待ち、終了ステータスが0ならばBを実行。   ;  A;B   Aが終了した後、Bを実行(順次実行)
コマンドのグループ化
()内を別プロセスで実行する。
※ リダイレクトは()単位で行われる。

(ls dir1;ls dir2) > outputfile

コマンドを単純に別プロセスで実行したい場合
(ls dir1) &

コマンド置換(`逆引用符)
 ``内のコマンドを実行した結果で置換される  FILENAME=BK`date +%Y%m%d_%H%M%S`  TARGET=$LOG_PATH/$FILENAME.tar.gz ※注意点 `date +%Y%m%d %H%M%S` という記述はlinuxではエラーになる。(bashの問題?) `date "+%Y%m%d %H%M%S"` とクォートすれば通る。 なお、solarisではクォートは不要。
コマンド置換の抑止

’で括る。
(”で括った場合、変数置換・コマンド置換が実行される)

ヒアドキュメント
label_Stringまでをリダイレクトする cat <<label_String 1行目 2行目 3行目 label_String
パイプ
 read -p 変数名 [変数名 変数名 変数名 ...]   名前付きパイプから読み取る  print -p "文字列"   パイプに書き出す ・コンソールからの入力  read [変数名[?入力プロンプト表示文字列]] [変数名[?入力プロンプト表示文字列]] ...
算術演算
 Bourneシェルには、算術演算の機能がない。(文字列変数しかない)  外部コマンド expr を実行して演算を行う。   iCnt="1"   iCnt=`expr $iCnt + 1` ※ =の前後に空白を入れるとエラーになるので注意。
expr
 外部コマンド  乗算記号 * は、\*, '*'と書かないと駄目 rc=`expr $1 \* $2`  &   AND  |   OR  !   NOT  <  <=  >  >=  =   等号が2種類ある点に注意  ==  !=  +   +  -   −  *   ×  /   ÷  %   剰余   文字列 : 正規表現    一致した時、真を返す
bc

exprは整数のみ対応であるため、0.125などを入力するとエラーになる
この場合は bc を利用する。
rc=`echo "0.125 * 1024" | bc`

条件式
 [ condition ] ※ これは、test condition の省略形  (要するにBourneシェルには演算機能がない)   [ → test と置換される。   ※ [ の直後に演算子を、空白を入れずに書くとエラーになるので注意   ※ ]は、testコマンドの引数として処理される(無視される)
testコマンド
 以下の終了コードを返す。   条件が成り立つ(真)なら0   条件が成り立たない(偽)なら0以外  ※ 第一引数は "で括ること。   (第一引数が[空|未定義]のとき、エラーになるため)
論理演算
    記号  意味      例     !     NOT       !condition     -a    AND       condition1 -a condition2     -o    OR        condition1 -o condition2
数値比較
  記号  意味      例   -eq   ==        condition1 -eq condition2   -ne   !=        condition1 -ne condition2   -lt   <         condition1 -lt condition2   -gt   >         condition1 -gt condition2   -le   <=        condition1 -le condition2   -lg   >=        condition1 -lg condition2
ファイル属性
 [ -f filename ]   記号  意味   -s    ファイルサイズが0以外ならば真を返す   -r    読取可能ならば真を返す   -w    書込可能ならば真を返す   -x    実行可能ならば真を返す   -f    通常ファイルならば真を返す   -d    ディレクトリならば真を返す
文字列比較
真となる条件            記述式 -------------------------------------------- 文字列の長さが 0        -z 文字列 文字列の長さが 1 以上   -n 文字列 一致                    文字列1 = 文字列2 不一致                  文字列1 != 文字列2 大小比較                文字列1 < 文字列2 大小比較                文字列1 > 文字列2 ※注意点 if [ -z ${etc} ] という記述はsolarisではエラーになるので注意。 (if [ -z "${etc}" ] ならば通る。)
文字列長の参照
  echo ${#EXT}
制御構造
関数定義
 使う前に定義されていなければならない。 関数名() { echo etcetc. return -1; }  ・()はダミー(大域変数しか存在しない)  ・関数以外の行から実行される。  呼び出しは、単純に関数名を記述する。 関数名 param1 param2 param3 param4 ...  ※ 関数の呼び出しは、コマンドの呼び出しと同じ形式になる。 ex) FuncA param1 param2 param3 if [ $? -ne 0 ] then echo "ERROR!" exit 203 fi 上記の場合、こう書いてもよい。 if FuncA param1 param2 param3 then echo "ERROR!" exit 203 fi
直前のコマンドの終了コード
 $? 復帰値が0ならば〜   [ $? -eq 0 ]
if文
if [  ] then else fi  ※判定がCとは逆なので注意。 0:真 1:偽(0以外は全て偽)
case文
引数1の判定の例 case $1 in   ONE*)   ls   ;;   "TWO")   echo   ls   ;;   *)   echo OTHER STORNG   ;; esac
for文
for i in 1 3 5 7 do     [break]     [continue] done
while文
 条件が真(0)ならば繰り返し while [ ] do     [break]     [continue] done ex) 無限ループの例   while true   do     command &     sleep 30   done
until文
 条件が真(0)以外ならば繰り返し until [ ] do     [break]     [continue] done
trap を用いたシグナル処理
 一時ファイルの削除処理等に使用する  trap 'rm $tempfile; exit' 0 1 2 13 15  上記は、終了コードが  0 1 2 13 15 の場合、  rm $tempfile; exit を実行する。
変数名の解釈
EXT="input name" 内容の単純参照   echo $EXT   echo ${EXT}   {}を使用する理由   echo ${EXT}ABC
インクルード

ピリオド(.)がインクルード命令になっている。

. filename
  その位置にfileを読み込む

インクルードファイルの位置を自動算出する
CWD=`dirname $0`
. ${CWD}/config.sh

正規化されたパスが必用な場合
BASE_DIR=(cd $(dirname $0); pwd)

文字列の切り出し

rc=`echo ${str} | cut -d, -f1`
  カンマ区切りの最初の項目を取得する

●デバッグ

sh -x sample.sh
実行時チェック
sh -x sample.sh
実行行を展開して表示(Bash)
sh -u sample.sh
初期化されていない変数の参照をエラーにする
sh -v sample.sh
実行行を表示
sh -n sample.sh
構文チェック(非実行)
実行しているシェルの名を知る
echo $0

mailto: spam_spam_spam_spam_spam_spam@mbj.nifty.com
Last update:2019/03/17