📚

シェルスクリプトのTip集

2020/10/24に公開

まえがき

シェルスクリプトとはUNIXでコマンドを実行させていくものである。厳密にはUNIXを元に定めららた国際基準のPOSIX準拠のBourne Shellを指す場合があるが現在ではbashがデファクトスタンダードとなっている。bash以外にもcshやtcsh、zsh、他にもいろいろあり、独自の拡張がなされている。
シェルスクリプトの主な機能はコマンドを流してその結果を確認することである、そのためコマンド群も含んだ上でPOSIX基準と呼ばれることもある。
この記事ではシェルスクリプトの基本的(?)な使い方を実例を出しつつ解説したい。

様々な記号

  • $# 引数の数(スクリプト自身は数えない)、./test.sh aaa のときは1
  • $@ 全パラメーター、""でくくると空白ごとに切られる
  • $* 全パラメーター、""でくくると一つにまとめられる
  • $0,$1,$2... コマンドラインのパラメーター、$0はスクリプト自身の名前
  • $? 直前のコマンドの終了ステータス

変数について

シェルスクリプトは変数は格納するときは$をつけない$var="hoge"とすると$varhogeがきちんと格納されない。
一方で参照するときは$をつけないといけない。
シェルスクリプトには基本的にスコープという概念がないので変数はネストの中で書いても外に影響しますし、当然、定数もないのでどこででも好きに書き換えられます。

標準出力、標準エラー出力、リダイレクト

$ test.sh > dev/null 1>&2

ここでの1は標準出力を、2は標準エラー出力を表しており>は出力先をリダイレクトすることである。
$ test.sh > log.txtとするとlog.txtのファイルに書き出すことになる。
/dev/nullは空(null)のファイル出力先であり記録されない、1>&2は2の出力先を1と同じにするという意味(らしい)。

追記

>>を使うと追記になる。ログなどの追記のために使う

シングルクォート'、ダブルクォート"、バックスラッシュ`

シングルクォートは変数展開しない、ダブルクォートは変数を展開する、バックスラッシュは中身をコマンドとして実行してその結果を変数に格納する

test.sh
#!/usr/bin/env bash
var="hoge fuga"
echo "$var" # hoge fuga
echo '$var' # $var
echo `$var` # :コマンドが見つかりません

コマンドとしての$()

バッククォートの代替として$()も使える。これはPOSIX準拠ではないがbashだと使えて最近のテキストエディタなどではシンタックスハイライトもつくので使えるならこっちを使っていきたい。

if文とtestと[]

if文の判定にtestと[]という書き方がありますがまったく同じです。
if文の中身は開始時点ではthenelif(else ifの意味)、elseそして終わりにはfiを使います。
これらの制御構文は別の行に書かなければなりませんが;文の終わりを明示してやると一行にまとめられれます。C言語的なif() 文;という書き方も出来ます。

if.sh
if test $hoge == hoge
then
   echo "fuga"
fi
if [ $hoge == hoge ]; then
   echo "fuga"
fi #こうできる
if [ $hoge == hoge ]; then echo "fuga"; fi #こうもできる

いわゆる制御構文はそれがあると改行があるとみなされるようで;を入れるとエラーになります。

for文

for文ではinがよく用いられる。これはfor $elem in $var1 $var2 $var3 ...のような形、もっというとvarNはコマンドの結果を用いることが多い。制御構文にはdodoneを用いる。
例えば以下のようなことができる。(この程度であればfindコマンドを使ったほうが早いが...)

ls.sh
for name in $( ls ); do
    if test -f $name ; then echo "$name はファイルです"; fi
    if test -d $name ; then echo "$name はディレクトリです"; fi
done

計算の(( ... ))とforループ

POSIX基準だと計算はexprコマンドを使うことになるがbashだと((...))の中身は計算式として展開される(速度も組み込みなので早い)。
これを使うとC言語風なfor (( i=0; i<N; i++ )); do...という書き方が可能である。

イディオム

while read

for inを使った場合、区切りは空白になってしまうので一行ずつの処理が出来ない。そこでwhilereadを使ったイディオムがある、その場合コマンドはパイプライン|で流し込む。

ps.sh
ps -aux | while read line; do
    echo $line
done

とできる。これだけだと意味がないがいろいろなものを抜き出したりなどができる。

sedコマンドで一行目を削除

コマンドの結果の一行目はタイトル的なものが入ってるので読み飛ばしたいときがある。そんなときにはsed '1d'を使って削除できる。先の場合だとps -aux | sed '1d'のようになる。

awk '{print $n}'

複数の項目の中から特定の場所のもをを取り出すのにはawkコマンドが使えるタイトルのようにすることでn個目の要素を取り出せる。
これらを組み合わせると次のようなことができる。

ps.sh
ps -aux | sed '1d' | while read line; do
    pid=$( echo $line | awk '{print $2}' )
    echo $pid
done

のようにプロセスIDだけ抜き出せる。

あとがき

とういことで実例からシェルスクリプトの使い方を解説した。
今回はコマンドの結果からある値を取り出すという操作をした。

Discussion