シェルスクリプトでcalenderコマンドを作った話 (その1)

シェルスクリプトにふわっと入門した。

今回はcalenderコマンドを作りながら調べたことを入門チックにまとめた。

長くなったのでコマンドの作成過程は次回の記事で。

シェルスクリプト楽しいよ。関数型もあるよ。みんなも書こうよ。

シェルスクリプトの基本について調べた

シバン(シェバン)について

  • シェルスクリプトのファイルの先頭の#!/bin/shのこと
  • shell-bang、hash-bang、sharp-bangとも
  • shell-bangを縮めたshebangが一般的
  • #!/bin/shとか、#!/bin/bashとか
  • shがもっとも基本的だけど、Linuxは大抵bashだし、bashで開発することが多いからbashでいい(らしい)
  • というわけで以降#!/bin/bashでいきます
  • まあzsh使ってるんですけどね
  • あとコマンドがbashのビルトインコマンドなのかとかいちいち調べません
  • 取り敢えず動いたらそのままいきます
  • そういうの調べるのはもうちょっと慣れてからでいい気がする
  • ちなみに#!は以降の文字列をそのままexecに渡すらしい
  • このシバンってどこでも使われてる割には仕様がふわふわらしい

変数について

hoge=hoge
foo="foo"
hogefoo=$hogefoo
bar="$1"
echo $hoge$foo
echo "$hoge$foo"
echo "${hoge}111"
echo $bar
  • 宣言時はダラー$なし、呼び出しのときはあり
  • 変数名と==と値の間にスペース入れるとエラー
  • 値があるか分からない変数($nとか)を代入するときは注意
  • $nについては下の関数と引数の話で出てくる
  • hoge=$1とすると、$1が未定義のときhoge=となりエラーが出る
  • hoge="$1"としておくと安心
  • ローカル変数はlocal foo=foo
  • 関数の中とかで使えますね
  • 定数はreadonly
  • ローカル定数はlocal readonly

文字列について

  • ""''がある
  • ""は変数展開するけど''はしない
  • 連結の演算子とか関数とかはない、並べるだけ
hoge="hoge"
echo "$hoge"
# => hoge
echo '$hoge'
# => $hoge

数式処理について

num=3
echo $(($num + 5))
echo $((num + 5))
# => 8
  • 計算式は$(())で囲む必要がある
  • 中で変数使うときダラー$あってもなくても動いた
  • exprコマンドでも同じようなことできるけど遅い
  • ただ$(())はshにはないので、sh使うときはexprしかない
  • $(())では整数しか処理できないらしい

関数、引数について

  • 関数はfoo() {} などと定義
  • 引数は特に指定せず、$nで受け取る
  • nはCと同じで1から
  • シェルスクリプト自体の引数も同じく
  • 関数の中では関数の引数が優先的に$nに代入される
  • しかし$0だけは関数の中でもそのシェルスクリプトのファイル名になる
  • returnは値を返さず、関数をただ終了するだけ
  • 代わりにechoで値を返す
  • というかもう標準出力にだしちゃう
  • めっちゃ不便…
  • リダイレクトすればいい話だった
  • リダイレクトとかパイプで処理を繋げていく感じ
  • さらに訂正、リダイレクトじゃなくてパイプ|だった…
  • 関数の合成に似てる
  • プログラマーの君! 騙されるな! シェルスクリプトはそう書いちゃ駄目だ!! という話 - Qiita
  • まあシェルスクリプトの書き方としてあまり関数は使わないほうがよさそう?

リダイレクション、パイプについて

  • リダイレクション>>>は標準出力をファイルに流す
  • >は上書き、>>は追記
  • パイプ|は標準出力をコマンドに流す
echo hogehoge > hoge.txt
# hoge.txt => hogehoge
echo fugafuga > hoge.txt
# hoge.txt => hogehoge
#             fugafuta
echo foofoo > hoge.txt
# hoge.txt => foofoo

seq 1 9 | head -n 2
# => 1
# => 2

ifについて

  • elifだけ注意
foo="foo"
[ "$foo" = "foo" ] && echo $foo || echo "no foo"

if [ "$foo" = "foo" ]; then
  echo $foo;
elif [ "$foo" = "bar" ]; then
  echo "it's bar";
else
  echo "else";
fi
  • [の右と]の左はスペースないとエラーになる
  • []もコマンドだから
  • というかシェルスクリプトは(ほぼ)全部コマンドからできてるらしい

testについて

  • testは与えられた条件式によって0または1を返す
  • []testのエイリアス
test "" = ""; echo $?
# => 0
test "" != ""; echo $?
# => 1
  • ちなみに[]はbashとかzshとかのビルトインコマンド
  • なのでshにはない(たぶん)

ifとかで何もしたくないときについて

  • nullコマンド:が使える
cond=`test "" == ""`
if [ $cond ]; then
  :
else
  echo "if not";
fi
  • まあ! $condでもいいんだけど、覚えておくと使い道あるかも

=について

  • =しか使えなかったけど、今は==も使えるらしい
  • testコマンド中だとオプションの-eq-ne-ltなども使える

while、forについて

while test "" = ""
do
  echo "one time"
  break
done

cond=1
while [ "$cond" ]; do
  echo "two times"
  if [ "$cond" = "1" ]; then
    cond=0
    continue
  else
    break;
  fi
done
  • whiledoを1行で書くのに;で繋いでるのよく見る
  • 別にtestコマンド使わなくてもいい
for var in hoge fuga foo bar
do
  echo $va
done

for i in $(seq 0 4); do
  echo $i
done

foo=foo
bar=bar
for val in "$foo" "$bar"; do
  echo $val
done
  • 配列(?)を回す
  • seq使う時は$()で囲む必要あるみたい(バッククォート``でも可)
  • 変数使う時は""で囲まないと変数中のスペースが無視される

コマンド結果の変数への代入について

  • バッククォート``使う
  • バッククォート``は入れ子にできないし$()の方使うべきらしい
  • まあbashの機能だからshにはないけど
# firstday=`date -d "2017-01-15" "+%w"`
firstday=$(date -d 2017-01-15 +%w)
echo $firstday
# => 0 (日曜日)
  • 下で書くけどdateの書き方、これはGNU系の話…
  • Macだとエラーを吐く
  • BSD系はオプションが異なる

色々あるダラー$の使い方について

  • bashの変数($,ダラー,ドル)まとめ - nori3tsu's blog
  • 算術演算 $((1+2))
  • 変数fooが未定義のときbarを使う ${foo-bar}
  • 変数fooが未定義または空文字列のときbarを使う ${foo:-bar}
  • 変数fooが未定義のときbarをfooに代入して使う ${foo=bar}
  • 変数fooが未定義または空文字列のときbarをfooに代入して使う ${foo:=bar}
  • 色々と便利そうなのある

かっこ類について

bracket [ ]

  • testコマンドのエイリアス

double bracket [[ ]]

  • [ ]の強化版
  • 中で&&||、Pattern matching、正規表現などが使える
  • 逆に[ ]の中で使えないんですね…

Parentheses ()

  • subshellを起動してコマンドを実行
  • かっこ類の中でこれだけ両端にスペースがいらない
(cd /tmp; pwd)
# => /tmp

Braces

  • 「変数の展開」または「一連のコマンドをカレントシェルで実行」
# 変数の展開
foo=foo
foobar=${foo}bar

# 一連のコマンドをまとめて実行
{ time -p { sleep 1; sleep 2; echo "finished"; }; }

その他

  • プログラムの最後は改行で終わること
  • 「改行するまでが1つのコード」的なことが何かで決められてるらしいので、最終行も改行しないとたまにちゃんと動かない
  • 何でどう決められてたか忘れてしまった…

シェルスクリプトで関数型チックなことができた

シーケンス

  • seqコマンドを使う
  • Pythonのrangeみたいな感じ
seq 1 3
# => 1
# => 2
# => 3

seq 1 3 | head -n 1
# => 1

seq 1 3|tail -n2|head -n1
# => 2

map

  • これめっちゃ使った
seq 0 3|while read i;do
  echo $((i*2))
done
# => 0
# => 2
# => 4
# => 6

内包表記

  • 使わなかった
  • 大抵mapでどうにかなる
for i in $(seq 0 3);do
  echo $((i * 2))
done | head -n 2
# => 0
# => 2

コマンドを作った

  • コマンドの作成過程は次回の記事で
  • 今回の実装もつらみがたくさんあった
  • でもがんばった分シェルスクリプトが好きになれた気がする
  • あと、上記の記述は全部初心者理解なのであしからず
  • UNIX & Linux コマンド・シェルスクリプト リファレンス
  • 時間あるときにここ読んでいこう
  • 気が向いたら書籍も買いたい