Memo/Linux/ShellScript

http://dexlab.net/pukiwiki/index.php?Memo%2FLinux%2FShellScript
 

bash:シェルスクリプト


入力プロンプト

  • -sを付けると入力文字が非表示になるのでパスワードに使える
    1. read -p "proceed ? (y/n)" answer;echo $answer
    2. proceed ? (y/n) y
    3. y

URIの分解


tmpwatchで一定時間以上経過したファイルを削除

  • filemy-tmpwatch
    1. sudo yum install tmpwatch
    2. sudo cp my-tmpwatch /etc/cron.daily/
    3. sudo chmod +x /etc/cron.daily/my-tmpwatch

コマンドを探す(which, hash, type)

  • whichはコマンド
  • hash, typeはbash組み込みコマンド
    • typeが良さそう
  • typeを使ってdigコマンドが無かったらエラーとする
    1. if ! $(type dig > /dev/null 2>&1); then
    2.   echo -e "ERROR\tdig is not found." 1>&2
    3.   exit 1
    4. fi

0埋め

  • printfを使う。C言語と同じ構文のようだ
    1. for i in {1..10}; do name=$(printf file%02d.log $i); echo $name; done
    2. file01.log
    3. file02.log
    4. ...
    5. file10.log

文法(syntax)チェック

  • bash, shも "-n"でチェックできる
    1. bash -n example.sh

バッググラウトプロセスの終了待ちと終了ステータス

  • 「$!」でバックグラウンドプロセスのPIDを取得できる
  • 「wait PID」 でプロセスの終了まで待ち、「$?」で終了ステータスを取得
  • 子プロセスから「$PPID」親PIDを取得できるが、バックグラウンドだと全て「1」になる。
  • サンプル:待ち時間と終了コードを同じにしている。filewaiting-for-parallel-tasks.zip
    1. ./parent.sh
    2.  
    3. parent.sh::PID: 6307
    4. run: 1, wait: 4, pid: 6310
    5. run: 2, wait: 9, pid: 6311
    6. run: 3, wait: 3, pid: 6312
    7. wait child pid: 6310, exit: 4
    8. wait child pid: 6311, exit: 9
    9. wait child pid: 6312, exit: 3

パスからファイル名/拡張子等の切り出し

  1. tmp_path="/etc/httpd/conf/httpd.conf"
  2. tmp_file=${tmp_path##*/} # httpd.conf
  3. echo $tmp_file # httpd.conf
  4. echo ${tmp_file%.*} # httpd
  5. echo ${tmp_path%/*} # /etc/httpd/conf
  6. echo ${tmp_path##*.} # conf

デーモンを作る


select: 選択式メニュー

選択式メニューを表示し、数字を入力するタイプ。

  • fileselect-yes-no.sh
    1. bash select-yes-no.sh
    2. 1) yes
    3. 2) no
    4. 3) all
    5. which ? > 1
    6.  
    7. 1

expect: 対話的なコマンド(ssh, telnet, ftp等)を自動実行

  • Man page of EXPECT
    • expect "...", send "..." のように'"'を使う。"'"は動作しなかった
  • 例:YAMAHA RT1xxxルータにtelnetでログインしてNATテーブル数を取得
    1. #!/bin/bash
    2. get_nat_table_count(){
    3. host=$1
    4. password=$2
    5. timeout=${3:-5}
    6. expect -c "
    7. set timeout $timeout
    8. spawn telnet $host
    9. expect \"Password:\" ; send \"$password\n\"
    10. expect \">\" ; send \"console character ascii\n\"
    11. expect \">\" ; send \"console lines infinity\n\"
    12. expect \">\" ; send \"show nat descriptor address\n\"
    13. expect \">\" ; send \"exit\n\"
    14. expect eof" | grep used | awk 'BEGIN {sum=0} {sum+=$6} END {print sum}'
    15. }
    16.  
    17. HOST=192.168.1.1
    18. PASSWORD=****
    19. get_nat_table_count "$HOST" "$PASSWORD"

タイムアウト処理

  • 例:mysqladmin pingが成功するまで、最大10分待機
    1. wait_connection() {
    2. local db_host=${1:-"127.0.0.1"}
    3. local db_user='root'
    4. local max_wait_sec=600
    5. local wait_sec=10
    6. local mysqladmin=/usr/bin/mysqladmin
    7. local start_sec=$SECONDS
    8. local end_sec=$SECONDS
    9.  
    10. if [ -f "$mysqladmin" ]; then
    11. while [ $(( $end_sec - $start_sec )) -le $max_wait_sec ]; do
    12. $mysqladmin ping -u $db_user -h $db_host > /dev/null 2>&1
    13. if [ $? == 0 ]; then
    14. break
    15. fi
    16. sleep $wait_sec
    17. end_sec=$SECONDS
    18. done
    19. else
    20. sleep $max_wait_sec
    21. fi
    22. return 0
    23. }

浮動小数点演算

  • デフォルトでは整数しか扱えない
  • bcで浮動小数点を含む演算をできる。scale=2;で小数点2桁まで計算
    1. echo "1.0 + 0.1 + 0.15" | bc
    2. 1.25

パイプで複数繋げたコマンドの終了ステータスを取得

  • $?はパイプの一番最後の終了ステータスしか取得できない
    1. true | false | (exit 3) | (exit 8)
    2. echo $?
    3. 8
  • ${PIPESTATUS[@]} を使うとパイプで繋いだコマンドの終了ステータスを取得できる
    1. true | false | (exit 3) | (exit 8)
    2. echo ${PIPESTATUS[@]}
    3. 0 1 3 8

cron実行時に多重起動の防止

  • flock (CentOS5以上)を使う。ただし、コマンドの終了コードは取れない。flockが成功時に0、失敗時に1になる
    1. crontab -e
    2. ----
    3. * * * * * /usr/bin/flock -n /tmp/test.lock /tmp/test.sh $(date '+\%Y-\%m-\%d \%H:\%M:\%S') >> /tmp/test.log 2>&1
    4. ----
  • PIDのロックファイルを作って防止する方法
    1. #!/bin/bash
    2.  
    3. export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
    4. readonly SCRIPT_NAME=$(basename $0)
    5. readonly LOCK_FILE=/tmp/${SCRIPT_NAME}.lock
    6.  
    7. lock() {
    8. if [ -f $LOCK_FILE ]; then
    9.  PID=$(cat $LOCK_FILE)
    10.  if (ps -e | grep -P "^\s*$PID" >/dev/null); then
    11.    echo $SCRIPT_NAME' is already running.' >&2
    12.    exit 1
    13.  fi
    14. fi
    15. echo $$ > $LOCK_FILE
    16. }
    17.  
    18. unlock() {
    19. rm -f $LOCK_FILE
    20. }
    21.  
    22. main() {
    23. lock
    24. for i in $(seq 1 7); do
    25. echo "PID=$$, seq:$i"
    26. sleep 10
    27. done
    28. unlock
    29. }
    30.  
    31. main
  • サンプルとしてよくある、pgrepの方はcronからの実行時に常に二重起動と判定されるため使えない。

日付の範囲指定

  • ソース
    1. #!/bin/bash
    2. callback() {
    3. local date=$1
    4. echo $date
    5. }
    6.  
    7. date_loop() {
    8. local callback=$1
    9. local start=$2
    10. local end=$3
    11.  
    12. while true; do
    13. eval "$callback $start"
    14. if [ "$start" == "$end" ]; then
    15. break;
    16. fi
    17. start=$(date "+%Y-%m-%d" -d "${start} 1 day")
    18. done
    19. }
    20.  
    21. date_loop "callback" "2012-02-28" "2012-03-01"
  • 実行結果
    1. 2012-02-28
    2. 2012-02-29
    3. 2012-03-01

$()演算子

  • コマンドの実行し、結果を変数に取り込める。配列も可
    1. ls_result=$(ls)
    2. for temp in ${ls_result[@]};do
    3.     echo ${temp}
    4. done
  • ``演算子と違い、入れ子にできる。「カレントディレクトリの*.shからhogeを含むファイル名を得る」
    1. result=$(grep -l "hoge" $(ls *.sh))

netstatで接続中のコネクション数を数える

  1. # get_netstat_established_count
  2. # @param int port : tcp port
  3. # @return int count
  4. get_netstat_established_count() {
  5. local port=$1
  6. local established_count=`netstat -nt | grep ":${port} " | grep "ESTABLISHED" | wc -l`
  7. echo $established_count
  8. }

エラー処理をシンプルに書く

  • {...}で括らないとexitが実行されないので注意
    1. command ... || { echo ... ; exit 1 ; }
    • 例:通常(mkdir -pを使えば良いが)
      1. mkdir tmp > /dev/null 2>&1
      2. if [ $? -ne 0 ]; then
      3.   echo "error"
      4.   exit 1
      5. fi
    • 例:シンプル化
      1. mkdir tmp > /dev/null 2>&1 || { echo "error" >&2; exit 1; }

バッチ処理

bashのバージョン情報の取得

  1. bash --version | perl -ane 'if($.==1 && /version (\d+)\./){ print $1;}'
  2. # 3

a-z等の文字列が欲しい

  1. var=`echo {0..10}` # 0 1 2 3 4 5 6 7 8 9 10
  2.  
  3. var=`echo {a..z}` # a b c d e f g h i j k l m n o p q r s t u v w x y z

空ディレクトリチェック

  1. if [ -z "$(ls -A ./dirname/)" ]; then
  2.         echo "empty"
  3. else
  4.         echo "not empty"
  5. fi

連想配列/ハッシュ

  • v4.0で新しく追加された。v3系ではちょっとわかりにくい
  • bash v4.x
    1. #/bin/bash
    2.  
    3. # bash v4. "-A":連想配列を宣言 / "-a":数値配列
    4. unset ary
    5. declare -A ary
    6.  
    7. ary=(
    8. ["key1"]="value1"
    9. ["key2"]="value2"
    10. ["key3"]="value3"
    11. )
    12.  
    13. # 要素の追加
    14. ary+=(
    15. ["key4"]="value4"
    16. )
    17.  
    18. # 要素数
    19. echo num: ${#ary[@]} # 4
    20.  
    21. # valueだけ欲しい
    22. for i in "${ary[@]}"; do
    23. echo $i
    24. done
    25.  
    26. # key, valueも欲しい
    27. for i in "${!ary[@]}"; do
    28. echo $i, ${ary[$i]}
    29. done

ヒアドキュメント

まとめて標準入力、標準エラー、ファイル等に出せる

  • 「cat << EOS」のままだと、変数展開される。「cat << 'EOS'」にすると変数展開されない。
  • 標準出力
    1. cat << EOS
    2. SHELL: $SHELL
    3. EOS
  • 標準エラー
    1. cat 1>&2 << EOS
    2. SHELL: $SHELL
    3. EOS
  • ファイルに出力。「>」で上書き、「>>」で追記
    1. cat > file.tmp << 'EOS'
    2. hoge
    3. moke
    4. EOS

テンポラリファイル名

  • $$はPIDなので一意。同時実行される可能性のある場合は便利
    1. TMP_FILE=$$.tmp
  • mktempも使える
    1. mktemp
    2. /tmp/tmp.0FEIQ9u8mo
    3.  
    4. mktemp example.XXXXXX
    5. example.Os0Nne

bashで秒、ミリ秒の取得

  • 秒:$SECONDS
  • ミリ秒:printf '%.3f' `date '+%s.%N'`
  1. #!/bin/bash
  2.  
  3. loop(){
  4.     for i in {1..10}; do
  5.         sleep 1
  6.     done
  7. }
  8.  
  9. START_SEC=$SECONDS
  10. START_MSEC=`printf '%.3f' \`date '+%s.%N'\``
  11.  
  12. loop
  13.  
  14. END_SEC=$SECONDS
  15. END_MSEC=`printf '%.3f' \`date '+%s.%N'\``
  16.  
  17. echo $(($END_SEC - $START_SEC))" s."
  18. echo `echo "scale=3;($END_MSEC - $START_MSEC)" | bc`" s."

bash最短関数

bashで関数名だけ用意したい事があるが、空の関数は書くとエラーになる。

  • bashにコロン":"が何もしない組み込み関数として用意されている
    1. dummy(){
    2.  :
    3. }
    4.  
    5. dummy

タイムゾーンを日本に変更

  • UTC設定サーバで一時的に日本時間で表示したい
    1. export TZ=JST-9
    2. date '+%Y/%m/%d %H:%M:%S %Z'

パイプの先は別シェルなので変数取得やexitが無効

  • シェルスクリプト内でパイプは別シェルが起動するためループ内で以下は無効になる
    • 変数が取得できない
    • exit等が無効
    • bugあり。$strに値が入らない。ループ内でexitしても終了しない
      1. #!/bin/bash
      2.  
      3. str=""
      4. cat /etc/fstab | while read line; do
      5.   str="$line"; break;
      6.   # exit;
      7. done
      8.  
      9. echo "str : $str"
    • リダイレクトに修正
      1. #!/bin/bash
      2.  
      3. str=""
      4. while read line; do
      5.   str="$line";break;
      6. done < /etc/fstab
      7.  
      8. echo "str : $str"

文字色の変更

  • CentOS: /etc/rc.d/init.d/functions にもchkconfig等で使われている色が乗っている
    1. echo_success()
    2. echo_failure()
    3. echo_passed()
    4. echo_warning()

IPアドレスの取得

  • eth0の取得
    1. NIC0=`/sbin/ifconfig | grep eth0 | awk -- '(NR == 1){print $1}'`
  • CentOS6.x
    1. /sbin/ifconfig eth0 | grep 'HWaddr' | awk -- '{print $5}'
  • CentOS5.x
    1. /sbin/ifconfig eth0 | grep 'inet addr:' | awk -- '{print $2}' | awk -F':' -- '{print $2}'

関数

  • returnに指定できる値は、1〜255 の正の整数のみ。終了ステータスとなる
    1. cat >> example1.sh << 'EOS'
    2. func() {
    3.   return 1
    4. }
    5.  
    6. # 呼び出し
    7. func
    8. EOS
    9.  
    10. # 終了ステータス確認(1が返る)
    11. bash example1.sh
    12. echo $?
  • 戻り値はないので、var=`func`で受け取る
    1. cat >> example2.sh << 'EOS'
    2. func() {
    3.   echo "result-data"
    4. }
    5.  
    6. result=`func`
    7. echo ${result} # result-data
    8. EOS
  • 引数
    1. cat >> example3.sh << 'EOS'
    2. #!/bin/bash
    3.  
    4. func() {
    5.   echo "\$0=$0" # $0=example3.sh
    6.   echo "\$#=$#" # $#=3
    7.   echo "\$@=$@" # $@=001 002 003
    8.   echo "\$*=$*" # $*=001 002 003
    9.   echo "\$1=$1" # $1=001
    10.   echo "\$2=$2" # $2=002
    11.   echo "\$3=$3" # $3=003
    12. }
    13.  
    14. func 001 002 003
    15. EOS
  • local変数。関数内でだけ定義できる
    1. cat >> example4.sh << 'EOS'
    2. #!/bin/bash
    3.  
    4. GLOBAL=global
    5.  
    6. func() {
    7.     local GLOBAL=local
    8.     echo $GLOBAL # local
    9. }
    10.  
    11. func
    12. echo $GLOBAL # global
    13. EOS

sudo実行したユーザ、グループ取得

  1. HOME_USER=${SUDO_USER:-"$USER"}
  2. HOME_GROUP=`id $HOME_USER -g -n`

ファイル名の一括変更

  • *us.txtを*jp.txtに変更
    1. for F in *us.txt;do mv $F ${F/us./jp.};done

JSONパーサー


計算

  • 出力したい式
    1. $min + $RANDOM % ( $max - $min )
  • expr
    1. expr 1 + $RANDOM % \( 10 - 1 \)
  • bash。若干早いが、互換性は無くなる
    1. echo $(( 1 + $RANDOM % ( 10 - 1 ) ))

ランダムな数値と文字列

  • $RANDOM: 0-32767 の値を返す
  • /dev/urandom から任意の長さの乱数文字列を取得
    • LANG=CはMacOSXで「tr: Illegal byte sequence」が出るための修正。catを使うとansibleからキックした場合に、プロセスが残る問題のためheadに修正。
      1. echo $(head -c 1k /dev/urandom | LANG=C tr -dc '[:alnum:]' | head -c 32)
      2. U2egJwX9m1WQyHMtaJpkrTuUdw2v71Bn
  • /dev/urandom を使って、指定範囲の乱数を取り出すスクリプト。/dev/urandom は非常に長いため遅い。mkpasswdも遅い
    1. #/bin/bash
    2.  
    3. # rand
    4. # @param int min : default 0
    5. # @param int max : default 32767,  max 32767
    6. # @return int    :
    7. rand()
    8. {
    9. local min=0
    10. local max=32767
    11. if [ $1 ]; then
    12. min=$1
    13. fi
    14. if [ $2 ]; then
    15. max=$2
    16. fi
    17. echo `echo $(( $min + $RANDOM % ( $max - $min ) ))`
    18. }
    19.  
    20. # rand_str
    21. # @param int len : default 8
    22. # @return int    :
    23. rand_str()
    24. {
    25. local len=8
    26. if [ $1 ]; then
    27. len=$1
    28. fi
    29. echo `</dev/urandom tr -dc A-Za-z0-9 | head -c $len`
    30. }
    31.  
    32. # rand_str_fast
    33. # @param int len : default 8, max 1000
    34. # @return int    :
    35. RANDSTRING=`</dev/urandom tr -dc A-Za-z0-9 | head -c 1000` # 1000文字まで作成しておく
    36. rand_str_fast()
    37. {
    38. local len=8
    39. if [ $1 ]; then
    40. len=$1
    41. fi
    42. local max=`echo $(( 1000 - $len ))`
    43. local start=`rand 1 $max`
    44. local end=`echo $(( $start + $len -1 ))`
    45. echo `echo -n "$RANDSTRING" | cut -c$start-$end`
    46. }
    47.  
    48. for i in {1..10}; do
    49. echo `rand 1 10`,`rand_str_fast 8`
    50. done
  • 実行結果
    1. 2,8NOLYUIK
    2. 3,j7y74euW
    3. 3,5XG20fJ1
    4. 3,Cn5NGZwM
    5. 4,MKwbgRJO
    6. 7,BpeOCLZM
    7. 9,O9zbPunH
    8. 8,0j3cSLWd
    9. 6,yoExCkEX
    10. 8,OHEyoqeJ

指定回数ループ

  • bash
    1. for i in {1..10}; do
    2. echo $i
    3. done
  • 変数で指定したい場合
    1. LOOP=10
    2. for i in `eval echo {1..$LOOP}`; do
    3. echo $i
    4. done
  • seqで桁合わせ
    1. for i in $(seq -w 001 003); do
    2. echo $i
    3. done
    4.  
    5. 001
    6. 002
    7. 003
  • 変数中の「$i」を置換
    1. for i in $(seq -w 001 003); do \
    2.   host=$(echo 'web$i' | perl -pe "s/\\$\i/$i/" ); \
    3.   echo $host; \
    4. done
    5.  
    6. web001
    7. web002
    8. web003

OSの判別

  • http://kanon.ultimania.org/hg/KanonConductor/file/803f7b41dfdf/kanon-setup
    1. #!/bin/bash
    2. OS=''
    3. APACHE_USER=''
    4.  
    5. get_os() {
    6. local os=''
    7. if [ -f /etc/debian_version ]; then
    8. os='debian'
    9. elif [ -f /etc/vine-release ]; then
    10. os='vine'
    11. elif [ -f /etc/turbolinux-release ]; then
    12. os='turbo'
    13. elif [ -f /etc/redhat-release ]; then
    14. local CHK=`egrep "CentOS .*release [5-6]|Red Hat Enterprise Linux .* [5-6]" /etc/redhat-release`
    15. if [ "$CHK" != '' ]; then
    16. os='rhel'
    17. fi
    18. else
    19. local os=''
    20. fi
    21.  
    22. echo "$os"
    23. }
    24.  
    25. get_apache_user() {
    26. local os=$1
    27. local user=''
    28.  
    29. if [ "$os" == "debian" ]; then
    30. user="www-data"
    31. elif [ "$os" == "rhel" ]; then
    32. user="apache"
    33. else
    34. user=''
    35. fi
    36.  
    37. echo "$user"
    38. }
    39.  
    40. OS=`get_os`
    41. APACHE_USER=`get_apache_user "$OS"`
    42. echo "OS: $OS"
    43. echo "APACHE_USER: $APACHE_USER"

複数行コメント

  • 「:<<'#文字列' 〜 文字列」までが複数行コメント
    1. #!/bin/bash
    2.  
    3. :<<'#comment'
    4. echo hoge1
    5. echo hoge2
    6. #comment
    7.  
    8. echo hoge3
  • 実行結果
    1. hoge3

バージョン番号の比較

  • Apacheの場合
    1. v=`httpd -v | perl -ane 'if( @F[2]=~/Apache\/(\d+\.\d+)/ ){ print $1; }'`
    2. r=`echo "$v >= 2.2" | bc`
    3. if [ $r -eq 1 ]; then
    4. echo "$v >= 2.2"
    5. else
    6. echo "$v < 2.2"
    7. fi

正規表現

  • `[[`、比較演算子`=~` を使うと、右辺は拡張正規表現とみなされる。
  • 右辺はシングル/ダブルコーテーションは動作しない
  • 右辺にスペースが入るとうまく動作しないので、変数に入れる
  • 左辺はダブルコーテーションで囲ってもOK
  • マッチ部分は BASH_REMATCH という配列に入る。[0]が全体。[1]〜が()で括った部分
  • bash 4.1.2
    1. mysql_ver=$(mysql -V) # mysql Ver 14.14 Distrib 5.5.30, for Linux (x86_64) using readline 5.1
    2. pattern='Distrib ([[:digit:]]\.[[:digit:]])'
    3. if [[ $mysql_ver =~ $pattern ]]; then
    4.   echo ${BASH_REMATCH[0]} # Distrib 5.5
    5.   echo ${BASH_REMATCH[1]} # 5.5
    6. fi

デバッグ

  • デバッグ時のみ処理
    1. #!/bin/bash
    2. DEBUG=1
    3. [ $DEBUG == 0 ] || { echo "debug";exit; }

エラーの際に処理を止める/未定義変数を参照した時にエラーとする

  • -e: エラーの際に処理を停止
    1. set -e
    2. touch /tmp/foo/bar.txt
    3. # /tmp/foo ディレクトリが無いので停止
    4. echo success
  • -u: 未定義の変数を参照した時にエラー
    1. set -eu
    2. echo $tmp_dir/bar.txt
    3. # $tmp_dir が未定義なため停止
    4. echo success

文字列操作

  • 先頭文字の取得
    1. VAR1=2012-08-01
    2.  
    3. # 最後から最長一致
    4. VAR2=${VAR1%%-*} # 2012
    5.  
    6. # 最後から最短一致
    7. VAR2=${VAR1%-*} # 2012-08
    8.  
    9. # 最後の一文字を削除
    10. VAR2=${VAR1/%?/} # 2012-08-0
  • 末尾文字の取得
    1. VAR1=example.tar.gz
    2.  
    3. # 左から最長一致
    4. VAR2=${VAR1##*.} # gz
    5.  
    6. # 左から最短一致
    7. VAR2=${VAR1#*.} # tar.gz

bash変数

  • local変数。関数内のみ有効なスコープ
    1. test() {
    2.     local var1;
    3. }
  • 定数
    1. readonly VAR1=test1
    2. VAR2=test2
    3.  
    4. VAR1=hoge1 # エラー
    5. VAR2=hoge2 # エラーにならない
  • グルーピング。echoでログファイルに書く場合などまとめる事ができる
    1. {
    2.   echo "hoge"
    3.   echo "fuga"
    4.   echo "foo"
    5.   echo "bar"
    6. } >>logfile.log
    7. # 最後を 1>&2 にすれば標準エラーにも吐ける

bash特殊変数

位置パラメータ
$0      シェルスクリプト名を表示
$1〜$9  シェルスクリプトのオプションを表示(数値は位置を現す。10以降は${10}、${11})

引数(オプション)関連
$#      引数の数を表示
$@      $0以外の全ての引数を表示("$@"にした場合は "$1" "$2" "…" のように展開)
$*      $0以外の全ての引数を表示("$*"にした場合は "$1 $2 ..." のように展開)
$-      シェルを起動する際に指定されていたオプションを表示

PID(プロセスID)関連
$?      直前のコマンドの終了ステータスを表示
        0   : 正常
        !0  : 失敗
        2   : 誤った使用
        126 : 実行できない
        127 : 存在しないコマンドの実行
$$      現在のシェルのプロセスIDを表示
$!      最期にバックグラウンドで実行されたコマンドのプロセスIDを表示
$-      現在のオプションフラグ

その他、シェルスクリプトで使いそうなもの
IFS     フィールドの区切り文字
PWD     カレントディレクトリを表示
PPID    親プロセスのプロセスIDを表示
HOME    ホームディレクトリを表示
$SECONDS 秒
$RANDOM  ランダム:0-32767
$LINENO 行番号
${PIPESTATUS[@]} パイプで連結された各コマンドの終了ステータスの配列

区切り文字変更 IFS変数

  • デフォルト
    1. IFS="$'\n'$'\t' "
  • カンマ区切り
    1. #!/bin/bash
    2. IFS=','
    3. str=a,b,c
    4. for i in $str; do
    5.   echo $i
    6. done
  • 改行
    1. IFS='
    2. '
    3.  
    4. または
    5.  
    6. IFS=$'\n'

変数にデフォルト値をセット

  1. NAME=$1
  2. NAME2=${2:-"DEFAULT"}
  3.  
  4. # デフォルトの値を表示
  5. echo ${NAME:-"DEFAULT"}
  6. echo "NAME: $NAME"
  7.  
  8. # 空文字列、未設定はエラー
  9. echo ${NAME:?"Error"}
  10.  
  11. # 空文字、未設定は空文字。それ以外は、wordに置換
  12. echo ${NAME:+"word"}
  13.  
  14. # デフォルトの値を代入
  15. var=${NAME:="Guest"}
  16. echo "NAME: $NAME"

高度


tputでメニュー

  • 10 Tools To Add Some Spice To Your UNIX Shell Scripts
    1. #!/bin/bash
    2.  
    3. # clear the screen
    4. tput clear
    5.  
    6. # Move cursor to screen location X,Y (top left is 0,0)
    7. tput cup 3 15
    8.  
    9. # Set a foreground colour using ANSI escape
    10. tput setaf 3
    11. echo "XYX Corp LTD."
    12. tput sgr0
    13.  
    14. tput cup 5 17
    15. # Set reverse video mode
    16. tput rev
    17. echo "M A I N - M E N U"
    18. tput sgr0
    19.  
    20. tput cup 7 15
    21. echo "1. User Management"
    22.  
    23. tput cup 8 15
    24. echo "2. Service Management"
    25.  
    26. tput cup 9 15
    27. echo "3. Process Management"
    28.  
    29. tput cup 10 15
    30. echo "4. Backup"
    31.  
    32. # Set bold mode
    33. tput bold
    34. tput cup 12 15
    35. read -p "Enter your choice [1-4] " choice
    36.  
    37. tput clear
    38. tput sgr0
    39. tput rc

getopt,getopts

  • getopt : 外部コマンド。ロングオプション(--)も対応。後ろにオプションがあっても大丈夫。MacOSX非対応
  • getopts : bashの内部関数。getoptよりすっきり書ける。オプションが最後にあるような構文は非対応。
  • $OPTIND : 次に処理するオプションのインデックスが入っている。
    1. # オプション部分を切り捨てる
    2. shift `expr $OPTIND - 1`
  • getopts版。引数を処理する - UNIX & Linux コマンド・シェルスクリプト リファレンス
    1. #!/bin/bash
    2.  
    3. export LANG=C
    4. CMDNAME=`basename $0`
    5.  
    6. usage_exit() {
    7.     echo "Usage: $CMDNAME [-a] [-b VALUE] [-c VALUE]" 1>&2
    8.     exit 1
    9. }
    10.  
    11. echo "[DEBUG GLOBAL] \$#   : $#"
    12. echo "[DEBUG GLOBAL] \$@   : $@"
    13. echo_debug() {
    14.     echo "[DEBUG LOCAL] \$FLG_A : $FLG_A"
    15.     echo "[DEBUG LOCAL] \$FLG_B : $FLG_B / $VALUE_B"
    16.     echo "[DEBUG LOCAL] \$FLG_C : $FLG_C / $VALUE_C"
    17. }
    18.  
    19. while getopts "a,b:,c:" OPT
    20. do
    21.   case $OPT in
    22.     "a" ) 
    23.         FLG_A="TRUE"
    24.         ;;
    25.     "b" )
    26.         FLG_B="TRUE"
    27.         VALUE_B="$OPTARG"
    28.         ;;
    29.     "c" )
    30.         FLG_C="TRUE"
    31.         VALUE_C="$OPTARG"
    32.         ;;
    33.     * )
    34.         usage_exit
    35.         ;;
    36.   esac
    37. done
    38.  
    39. # オプション部分を切り捨てる
    40. shift `expr $OPTIND - 1`
    41.  
    42. echo "[DEBUG GLOBAL] \$1   : $1"
    43. echo "[DEBUG GLOBAL] \$2   : $2"
    44. echo_debug
  • getopt版。シェルスクリプトを書くときに地味に便利なgetopt - Kirikaの非人間的生活
    1. #!/bin/bash
    2.  
    3. export LANG=C
    4. CMDNAME=`basename $0`
    5.  
    6. usage_exit() {
    7.     echo >&2 "Usage: $CMDNAME [-h|--hostname hostname] [-u|--hostuser username] [-s] [--longonly]"
    8.     exit 1
    9. }
    10.  
    11. # check command args.
    12. if [ $# -lt 1 ]; then
    13.     usage_exit
    14. fi
    15.  
    16. echo "[DEBUG GLOBAL] \$#   : $#"
    17. echo "[DEBUG GLOBAL] \$@   : $@"
    18. echo_debug() {
    19.     echo "[DEBUG LOCAL] hostname  : $hostname"
    20.     echo "[DEBUG LOCAL] hostuser  : $hostuser"
    21.     echo "[DEBUG LOCAL] shortonly : $shortonly"
    22.     echo "[DEBUG LOCAL] longonly  : $longonly"
    23. }
    24.  
    25. OPT=`getopt -q -o "h:,u:,s" -l "hostname:,hostuser:,longonly" -- "$@"`
    26. if [ $? != 0 ]; then
    27.     usage_exit
    28. fi
    29.  
    30. eval set -- "$OPT"
    31. while [ -n "$1" ]; do
    32.   case $1 in
    33.     -h|--hostname) hostname=$2; shift 2;;
    34.     -u|--hostuser) hostuser=$2; shift 2;;
    35.     -s) shortonly=1; shift 1;;
    36.     --longonly) longonly=1; shift 1;;
    37.     --) shift; break;;
    38.     *) echo "Unknown option($1) used."; exit 1;;
    39.   esac
    40. done
    41.  
    42. echo "[DEBUG GLOBAL] \$1   : $1"
    43. echo "[DEBUG GLOBAL] \$2   : $2"
    44. echo_debug

rootやsudoの判別

  • rootユーザもしくは、sudoの時は$EUID, $UIDが0になる。$SUDO_USERも定義される
    if [ $EUID -eq 0 ]; then
        echo "root"
    fi
    

全引数をダブルクォート付きで渡す

  • ダブルクォートで囲んだ場合の挙動が違う
    • "$*"
      "$1 $2 ..."
    • "$@"
      "$1" "$2" ...
      • よって、別コマンドへスペースも含んだコマンドを渡すには
        #/bin/bash
        hoge.php "$@"

配列変数を使う

  • bash2.0以降ならば、変数に配列が使用できるようです
  • 配列の場合必ず${...}として{}で括る
  • 使用例
    1. #!/bin/bash
    2. a=(
    3. "aaa"
    4. "bbb"
    5. "c c c"
    6. )
    7. #a[0]="aaa"
    8. #a[1]="bbb"
    9. #a[2]="c c c"
    10. for var in "${a[@]}"; do
    11.   echo "${var}"
    12. done
  • 出力結果
    1. aaa
    2. bbb
    3. c c c
  • 関数として値を渡す場合
    1. main(){
    2.     for arg in "$@"; do
    3.         echo $arg
    4.     done
    5. }
    6.  
    7. main "$@"
  • 配列のコピー
    1. ARRAY=( "a" "b" "c" )
    2. ARRAY2=${ARRAY[*]}
    3. echo ${ARRAY2[*]}
  • 要素を追加。""で括らないと"123 abc"のようなスペースを含む文字の場合の挙動が変わる
    1. ARRAY1=()
    2. str1="a"
    3. str2="b"
    4. ARRAY1+=("$str1" "$str2")
  • 要素数(添え字が連続している場合のみ。不連続の場合は意図した結果にならない)
    1. ${#ARRAY[*]}

シェルスクリプトでカレントディレクトリの取得

  • bash/zsh, source対応
    1. CUR_DIR=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
  • bashのみ。source非対応
    1. CUR_DIR=$(cd $(dirname $0);pwd)

シェルスクリプトの実行ログを記録

  • tee?
    export PATH=$PATH:/usr/sbin:/sbin
    sh install.sh | tee install.log

搭載メモリをMBで取得

  1. MEM_TOTAL=`perl -e '$m=readpipe("free -m");$m=~/Mem:\s+(\d+)/i;print int($1)'`
  2. MEM_FREE=`perl -e '$m=readpipe("free -m");$m=~/cache:\s+(\d+)\s+(\d+)/i;print int($2)'`

日付形式をチェック

  • YYYY-MM-DD形式以外はエラー
    1. echo "2012-01-01" | egrep '^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$' > /dev/null || echo $?

ホスト名にアンダーバーが含まれていたらエラー

  1.  match=`echo "$1" | egrep '^([0-9a-z\-]+)$' -`
  2.  if [ $? != 0  ]; then
  3.          echo "Error : Invalid character. allow [0-9, a-z, -]"
  4.          exit
  5.  fi

添付ファイル: filemy-tmpwatch 353件 [詳細] filewaiting-for-parallel-tasks.zip 679件 [詳細] fileselect-yes-no.sh 461件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-02-15 (木) 17:37:21 (70d)