早崎トップ 研究(気候気象) 研究(大気汚染) データリスト Linux Tips Mac Tips

シェルスクリプトのメモ (hysk)

シェルスクリプトの知識

ものぐさな人ほど楽をしようとする. 楽をするためには,どんな苦労も厭わない・かなり長時間を スクリプト作成にささげてしまう... 本末転倒ですね. そんなあなたは,研究者として生きていくなら少し注意が必要です.

あぁ,私の事ですね.はい. スクリプト作成・プログラミングは適当に切り上げて, さっさと研究しましょう » 同類の方々

sh (というか bash)のお話がメインです. 詳しい情報を知りたければ,man bash にて自分で調べてください.

参照: ちょっと便利なコマンド; スクリプト雛型 (hysk)

説明よりもサンプルが見たい人: 自分専用コマンド・関数

文字列

  メタ文字

  "  $@&'()^|[]{};*?<>`\ スペース

メタ文字を表記する場合は,バックスラッシュを付ける.

 エスケープシーケンス

エスケープ文字 意味
\a ベルを鳴らす
\b バックスペース
\e エスケープ文字
\f フォームフィールド文字
\n 改行文字 (line feed)
\r 復帰 (carriage return)
\t 水平タブ
\v 垂直タブ
\数字 指定したASCII文字.数字部分は8進数3桁
\x数字 指定したASCII文字.数字部分は16進数3桁
  • echo 文でエスケープ文字を使う場合は, -e を付ける
 echo -e "hoge\t  = hogehoge" 

 文字列操作

  • 文字列の長さを調べる

${#変数名}

 用例:
   echo ${#hoge}
   シェル変数 hoge の文字列長を表示
  • 文字列入力

read を使う.

 ** sample.sh **
 #!/bin/sh
 echo -n "What is your name?"
 read ans
 echo "Hello, $ans"

これを実行すると,キーボードからの入力待機になる.

 read hoge1 hoge2 

として,スペース区切りでキーボード入力すれば,2個の変数に代入される.

ファイルからの入力例は以下の通り:

 ** sample02.sh *******************************************************
 BUFIFS=$IFS
 IFS=
 
 exec 3< 入力ファイル名
 while read FL 0<&3
 do
   処理
 done
 exec 3<&-
 
 IFS=$BUFIFS
 **********************************************************************

上記を実行すると,入力ファイルの内容を1行ずつ読み,変数FLに代入していく. 読み込んだファイル内容を利用する際は,$FL と指定する.

変数

 シェル変数と環境変数

シェル変数と環境変数の違い

自分で調べろや,そんくらい.

予約済み環境変数 Reserved environmental variables

以下はシステムで予約済みの環境変数.変更するべからず.

 PATH
 USER
 UID
 HOME
 HOSTNAME
 SHELL
 PWD
 OLDPWD
 LANG
 TERM
 PPID

  変数定義

基本ルール

  • ${変数名=値}で代入と変数の参照を同時にできる.

ただし,変数が未定義である必要がある.定義済みの場合,代入済みの値が使われる.

  • ${変数名:=値}とすると,定義済みの場合であっても

代入済みの値が NULL の場合に限り,指定した新たな値が代入される.

  • 変数を絶対に変えたくないときには,
 readonly 変数名 

と宣言すれば良い.解除は

 unset 変数名 

変数の一部を切り出す

変数の一部分を切り出す,つまり部分文字列の抜き出しの例

${hoge:n1:n2}
とする.hoge が変数名,n1 は開始文字位置(最初の文字がゼロ),n2 は抜き出す文字数(開始文字含む).

例:日付(YYYYMMDD 形式)の変数から,年,月,日を分割
yyyymmdd=20120312   # 2012年3月12日
yyyy=${yyyymmdd:0:4}  # "2012" を切り出し
mm=${yyyymmdd:4:2}    # "03" を切り出し
dd=${yyyymmdd:6:2}    # "12" を切り出し

変数の一部を置換 (bash内部コマンド)

変数の一部分を置換する事もできる. bash の内部コマンドを使うので,bash を使わないとできない.

${hoge/置換前文字列/置換後文字列}
または
${hoge//置換前文字列/置換後文字列}
とする.
前者は最初にマッチした文字列だけを置換,後者はマッチしたもの全てを置換.

私がよく使うのは,GMTによる作図で,EPS, PDF, PNG形式の画像ファイルの置き場所指定時.

eps_path=$HOME/dv1/air_pollution/eps/timeseries
pdf_path=${eps_path//\/eps\//\/pdf\/}
png_path=${eps_path//\/eps\//\/png\/}

スラッシュをバックスラッシュでエスケープしてるので読み取りにくい. 同じことを sed を使っても可能 (See grep, sed, awk (hysk) > 入出力パスの一部のみ変更). 人それぞれの好み次第.

変数を配列で宣言する場合

#!/bin/bash

declare -a foo bar

#hoge=(0 1 2 3 4)
foo=(foo0 foo1 foo2 foo3 foo4)
bar=(bar0 bar1 bar2 bar3 hage1)

for i in 0 1 2 3 4 ; do
  echo "$i ${foo[$i]}  ${bar[$i]}"
done  # i

シェル変数の文字列の長さを知る

${#変数名}
※例1: 最も単純な例
i=hogehoge
echo "Length of \$i = ${#i}"

※例2: (応用例) 時刻情報の設定,その文字列長を表示.
  文字列が長いと,目で見て数えるのが面倒なので.
  ここでは固定長文字列を例にしたので利点を実感しにくいが,
  スクリプト中での可変長のシェル変数を使う際に,特に有効と思われる.

(ISO 8601形式に準拠した,早崎が default で使う形式.25文字固定長)
i=`date +"%FT%T%:z"`
echo "Date/time = $i ; Length of \$i = ${#i}"

(少し話が脱線: 上記で設定した変数i を使って,日付・時刻情報を抜き出す例)
yyyy=${i:0:4}
mm=${i:5:2}
dd=${i:8:2}
hour=${i:11:2}
minute=${i:14:2}
second=${i:17:2}
tz_offset=${i:19:6}
echo "$yyyy $mm $dd $hour $minute $second (offset = $tz_offset)"

時刻表記 (ISO 8601形式)については, 時刻に関する雑記参照.

  特殊変数 (special variables)

return code の真 (true) と偽 (false)

基本は true = 0, false = 1

例題: ディレクトリ tmp, hoge が存在する(return code = 0) か 存在しない (return code = 1) かを確認

% \ls -l
total 4
drwxrwxr-x 2 user1 user1 4096 Jun 24 12:59 tmp
(ディレクトリ tmp だけが存在)
% test -d tmp
% echo $?
0
% test -d hoge
% echo $?
1

特殊変数リスト

$?
result code of last command
$$
process ID
$!
process ID of current job (一つ前に動作させたジョブ)
$-
command option of current working shell
$#
number of argument(s)
$*
全ての引数をスペースで区切りながら繋げて,そのまま引き渡す
$@
$* と同じ
"$*"
全ての引数を1つの引数として扱って引きわたす
"$@"
"..." などでくくられた引数を1つの引数として扱って引きわたす

条件分岐

  変数に関する条件分岐

test コマンドを使用. if 文と併用するときには,コードの読みやすさのために [ を使う事が多い. というか,自分の場合は [ で書くことが大部分で,test で表記することはほとんどない.

##### -z または -n : 変数がカラか否かのチェックに使える
### -z 文字列1 = 文字列1 の長さがゼロなら true を返す
### -n 文字列1 = 文字列1 の長さがゼロでないなら true を返す
unset hoge hoge2
hoge=HOGE
hoge2=HOGEHOGE
if [ -z $hoge ] ; then
  echo "variable hoge is NULL"
else
  echo "variable hoge have already used ; \$hoge = $hoge "
fi

  文字列の条件式

条件式 意味
文字列, -n 文字列 指定した文字列が1文字以上あれば true
-z 文字列 指定した文字列が0文字(何もない)状態なら true
文字列1 = 文字列2 文字列1と文字列2が同じなら true
文字列1 != 文字列2 文字列1と文字列2が異なれば true

  数値の条件式

指定できる数値は,正負の整数のみ.

条件式 意味
数値1 -eq 数値2 数値1が数値2と同じであれば true
数値1 -ne 数値2 数値1が数値2と等しくなければ(異なれば) true
数値1 -lt 数値2 数値1が数値2より小さければ true
数値1 -le 数値2 数値1が数値2以下であれば true
数値1 -gt 数値2 数値1が数値2より大きければ true
数値1 -ge 数値2 数値1が数値2以上であれば true

 ファイルに関する条件分岐

test コマンドによるファイル・ディレクトリの条件判別. 下記以外にもたくさんあるけれど, 私が頻繁に使うオプションは,せいぜい4つ( -d, -f, -h, -s). これ以外は,コンピュータ専門家やPC管理者くらいしか使わないような気がする.

条件式 意味
-G 指定ファイルが存在し,所属グループが実行ユーザのグループと同じなら true
-O 指定ファイルが存在し,ファイル所有者が実行ユーザと同じなら true
-e 指定ファイルが存在すれば true
-d 指定ディレクトリが存在すれば true
-f 指定ファイルが通常ファイルであれば true
-h ないし -L 指定ファイルがシンボリックリンクなら true
-r 指定ファイルが存在し,読み出し可能なら true
-s 指定ファイルが存在し,ファイルサイズが1以上なら true
-w 指定ファイルが存在し,書き込み可能なら true
-x 指定ファイルが存在し,実行可能なら true
faile1 -nt file2 file1 の修正時刻が file2 よりも新しければ true
faile1 -ot file2 file1 の修正時刻が file2 よりも古ければ true

自分専用コマンド・関数

 自分専用コマンド personal-use commands

自分がよく利用する手順,つまり一種のルーチンワーク的な作業は, 『自分専用コマンド』にしてしまえば,シェルプログラムを書くのも楽になる.

例えば私の場合,『ヘッダ行(先頭文字が "#" で始まる行)を削除する』だけのコマンド rm_header を用意し, それをパスの通ったディレクトリ($HOME/bin/ 以下)に置いている. ファイル実行権限を付与する事を忘れずに(chmod 755 rm_header).

#!/bin/sh
#
# Remove header line(s) which starts as sharp "#" symbol in a line.
#
#                     M. Hayasaki

target=$1

### remove header lines(s) and write to standard output!
grep -e "^[^#]" ${target}

 サンプルスクリプト・自作コマンド List of sample shell scripts

 自分専用関数 personal-use functions

自分専用コマンドを作っていくと,用途は似ているが微妙に挙動を変更したコマンドを 多数用意したいことがある. そういったコマンドを一つずつ用意するのは面倒だし,メンテナンスが面倒. そんな場合は,シェルスクリプトではなく,関数を定義する. 関数ならば,一つのファイルに複数記述できるので,ファイル数がやたらと増えることもない.

私の場合,$HOME/bin/function/ 以下にファイルを設置. 必要に応じて,シェルプログラムの冒頭部分で読み込む( source $HOME/bin/function/hogehoge ). 読み込まない限り関数は使えないので,要注意. ログイン時に自動読み込みさせるのも一つの手段. ただし,現時点ではシェルプログラムごとに読み込む関数ファイルを明示するようにしている (2012-10-31 時点).

私の「デフォルトシェルスクリプト」の構造は, スクリプト雛形 (hysk) # シェルスクリプト を参照.

  • chk_dir_file: ファイル・ディレクトリ操作についての関数. 私がシェルスクリプトを書くとき,ほぼ常時使用.下記に関数の一部を紹介する:
    • chk_dir: ディレクトリが存在しなければ作成する
    • chk_file: ファイルが存在していたら remove し,カラのファイルを作る
  • get_dateinfo: 時刻処理についての関数.date コマンドを多用. date コマンドの使いかたは, 日付・時刻処理(dateコマンド)時刻表記(ISO 8601形式)を参照. 下記に関数の一部を紹介する:
    • utc2jst & jst2utc: 日本標準時と世界標準時の変換.引数に与えるのは10桁数値(yyyymmddHH)
    • yyyymmdd2doy & doy2yyyymmdd: 年月日(8桁数値,20120415 なら2012年4月15日)から day of year (1月1日から起算した日数)に変換
    • return_total_month: 開始年月と終了年月(共に yyyymm の6桁数値)を引数に与え,月の数をカウント,シェル変数 total_month に収納
    • return_total_days: 開始年月日と終了年月日(共に yyyymmdd の8桁数値)を引数に与え,日数をカウント,シェル変数 total_day に収納
  • obj_anal_function: 客観解析データについての関数.下記に関数の一部を紹介する:
    • set_year: データ名を引数に与え,データの開始・終了年(シェル変数 s_yyyy, e_yyyy)を返す
    • set_var_lev: 要素名・高度(気圧面)を引数に与え,それを連結したシェル変数 var_lev を返す. ただし,2次元要素・地表面要素の場合は高度の情報を付与しない(例: 500-hPa ジオポテンシャル高度の場合,HGT0500, SLP の場合,lev は1013 と設定している ので,SLP そのまま.SLP1013 などとしない.

    (関連情報) 私が主に使用している客観解析気象データ(長期再解析; See also 再解析格子点気象データ情報)は, JRA25, JRA55, NCEP CFSR, ERA_Interim, である(初稿: 2013-05-07 時点; 上記リンク先は,早崎による私的メモページ).

更新履歴

  • テキストファイルでのメモからWikiに転載.
更新日 内容
2017-03-08 『変数の一部を置換』や『文字変数の文字列長を知る』などを追記.
2016-02-28 『変数の一部を置換』や『文字変数の文字列長を知る』などを追記.
2013-05-07 『自分専用コマンド・関数』の新規追加・リンク切れの修正.関連ページへのリンクを追加.
2012-11-02 『サンプルスクリプト』を追加.これまで,いろんな場所に散在していたサンプルファイル情報をまとめるため.
2012-10-31 『自分専用コマンド・関数』を追加.
2012-03-24 ファイルに関する条件式の記述を追記. 条件分岐の見出し構造を整理.
2011-06-09 更新リストを時系列逆順に変更. return code 部分の記述ミスを修正.test の -d オプションが入ってなかった
2008-07-03 特殊変数やメタ文字に関する記述を追記
2007-06-12 作成開始(記録にある日付では最古だが,もっと前から書いていたはず)