背景

  • ラズパイやりたい
  • よくエラーが出る。出た場合に、スマートに原因確認したい
    • 人口少ないのでググっても解決されないこと多々ある
  • シェル読めばいいのでは?
  • 雰囲気でしか読めない
  • シェルの知識を一回洗いたい
  • ということで改めて雰囲気でやってたところお勉強

注意

筆者はターミナルでzsh使ってますが、クウォーティングがbashと異なるせいか、ターミナルで思ったように動かないことが多発しました。そんなときはきちんとファイルにして、shebangで/bin/bashを設定して遊んで見ましょう。zshなんか個人の問題だしシェルスクリプトをzshで書くのはやめようね。色々困るので

そもそも入力されたコマンドってどうなってるの?

雰囲気というか、全く勉強したことなかった笑
どうやらLinuxでは以下の順番でシステムコールというものが実行されています、

  • fork = プロセスをコピーして子プロセスを作成する
  • exec = コマンドを実行する
  • wait = 親プロセスが子プロセスを監視する
  • exit = 実行結果を返す
  1. forkによって現在のコマンドラインのプロセス(以降,X)から子プロセス(以降,x)をコピーして作成
  2. Xはwaitによって、子プロセスを監視
  3. x上でexecによってコマンドが実行され、exitで結果が返される
  4. waitが感知してコマンドを終了する

shebang

シェルスクリプト先頭にあるこういう表現。

#!/bin/bash
#!/bin/sh

ただのコメントやん。と思ってしまいますが、実はコメントではないので、なんでも書いていいわけではないようです。

実は、先ほど書いたexecシステムコールに関係します
execシステムコールは、バイナリ形式のファイルを読み込む仕組みです
しかし、ファイルの先頭が#!となっている場合のみ、#!から改行までをコマンド、それ以降は、そのコマンドにファイルが引数として渡されているとして扱われます。

なので、例えば

sample_php.sh
#!/usr/bin/env php
<?php  
    echo "Hello,world";  
?>

これを、こうすると動くんです

$ ./sample_php.sh

便利やんと思う反面、shebangを扱う上で弱点があるようです。
ファイルに必ず実行権限がないと実行できないようです

$ chmod -x sample.sh
$ ./sample.sh
zsh: permission denied: ./sample.sh
$ chmod +x sample.sh
$ ./sample.sh
Hello,world

この問題は、直接shebangをコマンドラインで指定すると解決されます。

$ chmod -x sample.sh
$ /bin/bash sample.sh
Hello,world

環境構築しているときに、たまにこういう場面きますよね、こういう理由だったみたいです。

sourceコマンド

よく、.bashrcを修正した後、その修正を反映させるにはsource ~/.bashrcをしろっていうのをみますね、
これに使われるsourceコマンドですが実はスクリプトファイルを1行ずつ読んでいっているだけなんです。これを聞くと普通のshと同じようです。唯一違う点は、sourceは子プロセスで実行しないんです。なので変数などの設定が現在のプロセスに全て引き継がれます。だから追加したばかりのaliasが使えたりするんですね、

ブレース展開

パス名展開(*.shとかsample.[ch]とかのパス名を展開する機能)はみなさん知っていると思いますが、ブレース展開というものもあります

めっちゃ便利そうです。

mkdir {src,test} // srcディレクトリ,testディレクトリを作成
mkdir dir_{1..100}  //ディレクトリを`dir_1`から`dir_100`まで作成
mkdir sample-{a..d} // sample-aからsample-dまで作成
mkdir sample-{4..11..2} //4から2ずつ増えて11まで展開する。従って `4,6,8,10`として展開され作成

やばい、めっちゃ便利。

パラメータ展開

${変数:-}

たまにみますよねこの表現、変数に値が設定されていない場合はを使うみたいです。

コマンドの引数が設定されていない場合のデフォルト値を設定する際によく使われてます

myecho.sh
#!/bin/bash
echo ${1:-デフォルトパラメータだよ}
$ ./myecho.sh
デフォルトパラメータだよ
$ ./myecho.sh 引数1
引数1

これと似た表現で、以下のようなものもありますね、

${変数:=値}

これはどうやら変数が空文字または未定義だった場合にを代入して展開します

さらに似たような表現で

${変数:?値}

もちょくちょく見ます。

これは、変数が未定義だった場合は標準エラーにを出力します。

$ echo ${file:?Please set file}
zsh: file: Please set file

使いどころありすぎてイイ!

if文

以下のように[]があったりなかったりと3パターン存在しているのがすごい気になってた。

if コマンド; then
    :
fi

if [条件っぽいもの]; then
    :
fi

if [[条件っぽいもの]]; then
    :
fi

ちなみに、:はヌルコマンドといって、文字どおり何も実行しない。しかしこれを書かないとなんか処理しろやゴルァと言われるので書いている

まず一つ目if コマンド; then ...

コマンドの実行結果は,正常終了なら0,それ以外なら0以外を返す。
if {コマンド}; then ....の場合は、そのコマンドの実行結果が0である場合に条件に合致するということだ

if cd ~/sample; then
    echo 'Dir exists.'
else
    echo 'Not exists.' >&2
fi

この場合、cd ~/sampleで、sampleディレクトリが存在している場合は条件内に入る。

echo 'Not exists.' >&2は、標準出力を標準エラー出力ヘリダイレクトしている。エラーを手っ取り早く表示するにはこの方法がいいらしい

次にif [条件っぽいもの]; then ...

独特の記法がされている。これはtestコマンドのエイリアスらしい。

testコマンドとは、引数に独自の演算子を渡すと、文字列のチェックやファイルの権限確認などを行なってくれる

演算子 0(true)を返す場合
-a x xが存在する場合
-e x xが存在する場合
-d x xがディレクトリの場合
-f x xがファイルの場合
-L x xがシンボリックリンクの場合
-h x xがシンボリックリンクの場合
-z x xが空文字列の場合
-n x xが空文字列ではない場合
x === y xとyが等しい場合(数値でも文字列でも可能)
x == y xとyが等しい場合(数値でも文字列でも可能)
x != y xとyが等しくない場合(数値でも文字列でも可能)
x < y xがyよりも辞書順で前である場合(数値でも文字列でも可能)
x > y xがyよりも辞書順で後ろである場合(数値でも文字列でも可能)
$ test 0 == 1
$ echo $?
1    // falseとして1が返る

$ [ 0 == 1 ]
$ echo $?
0     // trueとして0が返る

$ [ 'hoge' \< 'fuga' ]  // クウォーティング必須。 '<'でも可
$ echo $?
1     // falseとして1が返る

ちなみに、$?は直前のコマンドの実行結果を取得するコマンド。

また、これらを複雑に条件づけるために、以下の演算子も存在する

演算子 処理
式a -a 式b aかつb。よくある(a&b)
式a -o 式b aまたはb。よくある(a

最後にif [[条件っぽいもの]]; then ...

これは、testコマンドをさらに拡張したものみたいです

簡単にいうと、先ほどの-aや大なり小なりのクウォーティングが不要になるんです。

testコマンド時▼

[[]]時▼

$ [ 'hoge' < 'fuga' && 1 == 0]
$ echo $?
1

後者の方がわかりやすいですねえ。こんな書き方ができるのは、[[]]がコマンドではないためらしいです。

しかも、[[]]の場合には、&&の前者で1(false)だった場合は後者の処理は行わないので使いやすいですね。なので先ほどの処理では実は1 == 0の処理は行われていないんです!

まとめ

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.