L'Isola di Niente
L'Isola di Niente » ShellScript Tips » シェルスクリプトの基本

シェルスクリプトの基本

# 最終更新日 2017.01.01

GUI ばかり使う人でも「これくらいは知っておこう」な解説

Linux のコマンド
Linux におけるコマンドとは極端に言うと Windows でいう CUI アプリケーションと思えばよい。
Windows とは違い Linux は CUI と GUI に違いは無いので GUI もコマンドです。
which というコマンドを探すコマンドがあるので試してみましょう。
$ # Fedora の場合
$ which cp
/usr/bin/cp
$ which echo
/usr/bin/echo
$ which which
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
	/usr/bin/which
$ which google-chrome
/usr/bin/google-chrome

よく利用する cp, mv. rm なんかも、更には echo さえも実行ファイルであると解る。
(本当はちょっと違って POSIX の.....興味があるなら調べてね)

which 自身も、Fedora ではエイリアスで上書きされているなんてこともあります。
cd 等は組み込みコマンド、if 等も制御文なので which では何も見つかりません。
そのまま利用できるコマンドは環境変数 PATH で指定されているディレクトリにあるコマンドです。
$ echo $PATH
尚、コマンド自身がスクリプトであることも多いです、/usr/bin を覗いてみれば解ります。
Windows は拡張子が無いとお手上げですが Linux コマンドでは付いていることのほうが少ないです。

コマンドを追加したい場合は /usr/bin に入れるのがお約束になっています。
単純なアプリケーションであればそのまま入っているだけですが複雑な形式になっている場合もあります。
たとえば Google Chrome は /opt/google/chrome に入り実行シェルスクリプトも同じ位置にある。
/usr/bin にはそのシェルスクリプトへのシンボリックリンクがあるという仕組みになっています。

パスを通す、実行する
環境変数 PATH には自分の都合がよいディレクトリを追加することもできます。
たとえば自分のホーム以下に app というディレクトリを作りソレを追加するには
$ export PATH=$PATH:$HOME/app
という具合に指定、但し追加したインスタンス内でしか有効になりません。

常に有効にしたい場合は ~/.bash_profile (又は ~/.profile) に上記を追記する。
このファイルはログイン時に読み込まれるファイルです。

Fedora では ~/.bash_profile に以下の指定が最初からあります。
ので ~/bin を作れば再ログインで勝手に通ります。
PATH=$PATH:$HOME/.local/bin:$HOME/bin

又、環境変数 PATH に入っていなくても絶対パスを指定すれば実行することができます。
カレントディレクトリのスクリプトなら ./ をコマンドの先頭に付加すれば実行できます。
Nautilus であればスクリプトファイルのダブルクリックで実行できます、この動作は設定で変更できます。

更に端末から sh コマンドに引き渡せば実行パーミッションを付ける必要もありません。
$ sh testfile.sh #例
他に Nautilus や Gedit 等シェルスクリプトで拡張するという使い方もあります。

カレントディレクトリ
カレントディレクトリとはユーザが現在作業を行なっているディレクトリです。
gnome-terminal を普通に起動すると自身のホームがカレントディレクトリになります。
Nautilus でダブルクリックは開いているディレクトリをカレントディレクトリに設定してくれます。

通常コマンドはカレントディレクトリを移動、その場所にあるファイルに対して実行する。
Windows で GUI アプリの場合は何をやるのも絶対パスですが CUI ではそれが普通です。

たとえばあるディレクトリの ZIP ファイルのみを全削除したい場合
#!/bin/sh
rm *.zip
という内容のシェルスクリプトファイルを作り実行したいディレクトリに置きます。
そのファイルに実行パーミッションを与え Nautilus から W クリックする。
するとそのディレクトリ内の *.zip ファイルのみが全削除される、という動作をします。

bash
/bin/sh は必ず bash へのシンボリックリンクであるとはかぎりません。

ただ現在 macOS, Redhat 及び Fedora 等の主要な UNIX 系はすべて bash になっています。
ぶっちゃけ bash 依存のコマンドをバリバリ使っても問題ないと思います。
tcsh 等を愛用している人はそんなことは解っているだろうし。

Ubuntu日本語フォーラム / selectコマンドは、どのパッケージにあるのか?
Ubuntu の場合はこんなことがあるようです、最近の奴は知りませんけど。

端末のおさらい
改行したくない「 ; 」
日本人は何故か大好きな人が多い(不思議だ...)ワンライナー。
; の前後コマンドに何の関連性もありません、どうしても改行したくない場合のみ。
$ echo abcd ; echo えーびーしぃでぇ

子シェルで実行「 () 」
コマンドを () でくくると子シェル、つまり別のシェルで実行させた扱いになります。
() を脱出した時の環境に影響を与えません、上記 ; と組み合わせると良いでしょう。
$ (cd /usr/bin ; pwd)
/usr/bin
$ pwd
/home/sasakima-nao

パイプ「 | 」
前方のコマンドを実行して得た結果を後方のコマンドに渡す場合はパイプを利用する。
以下の例は /usr/bin 内にある gnome アプリ名を grep で確認する方法
$ cd /usr/bin
$ ls | grep gnome

バックグラウンドで実行「 & 」
端末から GUI アプリケーションを起動するとソレを終了するまで処理が戻ってこない。
バックグラウンドで実行させるにはコマンドの後ろに & を付ける。
$ gnome-calculator &
& の後ろにコマンドを書けば同時起動もできる、使い道はともかく...
$ gnome-calculator & gedit
ついでに、GNOME では Nautilus では不要。
$ # & はいらない
$ Nautilus
ファイルマネージャは常に起動しているので GApplication の仕組みで転送により終了する
Gedit を起動した状態で端末から Gedit を起動等も同じようになる

終了コードを調べる「 $?, &&, || 」
C 言語で main 関数の最後に return 0; と書く、途中で何事かがあればゼロ以外を return させる。
のは C 言語を少しでも勉強した皆さんならばご存知のはず。
これは何事も起こらず最後まで到達したらゼロ(正常終了)という終了コードを戻しているのです。
$ nano #Ctrl+X で終了させる
$ echo $?
0
$ ^C #Ctrl+C で強制終了
$ echo $?
130
&& はゼロなら次のコマンドを実行、|| は逆の動作を行います。
$  echo 新規書き込み > a.txt && echo 書き込み成功
書き込み成功
$ cd /
$ echo 新規書き込み > a.txt && echo 書き込み成功
bash: a.txt: Permission denied
$ echo 新規書き込み > a.txt || echo 書き込み失敗
bash: a.txt: Permission denied
書き込み失敗

コマンドの結果を引数に入れる「 `` 」
`` でコマンドを囲むと結果を引数に指定できます。
$ echo 現在 `pwd` にいます
$ # 変数に入れることもできる
$ text=`cat .bashrc`

コマンドを途中で改行「 \ 」
区切りを見やすくする、やる人はいないだろうけどコマンド名の途中でも改行可能。
$ ec\
> ho あいうえお
あいうえお

ホーム「 ~ 」
$ cd /usr/bin
$ cd ~   # ホームに戻る
$ echo ~ # ホーム位置が表示される

特殊ディレクトリ「 . 」「 .. 」
$ # . が現在のパスに展開される
$ ./test.py
$ # 一つ上の階層ディレクトリに異動
$ cd ..
$ # 現在のカレントディレクトリでファイルマネージャーを起動
$ gvfs-open .

リダイレクト「 >, >>, <, << 」
標準出力先を変更する、端末に出力される結果をテキストファイル等で書き出す。
> なら書き換え
>> なら追記となる。
$ echo `pwd`のファイル一覧 > d.txt
$ ls -l >> d.txt
標準出力なので gnome-terminal を2つ起動してでこんなこともできます。
$ tty
/dev/pts/0
別の端末から
$ echo あっちに出力 > /dev/pts/0 # あっちに出力される
< はあまり使い道が無い、cat の代わりなんかに使える。
$ cat bike.txt | grep HONDA
# 上記と動作は同じ
$ grep HONDA < bike.txt

<< はヒアドキュメントで主に使う。
端末上では使い道が少ないけど改行付きの長い文字列を簡単に扱える。
$ cat << _EOS_ > filelist.txt
> `pwd` のファイル一覧
> 作成日
> `date`
>
> `ls -1`
> _EOS_

標準エラー出力のリダイレクトは 2>, 2>> と打ち込む。
$ cat 存在しないファイル.txt > c.log 2>> error.log

メタ文字
Linux ではファイル名に半角スラッシュ以外は何でも利用できます。
恐るべきことに改行コードさえファイル名に含むことすら可能。

しかし困ったことにシェルでは利用できないメタ文字が沢山あります。
記号は全滅、タブ文字や半角スペースは区切りという意味になる。
cat My Documents.txt
上記では[My] と [Documents.txt] という2つのファイルを読み込もうとする。
この場合はクォーテーションで囲むかバックスラッシュ記号(エスケープ文字)を付ける。
cat "My Documents.txt"
cat 'My Documents.txt'
cat My\ Documents.txt
# 記号を使ったファイルも同様に
cat \#012.txt
cat "@1st.txt"
シングルクォーテーション内はシングルクォーテーション以外のエスケイプは不要。
ダブルクォーテーション内も同様だけど $, ` のメタ文字は本来の意味を保持する。
つまり変数やコマンド結果を含めるかどうかを選択できる。
HONDA=KAWASAKI
echo '$HONDA `pwd`' #=> $HONDA `pwd`
echo "$HONDA `pwd`" #=> KAWASAKI /home/sasakima-nao

エスケープシーケンスというのもあります。

エスケープシーケンス 意味
\aベルを鳴らす
\bBackSpace
\fフォーム フィード
\n改行
\rキャリッジ リターン
\t水平タブ
\v垂直タブ
\\\ を表示
\?リテラル疑問符
\'シングルクォーテーション
\"ダブルクォーテーション
\0NULL 文字
\数値8 進数
\x数値16 進数

C 言語の経験があるならお馴染ですよね。
echo -e "これで\n改行できる"
たとえばソースコードで半角スペース4つのインデントを水平タブに全置換したものを作る場合
cat hankaku.c | sed 's/    /\t/g' > tab.c

変数
変数の名前はアルファベット(大文字小文字は区別する)、数字、アンダースコア(_)を利用できる。
但し名前の先頭に数値は指定できない。
値を代入するには = を使う、イコールの前後に空白(スペースやタブ)を入れてはいけない。

変数を参照する場合は $ を名前の前に付ける。
初期化が終わっていない(何も代入していない、未宣言)変数を参照すると NULL として扱われます。
以下のような場合には参照できません(あまりこういう場合は無いですが)
#!/bin/sh
name=sasakima
echo $namenao #=> NULL
変数名がどこからどこまでか判断できないということです、こういう場合は
#!/bin/sh
name=sasakima
echo ${name}nao #=> sasakimanao
と { } で変数名を囲む、囲むのが正しい表現とはこういうことです。

現在定義されている変数を確認するには以下のコマンドを使います。
$ set      # 現在 Shell で定義されている変数一覧
$ env      # 現在 Shell で定義されている環境変数一覧
$ readonly # 現在 Shell で定義されている読み込み専用変数一覧
定義した環境変数はそのシェルから起動したアプリに引き継ぎできる。
$ export CAR=Toyota
$ gnome-terminal # このシェルから $CAR が参照できる
変数に入るもの、というか扱えるものはすべて文字列です。
#!/bin/sh
NUM=3
echo $NUM + 6 #=> 3 + 6
Perl や Python と違い整数として扱ったり計算をするのに一手間掛かります。

数値計算
シェルスクリプトは文字列しか扱えないので数値として認識させる必要があります。
色々な方法がありますが以下に例を、ちなみに数学同様に掛け算割り算が優先されます。
expr 計算式
結果を標準出力に書き出すコマンド、変数に代入する場合はバッククォーテイションでくくる。
演算子と整数の区切りに半角スペースが必要、括弧と掛け算はエスケープが必要。
$((計算式))
結果を戻す関数のように使える、半角スペースやエスケープは不要。
bash のみと書かれている場合が多いけど dash でも利用可能。
$[計算式]
同上だけど bash のみ有効であるようです。
let 変数=計算式
bash のみ有効。

実際に bash と dash でやってみる。
bash で動かせば全部問題なく 43 になりますが、$((計算式)) を主に利用するのが一番かと。
#!/bin/sh
echo $((3+5*8))  #=>43
expr 3 + 5 \* 8  #=>43
echo $[3+5*8]    #=>$[3+5*8] 
let aa=3+5*8
echo $aa         #=>./calc.sh: 5: let: not found

bc という小数点を扱った計算もできるコマンドもあります。
expr 等と違いファイルやパイプで引き渡された計算式を処理します。
オプション無しで起動すれば計算式の次行に結果を出力する簡単な計算機になります。
$ echo "3.5+2.2" | bc
5.7
$ bc
3.5+2.2
5.7

スクリプトファイル
Linux ではテキストファイルの一行目は特別な意味があります。
以下のスクリプトを web という拡張子の無いファイルで保存したとします。
#!/usr/bin/firefox

このファイルを火狐で開きます
chmod +x web で実行パーミッションを付け ./web で実行するとあら不思議。
実行パーミッションがある場合はその一行目アプリにファイルが引き渡されます。
シバンといいます。

Nautilus 等が拡張子が無いファイルを見分けているように Linux は拡張子は無意味です。
(/usr/share/mime/magic 等を使って見分けている)
又 CUI と GUI にまったく区別が無いということも上記で解ると思います。

ということでシェルスクリプトファイルは一行目に #!/bin/sh が必要です。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.