とらりもんHOME  Index  Search  Changes  Login

シェルスクリプト

  • 2015/04/15 if文,while文,for文と条件文の書き方およびシェル変数(特別な意味を持つものとawkでの使い方)について追記,筑波大 秋津朋子
  • 2015/05/07 シェル変数の一部取り出しについて追記,筑波大 秋津朋子

 目次

シェル変数の取り扱い1: exportコマンド

シェル変数は,

$ x=abc
$ echo $x
abc

のように扱える。しかし, こうやって設定したシェル変数は, 他のシェルスクリプトの中までは持ち越されない。例えば,

#!/bin/sh
echo $x

というシェルスクリプト(test1.shという名前)があったとして, それを実行しても,

$ x=abc
$ sh test1.sh

となって, abcとは表示されない。ところが,

$ export x

としてやると, 変数xは環境変数になる。環境変数の場合、変数はshellスクリプトの中でも参照可能である。

$ sh test1.sh
abc

というふうに, xの内容がtest1.shの実行中まで持ち越される。これがexportコマンドの機能である。

シェル変数の取り扱い2: sourceコマンド

上とちょっと似た話だが, シェルスクリプトの中でシェル変数を設定しても, シェルスクリプトの終了後までは持ち越されない。例えば,

#!/bin/sh
export y=pqr

というシェルスクリプト(test2.shという名前)があったとして, それを実行しても,

$ sh test2.sh
$ echo $y

となって, pqrとは表示されない。ところが,

$ source test2.sh

としてやると,

$ echo $y
pqr

というふうに, yの内容がtest2.shの実行後まで持ち越される。これがsourceコマンドの機能である。 ちなみに "source test2.sh" と ". test2.sh"は同じ動作をする。"."もコマンドである。

シェル変数の取り扱い3: evalコマンド (2013/06/12 追記)

シェルスクリプトの中で、シェル変数に格納された値を、シェル変数名として、シェル変数を定義したい場合が有る。 例えば、

a = ???

のようなケースを考える。aに格納される値はわからないが、 aに格納した???を変数名とした変数を定義したいとする。 つまり、

??? = OK

という風に定義したい。???は、スクリプトを実行した時点では定義されているが、実行前には bかもしれないし、cかもしれない。

ここで、evalというコマンドを使う。

eval $a=OK

とする。すると、aに格納された値をシェル変数名としたシェル変数が定義出来る。

以下に具体的な例を記載する。

$ a=b
$ eval $a=OK
$ echo $a
b
$ echo $b
OK

ここで注意して置きたいのは

$ eval $a=OK
$ a=b
$ echo $b

のように、evalを実行してから、a=bと定義しても、bは定義されない。

特別な意味を持つシェル変数(2015/4/15 秋津追記)

  • $$: シェル自身のPID(プロセスID)
  • $!: シェルが最後に実行したバックグラウンドプロセスのPID
  • $?: 最後に実行したコマンドの終了コード(戻り値)
  • $-: setコマンドを使って設定したフラグの一覧
  • $*: 全引数リスト。
  • $@: 全引数リスト。
  • $#: シェルに与えられた引数の個数
  • $0: シェル自身のファイル名
  • $1〜$n: シェルに与えられた引数の値。$1は第1引数、$2は第2引数…となる。

シェル変数の一部を取り出す(2015/5/7 秋津)

  • ${変数#パターン} 先頭から一致した部分を取り除く
  • ${変数%パターン} 末尾から一致した部分を取り除く

以下に具体的な例を記載する。

  • ファイル名の拡張子を取り除く
file=aaaa.txt
output_file2=${file%.txt}.eps

シェルスクリプトをデバッグモードで入らせる (-xオプション!)

$ bash -x test.sh

シェルスクリプトに標準入力を与える ... readコマンド

#!/bin/sh
# test.sh
while read i ; do
    echo $i
done
$ echo abc | test.sh

シェルスクリプトでのif文の書き方(2015/4/15 秋津)

基本構造

if [ 条件1 ]
then
 処理1
elif [ 条件2 ]
then
 処理2
else
 処理3
fi

[ ]の前後には必ずスペースを入れる。

シェルスクリプトでwhile文の書き方(2015/4/15 秋津)

基本構造

while [ 条件 ]
do
  処理
done
例:
a=100; while [ $a -lt 366 ]; do
mkdir dc_2015_${a}
a=`expr $a + 1`; done

シェルスクリプトでfor文の書き方(2015/4/15 秋津)

基本構造

for 変数 in 引数1 引数2 …
do
  処理
done

シェルスクリプトでの条件の書き方(文字列)(2015/4/15 秋津)

  • [ 文字列1 = 文字列2 ]: 2つの文字列が等しい
  • [ 文字列1 != 文字列2 ]: 2つの文字列が等しくない

シェルスクリプトでの条件の書き方(数値)(2015/4/15 秋津)

  • [ 数値1 -eq 数値2 ]: 数値1と数値2が等しい(=)
  • [ 数値1 -ne 数値2 ]: 数値1と数値2が等しくない(!=)
  • [ 数値1 -gt 数値2 ]: 数値1が数値2より大きい(>)
  • [ 数値1 -lt 数値2 ]: 数値1が数値2より小さい(<)
  • [ 数値1 -ge 数値2 ]: 数値1が数値2より大きいか等しい(>=)
  • [ 数値1 -le 数値2 ]: 数値1が数値2より小さいか等しい(<=)

複数の条件の書き方(2015/4/15 秋津)

  • 「and」
[ 条件式その1 -a 条件式その2 ]
  • 「or」
[ 条件式その1 -o 条件式その2 ]
  • 「ではなく」
[ ! 条件式 ]

if文のテクニック

#!/bin/sh
if ! [ -e dir1_name/dir2_name ] ; then mkdir -p dir1_name/dir2_name ; fi

ifのあとの"!"は、条件を逆にする。"-f" は、ファイルが存在する場合 "真"、存在しない場合 "偽" とする。 mkdir の "-p" は、親ディレクトリが存在しない場合、親ディレクトリもつくる。

シェルスクリプトで数値の計算(2015/4/15 秋津)

「expr」コマンドを使用する。 例)a=`expr 10 + 3`

  • a + b: aとbの和
  • a - b: aとbの差
  • a \* b: aとbの積(*はワイルドカードの意味があるので\で打ち消す)
  • a / b: aとbの商
  • a % b: aとbの剰余

演算子の前後には必ずスペースを入れる。

シェル変数をawkの中でも使う方法(2015/4/15 秋津)

  • シェル変数をシングルクオテーションで囲むとawkの中でもそのまま使える。
例(この例だとシェル変数をわざわざ読み込まず,awkで変数を定義したほうが早いが,シェルの中の他の部分でもそのシェル変数を使う場合に有用。)
#!/bin/sh
YEAR=2015
awk 'BEGIN{FS=",";OFS=","}$1=='${YEAR}' && $2==1 && $4>=5 && $4<=18{min=$5; hour=$4;time=hour+(min/60)+(20.48/60);DOY=$3;print}'>data1

"&&" と "||"

shellスクリプトを書いていると、

command Aが成功したときは、command Bを実行したい。 もしくは、command Aが失敗したときだけ、command Bを実行したいときがある。

愚直に書くと

command A
if [ "$?" == "0"  ]
then
command B
fi

もしくは

command A
if [ "$?" != "0" ]
then
command B
fi

になる。 これを"&&"と"||"を使うと、もっと簡単に書ける。

command A && command B  ※command Aが成功したときは、command Bを実行する。
command A || command B  ※command Aが失敗したときは、command Bを実行する。

Tips

テキストデータの行と列をいれかえる

touch out.txt  #out.txtを作成
col=`cat inp.txt | awk '{print NF}'` #変数colに列の数をいれる
a=1 
while [ $a -lt $col ] #列の数だけループ
do
  cat inp.txt | awk '{printf "%f ", $'$a'} END{printf "\n"} >> out.txt
  a=`expr $a + 1`
done

いちばん最後の引数だけ別の処理

for args in "$@"; do
       last_args=${args}
done

ファイルの作成日を取得

date '+%Y_%m%d_%H%M' -r filename

時刻を、年月日からDOY(1月1日からの日数)に変換

inp=20080101
year=`echo $inp | cut -b 1-4`
month=`echo $inp | cut -b 5-6`
day=`echo $inp  | cut -b 7-8`
doy=`date --date=$year/$month/$day +%j`
echo $doy

時刻を、DOYから年月日に変換 (2013/02/17奈佐原)

$ y=2012
$ d=123
$ date -d "$y/01/01 1day ago ${d}days"
2000年  5月  2日 火曜日 00:00:00 JST
$ date -d "$y/01/01 1day ago ${d}days" +%Y/%m/%d
2000/05/02

コメント: 上のdateコマンドの中で, 1day agoというのが味噌。その年の元旦($y/01/01)から1日前は, 前年の大晦日。そこからDOYだけ日数を数えれば, 目当ての日付にたどり着く, という発想。

中間ファイルのファイル名にはプロセスIDをつけよう

×: dummy.txt
○: dummy.$$.txt

同じスクリプトを(違う対象に対して)同時にいくつかのジョブとして走らせるとき、中間ファイルの名前が同じだと、データの上書きなどの混乱が生じる。$$は、プロセスIDをあらわすシェル変数。

bashの便利コマンド類 (2016/04/01水落)

$ seq [整数1] [整数2] [整数3] #[整数1]〜[整数3]までの連続値([整数2]ステップ)を返す
$ cat hoge.txt | sort | uniq #ファイルhoge.txtの重複行を削除して返す
$ join -j 1 hoge.txt fuga.txt #hoge.txtとfuga.txtの1列目を比較し、一致した行を結合して返す
$ cat hoge.txt | head -n [整数1] #hoge.txtの[整数1]行目までを返す
$ cat hoge.txt | tail -n +[整数1] #hoge.txtの[整数1]行目以降を返す
$ SECONDS=0;bash hoge.sh;echo ${SECONDS}; #hoge.shの処理時間を計測
$ cpulimit -l 400 -p [PID] #プロセス[PID]のCPU使用率をコア4個分に制限
$ printf "%02d" [数値1] #[数値1]を形式指定して出力

ファイルの結合(2016/06/09 秋津)

join: 異なるファイルで共通のキーをもつデータを結合する

  • joinコマンドを利用する場合は2つのファイルの結合する列をそれぞれsortしておかないとうまく動作しない。
sort file1 > file11: 1列目でsortする。
sort +1 file1 > file11: 2列目でsortする。
  • 結合する列のデータは同じ形式にしておく必要がある。小数点の数とか,桁数とか厳密に。printfとかで予め整形しておく。
  • ファイル数は必ず2つ

↑ sortで整数順にならべるときは-nをつけないとsortされないことがあります(2016/06/14 片木) ↑ sort -n は、sort対象を数字として捉え、-nがない場合は、sort対象を文字列として捉えるため。 ↑ ちなみに実数の場合は -g をつけます(POSIX非準拠)。sortはこのページに詳しくまとめられています。

usage:join [オプション] File1 File2
join -1 n : File1のn列目のデータを用いてjoinする *指定しないと1列目が結合するキー列になる。
join -2 n : File2のn列目のデータを用いてjoinする
join -j n : File1のn列目のデータとFile2のn列目のデータを用いてjoinする
join -a n : ペアにならなかったFile nの行も出力する。
 例)join -1 1 -2 2 -a 1 File1 File2 → File1の1列目のデータとFile2の2列目のデータを用いて結合する。File1でペアにならなかったデータも出力するがはFile2でペアにならなかったデータは出力されない。
join -t ,: File1とFile2がカンマ区切りの場合は-tで指定する。
join -o 0 1.2,2.4: 出力列を指定する。この場合は,条件にした列(0で指定する)とFile1の2列目とFile2の4列目を出力。

cat: ファイルを単純に結合する

usage: cat File1 File2: File1の下部(1番下の行の後)にFile2が結合される。

paste: ファイルを横(行ごと)に結合する。

usage: paste [オプション] File1 File2: File1の横(行の右)にFile2が結合される。
paste -d ",": pasteで結合するFile間のデータの区切り記号を""の中に指定する。例の場合はカンマ区切り。

sed: 文字の置換

sed -e 's/:/ /g': ":"を探して空白(スペース)に置換する。

awkの小ワザ(2016/06/07 秋津)

ファイルの区切り文字の指定や変更

  • 入力ファイルの区切り文字の指定
cat t|awk 'BEGIN{FS=","}'
  • 出力ファイルの区切り文字の指定
cat t|awk 'BEGIN{OFS=","}'

これらを組み合わせると入力ファイルと出力ファイルの区切り文字を変換できるのだが,下のようにファイルの中身を全部変換しようとすると上手く動かない。

cat t|awk 'BEGIN{FS=" ";OFS=","}{print}'

そういう時は,下のように一度ファイルの中身を読み込んであげると動くようになる。

awk 'BEGIN{FS=" ";OFS=","}{$1=$1; print}'

awkでprintf,for文

  • 列が多い時に,同じ計算式を繰り返したいや一部だけ式を変更したい時に便利。for文と一緒に使う。(私はもっぱらこの目的で使用することが多い。)
  • printfは,データの書式を指定したい時(桁合わせとか)にも使う。(これが本来の目的だろう。)
  • printf(書式を指定, 書式で指定した所に入れるデータ)
  • printfの書式
%s(文字)
%d(整数)
%f(固定小数点表記 123.456789)
    • joinコマンドを使うときには数字の桁を厳密に合わせる必要があるので,さらに小数点の桁を指定する。
%8.4f(小数点以下4桁で出力(全部で8文字分確保))
%08.4f(小数点以下4桁で出力(全部で8文字分確保)。さらに,整数部分の文字数を合わせる(035.1234のように))
  • printfは単純に文字や数字を「書く」だけなので,データの区切り(スペースやカンマ)は自分でいちいち指定する必要がある。
  • 行の最後には,改行(\n)を指定しよう。そうしないと行の区切りもわからなくなる。
cat t|awk '{printf($1"\t"); printf($1-x"\t"); for (i=2; i<=NF; i++) printf("%f\t",$i); printf("\n")}{x=$1}'
cat t|awk 'NR>1{printf($4"\t");printf($5"\t"); for (i=6; i<=NF; i++) printf("%f\t",$i*(1-($2*10^-2)*('$days'-'$b'+'$a')/'$days')); printf("\n")}'

こんな感じ。詳細はまた今度時間のあるときに書くかも。

awkで空白のデータの入った行を削除する

  • 空白はNULLで判定できる。
awk '$3!=NULL{print}'
Last modified:2022/09/13 09:19:28
Keyword(s):
References:[中分解能衛星Terra/MODIS] [とらりもんHOME]