Tech Blog

シェルプログラムの基本

2021-02-22

シェルプログラムの先頭には#!/bin/shを書きます。

シェルプログラムのファイルには実行権限(xフラグ)を与える。

#はコメントとして扱われます。

#!/bin/sh
#
# comment
# comment comment
echo "Hello World. # comment ";

また、上記の様にダブルクォーテーションで囲むと、 echoコマンドの引数として扱われるため、コメントとしては取り扱われません。

./comment
Hello World. # comment

改行

コマンドは改行することで1つのコマンドとして区切られます。

改行を無視する場合、は\を利用する

echo Hello \
then else> World.
Hello World.

\の前にスペースをいくら入力しても、1つのスペースに置き換えられる点に注意が必要です。

echo Hello     \
then else> world.
Hello world.

ワイルドカード

ワイルドカード説明
*文字列全部
?一文字
[...][ ]の中に含まれる文字のどれか1つ
[!...][ ]に含まれない文字
*abc〜abcの様に、abcで終わるファイル。abcというファイル名でもOK
*abc*ファイルの中にabcが含まれる文字列。
[a-z]*aからzまでの文字のどれかで始まるファイル。
[-a-z]*-aからzで始まるどれかで始まるファイル
a-zA-Z]*アルファベットの大文字か小文字で始まるファイル
*[0-9]*ファイル名の中に数字が含まれるファイル
[!0-9]*数字では始まらないファイル
??2文字のファイル
??*2文字以上のファイル
abc/*abcというディレクトリ下の全ファイル

隠しファイル

.で始まるファイルを隠しファイルといいます。

隠しファイルはワイルドカードを利用しても除外されます。

ls -a
.          .Xdefaults .exrc      ZZZ        dir_a      xyz
..         .cshrc     ABC        def        ghi
ls
ABC   ZZZ   def   dir_a ghi   xyz
ls .*
.Xdefaults .cshrc     .exrc
ls .*rc
.cshrc .exrc

引用符(クォーテーション)の使い方

  • バックスラッシュ\を使う
  • シングルクォートで囲む。
  • ダブルクォートで囲む。

ワイルドカードカード等のシェルにとって特別な文字は、メタキャラクタと呼ぶ。

;&()|~<>?*[]$'"バッククォート{}改行タブスペース

バックスラッシュ

1文字をクォートするにはバックスラッシュを使います。

echo abc\\def
abc\def

シングルクォート

シングルクォートで囲まれた文字列はすべて普通の文字となる。

ダブルクウォート

ダブルクォーテーションはほとんどすべての文字をエスケープしますが、 $、バッククオート、\という3つの特殊文字はエスケープしません。

  • ダブルクォートで囲んだ場合は$を利用した場合に変数名で展開される
  • バックスラッシュの直後の文字が$の場合はエスケープされる
  • シングルクォートの場合は展開されず、すべての特殊文字がエスケープされる

クォーテーションを使うタイミング

コマンドの引数がシェルに解釈されないようにしたいときに利用する。

シェルがそれを解釈してもよいかどうかを考えて利用する。

echo *
ABC ZZZ def dir_a ghi xyz
echo "*"
*

クォーテーションの使い分け

囲んだ文字列の中で、変数の置き換えやコマンド置き換えした結果を使いたいときは、 ダブルクォートを使う。

FILE=testfile
echo "Cannot remove $FILE"
Cannot remove testfile
echo "Today is `date`"
Today is 2021 2月22日 月曜日 22時34分07秒 JST

シングルクォートはそのままの文字として使いたい場合です。

シングルクォートはダブルクォートをエスケープでき、

ダブルクォートはシングルクォートをエスケープします。

echo "'abc'"
'abc'
echo '"abc"'
"abc"

バックスラッシュをエスケープするには、 シングルクォートでもバックスラッシュでも可能です。

シングルクォートの中で変数を展開したい場合は、以下のように書く

'Part1'"Part2"'Part3'

バッククォートによるコマンド置き換え

バッククォートはその中に書かれたコマンドを実行し、その結果をその位置に書き込みます。

echo "Today is `date`"
Today is 2021 2月22日 月曜日 22時45分15秒 JST

バッククォートをバックスラッシュでエスケープすることにより、 コマンド置き換えのネストも可能です。

STRING=`echo "abc \`echo def\` ghi"`
echo $STRING
abc def ghi

最初にecho defの部分が置き換えられ、その後echo "abc def ghi"が実行されます。

以下が同じ意味のコマンドです。

TMPSTR=`echo def`
STRING=`echo "abc $TMPSTR ghi"`
echo $STRING
abc def ghi

コマンド終了時のステータス

コマンドは慣例として、正しく終了したときは0、失敗したときは、1が終了時にセットされ、 エクジットステータスと言う。

exit status実行結果正誤
0成功true
1失敗false

またexit statusはコマンドを実行した直後に$?という変数に代入される。

echo $?
0

コマンドセパレータ

コマンドは通常改行でコマンド行となりますが、その他にもコマンドの区切りとするものがあります。

これらをまとめてコマンドセパレータと呼びます。

コマンドセパレータ説明
改行1つのコマンドの区切り
;1つのコマンドの区切り
|出力された結果を次のコマンドの入力にする
&バックグラウンドで実行させる
||OR制御演算子
&&AND制御演算子

セミコロン(;)

セミコロンでコマンドを区切ると、左から順に実行されます。

cat file1;cat file2;cat file3
abc
def
ghi

セミコロンを使うと、複数行にまたがる記述を1行にまとめることができます。

パイプ(|)

左から右に流していくという意味で、パイプライン処理といいます。

|の右側で実行したコマンドの結果を、|の右側のコマンドの入力として処理する働きがあります。

echo abc | wc
       1       1       4
cat file2 file1 | sort | more
abc
def

パイプライン上にあるコマンドはそれぞれ別のプロセスとして動作しています。

一番右に書かれたコマンドの終了コードが、そのパイプライン全体の終了コードとなります。

command1 | command2

を書き直すと、

comamnd1 > tmpfile; command2 < tmpfile

となります。

command1の結果をtmpfileに書き込み、その後tmpfilecommand2の入力とします。

バックグラウンドでの実行

アンパサンド(&)をコマンドの後ろにつけると、そのコマンドはバックグラウンドで実行されます。

make &

OR演算子

command1 || command2

command1はすぐに実行されますが、command2は実行結果が0でない(trueではない)場合に限って実行されます。

AND演算子

command1 && command2

command1の実行結果が0(true)の場合に右側のコマンドが実行されます。

mkdir directory && cp file directory

コマンドのグルーピング

丸括弧()や中括弧{}を使ってコマンドをグルーピングすることで、

複数のコマンドをあたかも一つのものであるかのように実行させることができます。

丸括弧()によるグルーピング

丸括弧で囲まれたコマンドは現在の動作しているシェルとは別にサブシェルのもとで動作することになります。

今のシェルが新しくシェルを動作させ、その中で動作するものです。

今のシェル(親)はサブシェル(子)が終了するまで次の処理には移行できません。

丸括弧を使うのは、現行の状態を変えたくは無いが、変えた状態で何かをやらなくてはならないときです。

(cd $HOME/makedir; make)

中括弧{}によるグルーピング

中括弧を使って

{ command1; command2; ....; }

の様に、グルーピングすることもできます。

中括弧で囲むと、現行のシェルの中で実行されます。

中括弧の前後にはスペースが必要です。

また、中括弧の中の最後のコマンドはセミコロンが必要です。

改行すればセミコロンやスペースが不要となります。

{
  command1
  command2
}

中括弧を使うのは、それぞれのコマンドの結果をひとまとめにしたいようなときです。

{ date; make; } > make.list

makeコマンドの出力結果の前に、その日の日付を挿入する例です。

中括弧はネストさせることもできますし、バックグラウンドで走らせることもできます。

バックグラウンドで走らせた場合は、サブシェルで実行されます。

制御文(if、for、while、case)

if文

if commandlilst
then
  command
else
  command
fi

command-listの結果が0の場合はthen、

そうでない場合は、elseのcommandが実行されます。

fiでif文を閉じます。

#!/bin/sh
if test -f file1
then
  echo "The file exists."
else
	echo "The file does not exist."
fi

else文はなくてもかまいません。

ifと同じ行にthenを書くこともできます。

#!/bin/sh
if test -f file1; then
  echo "The file exists."
fi

また、testコマンドは、鉤括弧で代用可能です。

#!/bin/sh
if [ -f file1 ]; then
  echo "The file exists."
fi

条件を増やす場合は、elif文を利用します。

if condition1
then
  command
elif condition2
then
  command
elif condition3
then
  command
else
  command
fi

for文

for variable in word-list
do
  command
  .....
done

word-listの部分には、スペースで区切って並べます。

#!/bin/sh
for i in a b c d
do
	echo $i
done
./for
a
b
c
d

while文

while command-list
do
  command
  .....
done

command-listの部分が0ではなくなった場合に処理を抜けます。

#!/bin/sh
a=1
while test $a -lt 3
do
	echo $a
	a=`expr $a + 1`
done

実行結果は以下となります。

./while
1
2

コロン(:)で無限ループの設定を行い、 何らかの条件でループを抜けるというやり方もできます。

コロンは、何も処理せず、0を終了コードとして返すコマンドです。

while :
do
  if ...
  then
    break
  fi
done

case文

case string in
  pattern1) command-list ;;
  pattern2) command-list ;;
  pattern3) command-list ;;
  ....
esac

stringのところにある値が、それぞれのpatternという条件に合うかどうか調べていき 条件があった場合にその後ろにあるcommand-listに並べられたコマンドが実行されます。

;;と書いたところまでが処理の対象となります。

caseの終わりはesacという文字です。

#!/bin/bash
STRING=abc
case "$STRING" in
	ABC) echo "STRING is ABC" ;;
	abc) echo "STRING is abc" ;;
	xyz) echo "STRING is xyz" ;;
esac
./case
STRING is abc

patternの条件を設定するときには、ワイルドカードが利用できます。

パイプを使って複数の条件を設定することも可能です。

#!/bin/sh
case $1 in
	abc ) echo "これはabcです" ;;
	def | ghi ) echo "これは、defかghiかのどちらかです" ;;
	abcd* ) echo "これはabcdで始まる文字列です" ;;
	[Yy]* ) echo "Yかyで始まる文字列です(yes、noの判定で使える)" ;;
	"[Yy]" ) echo "Yかyである" ;;
	"[Yy]*" ) echo "Y*あるいはy*という文字列である" ;;
	"[Nn]"* ) echo "Nかnで始まる文字列です" ;;
	[a-z]*[AB] ) echo "小文字で始まりAかBで終わる文字列" ;;
	\* ) echo "アスタリスク(*)です" ;;
	\$ ) echo '$です' ;;
	"!" ) echo "!です" ;;
	'&' ) echo "&です" ;;
	\? ) echo "?です" ;;
	? ) echo "なにか1文字の文字です" ;;
	"" ) echo "何も無い(null文字や変数が未セットの場合)" ;;
	'""' ) echo "ダブルクォーテーションが2つ並んでいる状態" ;;
	\'\' ) echo "シングルクォートが2つ並んでいる状態" ;;
	[!Nn]* ) echo "Nでもnでも始まらない文字列です" ;;
	* ) echo "今までの条件に合致しない残り全部" ;;
esac

testコマンド

testコマンドは、ある条件を判定し、その条件が正しい場合には真(0の値)を返し、 誤っている場合には偽(0以外の値)を返します。

if test -r file1
then
  echo "The file exists and I can read it.
fi
if [ -r file1 ]
then
  echo "The file exists and I can read it.
fi
while [ expression ]
do
  ....
done

改行コードとセミコロン

改行コードではなくセミコロンを使う事によって、複数のコマンドを1行に書くことができます。

if command-list; then command; ...; fi

メリットとして、見やすくなるということもありますが、

コマンドによっては1行にまとめてやらないと期待通りに動作しないものもあります。 (リモートシェル、Makefile)