07. シェルスクリプト


Photo by Javardh on Unsplash

今回の授業の概要と目標

  • シェルスクリプトを置いておくべきディレクトリを設置する
  • 環境変数 PATH に、(上で言及した)ディレクトリを追加する
  • 初歩的なシェルスクリプトを書くことができるようになる
  • Unix における各種操作をシェルスクリプトに置き換えられる

シェルスクリプトとは

スクリプトファイルのコマンド化 を利用して,シェルの操作を一つのコマンドに変えるものである. Unix で何回も似たような作業をするときなどには,非常に威力を発揮する.

今回は(主に) Bシェルのスタイルで学ぶ

少し困ったことに、文法がシェルによって異なり、

  • Bsh 系の方が文法が硬い(= きちんとしている) ("有害な csh プログラミング" という言葉で web を検索してみよう)
  • Csh 系の方が人間にとって直感的.
  • fish のような現代的なシェルの文法は進化している

という傾向がある.

今回は基本ということで主に Bsh 系で学習しておこう. なお、ときおり、現代的なシェルの代表として、fish の文法についても補足する.

準備, 一連の流れ

準備 1.「使える」スクリプトファイルを置くディレクトリを一つに統一しよう.

このあとの設定のため、伝統に従い、ユーザディレクトリの直下に bin というディレクトリを作り、ここにスクリプトファイル等、実行ファイルを置くことにする.下記のように作業する.

1cd ~ ; mkdir bin

準備 2.ディレクトリ bin を環境変数 PATH に追加する.

bin ディレクトリの中身はコマンドとして直接呼び出せるようにしたい. そこで以下の作業を行っておく.

  bash を使っている人の場合

~/.profile ファイル(ログインシェル起動時に読み込まれる) か ~/.bashrc ファイル(対話用に起動するときに読み込まれる)の中で設定するのが良い. 使い分けが面倒だという人は、~/.profile ファイルの中に

1source ~/.bashrc

と書き込んでおいて、設定自体は ~/.bashrc に書き込めば良い.

書き込む内容は、具体的には,これらのファイルの適当なところに

1if [ -d $HOME/bin ]
2        then
3        PATH=$PATH:$HOME/bin
4        export PATH
5fi

などとなる.
  文字 [ や文字 ] の前後に入っているスペースを除去してはいけない. [ は一文字だが,コマンドなので.

こう書き込んでおけば,次回ログイン時よりこれが有効になるが、テストも兼ねてこの設定を下記のようにして反映させよう.

1source ~/.profile

  fish を使っている人の場合

fish には ~/.config/fish/config.fish という設定ファイルがあるので,その中に

1fish_add_path $HOME/bin

という一行を書き込んでおけばよい.

こう書き込んでおけば次回ログイン時よりこれが有効になるが、テストも兼ねてこの設定を下記のようにして反映させよう.

1source ~/.config/fish/config.fish

  実習

上の準備 1, 2 を行っておこう.


あらためて、シェルスクリプト利用の仕組みと操作

スクリプトファイルのコマンド化 で既に学んだことからわかるだろうが、 シェルスクリプトを作って使うには、いつも、次の作業をしておけば良い. スクリプトを作ったら毎回忘れずに以下のようにしよう.

スクリプトを「使えるように設定する」手順:

  1. シバン #!/bin/sh を1行目に書いたシェルスクリプトを作る.

  2. ディレクトリ ~/bin にこのファイルを置く.

  3. スクリプトファイルに実行許可を与える.
    例えば,スクリプトファイルが dummy という名前ならば
    1chmod u+x ~/bin/dummy
    
    とすれば良い.

  実習

  1. Hello というファイル名で,次のような中身のスクリプトを作る.
    1#!/bin/sh
    2echo "Hello, world!"
    
  2. 上の スクリプトを「使えるように設定する」手順 を行う.

  3. bin 以外の任意のディレクトリから、
    1Hello
    
    と打ち込んで,作成したシェルスクリプトが動作することを確認する.



シェルスクリプトの基本機能

シェルスクリプトの基本機能を学ぼう.

0. 用語, tips 等

コメントアウト

# という文字から行末までがコメントとして取り扱われる(ただし,文字列中は除く).

文字列

文字列は quotation で囲んで表す.
' (シングルクォート) で囲むのと " (ダブルクォート) で囲むのでは以下のように性質が異なる.

文字列 解説
' (シングルクォート) で囲んだ文字列 単なる文字列扱い.
"(ダブルクォート) で囲んだ文字列 $ ` \ の3つの特殊文字をこれまで学んだ特殊な意味で扱う. よって、" (ダブルクォート)で囲まれた文字列の中で

1. 変数の参照は行える.
2. sh,bash の先読み $( ) は動作する
3. fish の先読み ( ) は動作しない

なお、*"(ダブルクォート)の囲み内部で上の3つの特殊文字を単なる文字として扱うには
\ を前につけて \ $ \` \\ とする.

変数

変数名は単なる文字列. 変数名の頭に $ をつけるとその変数の値となる(変数の参照と言う). 変数に値を設定するには以下の書き方を使う.

変数に値を代入(sh, bash の場合) 変数に値を代入(fish の場合)
変数名=値 set 変数名 値

  実習

先の Hello スクリプトを次のように、文字列と変数を使ったものに改修しよう.

sh, bash の場合:

1#!/bin/sh
2# Greeting Script
3g='Hello, world!'
4t=$(date +%m/%d)
5d="today is $t"
6echo $g, $d.

fish の場合:

1#!/usr/bin/fish
2# Greeting Script
3set g 'Hello, world!'
4set t (date +%m/%d)
5set d "today is $t"
6echo $g, $d.

そして,実行しよう.その出力と上の内容から、文字列と変数の働きを理解するとともに、先読み評価が動作するように配慮されていることにも注意しよう.

1. 引数

引数とは,プログラム起動時に一緒に与える文字列である. 例えば ls -a /tmp とプログラムを実行したら,通常は引数は /tmp の一つである( -a はオプション).
ただし、広い意味で引数は -a , /tmp の二つととらえることもある.

この引数は以下のように扱われる.

引数の参照(sh,bashの場合) (fish の場合) 意味
$n $argv[n] n 番目の引数.
$* $argv 引数全て.
$# (count $argv) 引数の個数
$0 (status filename) シェルスクリプト自身の名前

  実習

次の内容で Welcome というファイルを作り、コマンド化する.

sh, bash の場合:

1#!/bin/sh
2# Welcome Script
3
4name=$(whoami)
5g="($1 says) Thank you, $name,"
6h="and $2 laughed."
7echo $g $h

fish の場合:

1#!/usr/bin/fish
2# Welcome Script
3
4set name (whoami)
5set g "($argv[1] says) Thank you, $name,"
6set h "and $argv[2] laughed."
7echo $g $h

そして,このスクリプトを次のように実行してみよう.

1Welcome Ann David

その出力を読み、スクリプトの内容と比較して、引数と shift の働きを理解しよう.

2. 入出力 read/echo

シェルスクリプト自身で入出力を行うには,主に次のコマンドを用いる.

コマンド(sh, bash の場合) コマンド(fish の場合) 解説
echo "テキスト" (同左) 出力
echo -n "テキスト" (同左) 出力.ただし,出力後に改行しない.
read 変数名 (同左) 入力待ち.
ユーザからの(キーボード)入力を待ち,その結果を変数にいれる.
echo -n "テキスト"; read 変数名 read -P "テキスト" 変数名 説明付き入力待ち.

  実習

先の Hello スクリプトをさらに改修して次のようにしよう.

sh, bash の場合:

1#!/bin/sh
2# Greeting Script
3
4g='Hello'
5i='Please tell me your name: '
6o="Oh, nice to meet you"
7echo $g.
8echo -n $i; read ans
9echo $o, $ans.

fish の場合:

1#!/usr/bin/fish
2# Greeting Script
3
4set g 'Hello'
5set i 'Please tell me your name: '
6set o "Oh, nice to meet you"
7echo $g.
8read -P $i ans
9echo $o, $ans.

そして,このスクリプトを実行し、read/echo の働きを理解しよう.

3. 計算

シェルスクリプト中で計算が必要な場合は、複数の手段がある.主なものを以下に挙げる.

  どのシェルでも使える方法: bc -l コマンドに頼る

下の例のように、計算式を標準入力で bc -l に渡せば良い.なお、整数計算ならば bc -l ではなく単に bc としても問題ない.

1#!/bin/sh  
2echo $(echo "3 + 9" | bc)
3echo $(echo "1.3 + 2.5" | bc -l)
4a=$(echo "4*a(1.0)" | bc -l) ; echo $a
5echo $(echo "c($a)" | bc -l)

ちなみに, 4行目では $4 * \arctan(1.0) = \pi$ を、 5行目では $\cos(\pi) = -1.0$ を計算している.

  fish の場合: fish の機能 math を使う.

シェル fish には math という高度な計算機能が組み込まれている. fish はコマンド置換を単なる ( ) で行うので、下の例のように使うと良い.

1#!/usr/bin/fish  
2set a 5
3set b (math $a+10) ; echo $b
4set c (math $b\*2) ; echo $c 
5set d (math $c/7); echo $d  # 小数計算も可能.三角関数や対数なんかも計算できるよ.

上のスクリプト中の4行目の行だが,文字 * はシェルで展開機能を呼び出してしまうのでそれを防止するために \* として * をエスケープしている.

  実習

上のスクリプト 2つを試してみよう.ただし、fish がインストールされていない環境の場合は最初の 1つで良い.

4. シェルの関数

シェルでは,「複数のコマンドをまとめて名前をつけたもの」を関数として自分で作り、使える. 関数を作るには以下のようにする.

  sh, bash の場合:

1関数名() {
2  コマンド…
3}

という構文を用いる.

  fish の場合:

1function 関数名
2  コマンド…
3end

という構文を用いる.

また,この関数内部だけで「閉じた」変数を使いたければ、関数の内部で sh, bash ではlocal を頭部につけて変数宣言をすればよく、 fish では変数宣言の set コマンドに -l というオプションを付ければ良い.

詳しくは、以下の実習の内容を見ておこう.

  実習

  1. 下記のシェルスクリプトを実行し、宣言した関数 countdown の動作を理解しよう.

    sh, bash の場合:

     1#!/bin/sh
     2
     3countdown() {
     4  local i=$1
     5  while [ $i -ge 1 ]
     6    do
     7    echo $i
     8    i=$(echo "$i - 1" | bc)  # $(( )) という計算機能を使うと楽だが危険性もある.
     9    done
    10}
    11
    12countdown 100
    

    fish の場合:

     1#!/usr/bin/fish
     2
     3function countdown
     4    set -l i $argv[1]
     5
     6    while test $i -ge 1  # [ ] よりも test を使うほうが好まれるらしい
     7        echo $i
     8        set i (math $i - 1)
     9    end
    10end
    11
    12countdown 100
    

    なお,while コマンドの意味と文法はこのすぐ後で学ぶ.

  2. Hello スクリプトを次のように改修し,宣言した関数 minutes_since_month_start の動作を理解しよう.

    sh, bash の場合:

     1#!/bin/sh
     2# Greeting Script
     3
     4greeting='Hello'
     5
     6day=$(date +%d)
     7hour=$(date +%H)
     8minute=$(date +%M)
     9
    10minutes_since_month_start() {
    11    elapsed_days=$(echo "$1 - 1" | bc)
    12    echo "(($elapsed_days * 24 + $2) * 60 + $3)" | bc
    13}
    14
    15minutes=$(minutes_since_month_start "$day" "$hour" "$minute")
    16
    17echo "$greeting, about $minutes minutes have passed this month."
    

    fish の場合:

     1#!/usr/bin/fish
     2# Greeting Script
     3
     4set greeting 'Hello'
     5
     6set day (date +%d)
     7set hour (date +%H)
     8set minute (date +%M)
     9
    10function minutes_since_month_start -a day hour minute
    11    set elapsed_days (math "$day - 1")
    12    math "(($elapsed_days * 24 + $hour) * 60 + $minute)"
    13end
    14
    15set minutes (minutes_since_month_start $day $hour $minute)
    16
    17echo "$greeting, about $minutes minutes have passed this month."
    

5. シェルの制御構造

ある程度複雑な動作をさせるには、「もし…ならば」とか,「…を繰り返して…」という制御が必要になるだろう. それらについて説明しよう.

5a. if 文: 条件分岐

条件に応じて、スクリプト実行の流れを分岐させる. 具体的な構文は以下の通り.

sh, bash の場合:

 1if [ 条件1 ]
 2    then コマンド1 …
 3
 4    elif [ 条件2 ]
 5        then コマンド2 …
 6    elif [ 条件3 ]
 7        then コマンド3 …
 8    (以下, elifthen … を好きなだけ繰り返し)
 9
10    else コマンドN
11fi

fish の場合:

 1if test 条件1
 2        コマンド1 3
 4    else if test 条件2
 5        コマンド2 6    else if test 条件3
 7        コマンド3 8    (以下, else if … を好きなだけ繰り返し)
 9
10    else コマンドN
11end

条件 の書き方については,このすぐ後学ぶ. なお、elif, else 以下は存在しなくても良い.

そして、上の構文での流れは以下の通りになる.

  1. 最初に 条件1 が実行 & チェックされ,
  2. 条件1 が成り立つ(= 実行が成功する) → コマンド1… が実行されて if 文は終了する.
  3. 条件1 が成り立たない → 次の elif/else if へ.
    そこで条件2 がチェックされ,
    i. 条件2 が成り立つ → コマンド2… が実行されて if 文は終了する.
    ii. 条件2 が成り立たない → 次の elif/else if へ.
    (… 以下,繰り返し …)
  4. else に来た場合.無条件で コマンドN が実行されて if 文は終了する.

  実習

次のような内容のスクリプトを作り,実行し,if 文に関する動作を理解しよう. ただし,if のすぐ後の [ -e $fn ] / test -e $fn という部分については後述するので理解は後回しでよい.

sh, bash の場合:

 1#!/bin/sh
 2
 3fn=~/.bashrc
 4
 5if [ -e $fn ]
 6  then
 7    cat $fn
 8  else
 9    echo "You has no $fn."
10fi

fish の場合:

1#!/usr/bin/fish
2
3set fn ~/.bashrc
4
5if test -e $fn
6    cat $fn
7else
8    echo "You has no $fn."
9end

5b. while文, until文: 条件チェックつきでの繰り返し.

作業を繰り返すが、その繰り返しを止める条件をループを回すたびにチェックする仕組みだ.

まず、while 文の構文と動作の流れは以下の通りである.

sh, bash の場合:

1while [ 条件 ]
2do
3  コマンド…
4done

fish の場合:

1while test 条件
2  コマンド3end

なお、until の構文も同じ. 動作は以下の様な感じだ.

  1. 条件が成り立つかチェックする.
  2. while の場合: 条件が成り立つうちは,
    until の場合: 条件が成り立っていないうちは,
    コマンド… を実行してから 1. へ戻る.以下,繰り返し.

  実習

次のような内容のスクリプトを作って実行し,while 文の動作を理解しよう. ただし,while のすぐ後の [ $x -le 10 ] / test $x -le 10 という部分については後述するので理解は後回しでよい.

sh, bash の場合:

 1#!/bin/sh
 2
 3x=0
 4
 5while [ $x -le 10 ]
 6do
 7  v=$(echo "$x * 0.31416" | bc -l)
 8  echo "sin($v) = "$(echo "s($v)" | bc -l)
 9  x=$(echo "$x + 1" | bc)
10done

fish の場合:

1#!/usr/bin/fish
2
3set x 0
4
5while test $x -le 10
6    set v (math "$x * 0.31416")
7    echo "sin($v) = "(math "sin($v)")
8    set x (math "$x + 1")
9end

5c. break文、exit文: ループ脱出, スクリプト終了

  • break コマンドが実行されると,forwhile, until のループの中から脱出する.
  • exit コマンドが実行されると,その時点でスクリプト実行が終了する.

5d-1. 条件チェックコマンドとその形式

これまで何回か出てきた条件式について述べよう.

まず、条件のチェックコマンドの部分だが、以下の 2つの形式のどちらかで書かれる. 実は形式が異なるだけで動作は同じなのでどちらの形を使っても良い.

sh, bash でよく見る形式:

1 [ 条件式 ]
2    # [ の前後と ] の前には必ずスペース文字を入れる.
3    # これらのスペースがないとエラーになるので注意しよう.

fish でよく見る形式:

1test 条件式

5d-2. 条件式

条件チェックの際の中身の条件式は、基本的に、

  • 2つのなにかの関係を調べる
  • 1つのなにかを調べる

のいずれかである.以下で詳しくみてみよう.

● ファイルに関する条件式

そのファイルが存在する場合だけ処理したい、というような時は以下の条件式を上手く使えば良い. 他にも条件式はあるが、使用頻度は低いと思われるので補足に記載している.

条件式 意味
-e 名前 その名前のファイルが存在するならば真.
-d 名前 その名前のディレクトリが存在するならば真.

● 数値に関する条件式

条件式の判定には整数しか使えない. 気をつけよう.

条件式 意味
数値1 -eq 数値2 数値が等しければ真.
数値1 -le 数値2 数値1 ≦ 数値2 ならば真.

● 文字列に関する条件式

条件式 意味
文字列1 == 文字列2 文字列が等しければ真.
「=」を2回書くことに注意.

● その他の条件式

条件式 意味
! 条件式 ''NOT''を表す.
条件式が成り立たなければ真.
条件式1 -a 条件式2 ''AND''を表す.
両方の条件式が成り立てば真.
条件式1 -o 条件式2 ''OR''を表す.
どちらかの条件式が成り立てば真.

  実習

次のような内容のスクリプトを作る. このスクリプトは、スクリプトを実行する際に与えた引数に関して

  • 引数が1つだけか
  • その引数がファイル名であるとして、そのファイルが存在するか
  • 上の2条件が両立するか

をチェックし、答えを返すものだ.

実行し、引数の個数を 1ではないものに変えたりして動作がどうなるか確認しよう. そして、スクリプトの中身を読んで、条件式がどのように使われているか理解しよう.

sh, bash の場合:

1#!/bin/sh
2# 引数が1個で、かつ、そのファイルが存在するかを調べる
3
4if [ "$#" -eq 1 -a -e "$1" ]
5then
6    echo "$1 exists."
7else
8    echo "Please give exactly one existing file."
9fi

fish の場合:

1#!/usr/bin/fish
2# 引数が1個で、かつ、そのファイルが存在するかを調べる
3
4if test (count $argv) -eq 1 -a -e "$argv[1]"
5    echo "$argv[1] exists."
6else
7    echo "Please give exactly one existing file."
8end

発展・補足編

基本編の内容に対する、発展・補足的な内容を以下に述べる.
学習初期においては無視しても良い内容として、授業時も解説を省略する予定である.

なるべく頻繁にシステムの更新をしておこう!

いまどきの OS は,セキュリティ対策もあって常に細かいアップデートを続けている.

そこで,たとえば Ubuntu を使っている場合は授業に取り組む前に下記のようにしてシステムのアップデートをしておくとよいだろう.

1sudo apt update
2sudo apt upgrade
3sudo reboot

  ちなみに,Ubuntu の場合,OS そのもののアップデート(バージョン番号が変わるやつ)もコマンドを打ち込むことで可能だ.

まずは今使っているバージョンを、/etc/os-release ファイルを読むことで確認しよう.

1less /etc/os-release

最新のバージョンは、ubuntu Japanese Team を見れば右のニュース欄から把握できるぞ.

そして、使っているものが少し古いので新しいバージョンにしたいな,と思う時に次のようにすれば良い. ただしそこそこ時間はかかるので,時間的余裕のある時に行おう.

1sudo apt update ← 念の為.
2sudo apt upgrade ← 念の為.
3sudo reboot ← 念の為.
4sudo do-release-upgrade

  シバンのオプションによる動作変更

スクリプトの内容を実行しないで,文法チェックだけ行いたい場合は,

1#!/bin/sh -n

と先頭行に書けばよい. ファイルの削除など,やや危険な作業を行うスクリプトはこれでチェックすると良いだろう.

実行時にスクリプトの内容も表示したい場合は,

1#!/bin/sh -v

と先頭行に書けばよい. スクリプトの動作が望みのものと違う時はこれでチェックするのも良いだろう.

学習をサポート! シェルスクリプトを分析してくれるサイト, ソフト

自分でスクリプトを書いていると、うまく動作しない、バグが有る、というようなときに困ることも有るだろう. そうしたときに、下記のようなものを頼ると良い.

  sh/bash スクリプトを入力すると分析してくれるソフト shellcheck  

これはコマンドで、

1sudo apt install shellcheck

とするとインストールできる. 素で使うならば、

1shellcheck 検査したいシェルスクリプトファイル名

とすれば良い. エラーだけでなく、「ここはこうした方が新しい書き方だ」等々の指摘もしてくれる.

実行例 1: shellcheckを使ってみる

次のようにして、簡単なシェルスクリプトを検査してみよう.

1echo "ls -lga | sort | less" | shellcheck -
2  # 1. ls... という1行だけが書いてあるシェルを検査した、ということになる
3  # 2. shellcheck に "-" というファイル名を渡すと、標準入力を読み込んでくれる

とすると、次のような出力が得られる.

1In - line 1:
2ls -lg | sort | less
3^-- SC2148 (error): Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.
4^----^ SC2012 (info): Use find instead of ls to better handle non-alphanumeric filenames.
5
6For more information:
7  https://www.shellcheck.net/wiki/SC2148 -- Tips depend on target shell and y...
8  https://www.shellcheck.net/wiki/SC2012 -- Use find instead of ls to better ...

念の為に直訳すると、次のようになる.

11行目:
2ls -lg | sort | less
3^-- SC2148 (エラー): shellcheck が示す情報はこのスクリプトを実行するシェルに依存しますが、そのシェルが不明です。シバンまたは "shell"ディレクティブを追加してください。
4
5^----^ SC2012 (情報): 英数字以外のファイル名をより適切に処理するには、ls の代わりに find を使用してください。
6
7詳細については、以下を参照してください。
8https://www.shellcheck.net/wiki/SC2148 -- shellcheck が示す情報はこのスクリプトを実行するシェルに依存しますが、そのシェルが不明です
9https://www.shellcheck.net/wiki/SC2012 -- 英数字以外のファイル名をより適切に処理するには、ls の代わりに find を使用してください。

出力を読むと、ずいぶんと親切なコマンドであることがわかる. 大いに頼りになりそうだ.

  shellcheck を動かしてくれる webサイト   ShellCheck

shellcheck コマンドを動かしてくれるサービス ShellCheck がある.ここで試してみるのも良い. ShellCheck example
上図は ShellCheck web を使ってみた様子だ. もちろん、コマンドラインで動かしたときと同じ出力だが、読みやすい.

  Emacs で動作する flycheck パッケージ  
( flycheck-checkbashisms など )

エディタ Emacs に組み込む文法チェッカーだ.いろいろな言語のチェックができる.

flycheck example

上図は Windows 上の Emacs でシェルスクリプトファイルに flycheck を使ってみた様子だ. 使い方だが、flycheck がインストール1 されている Emacs で以下のように操作すればよい.

  1. Emacs でそのファイルを読み込む.

  2. Emacs で M-x flycheck-mode として、文法チェックモードに入る.

  3. あとは flycheck の機能を用途に応じて使う.
    次の3つが使えれば十分だろう.
    1. M-x flycheck-list-erros : エラー等の出力一覧を見る.
    2. M-x flycheck-next-error : 次のエラー位置へカーソルを移動.
    3. M-x flycheck-previous-error : 前のエラー位置へカーソルを移動.

  VS Code で動作する言語文法チェック用の各種拡張機能  

広く使われているエディタ VS Code にももちろんこうした拡張機能がある.

VS Codeでその言語のファイル(例: .py, .jl, .cpp)を開くと、画面右下に「〜〜用の推奨拡張機能をインストールしますか?」というポップアップが表示されるので、その指示に従ってインストールするだけで、自動的に強力な文法チェックが有効になる.



  上記の支援機能は基本的に sh/bash 用で、fish スクリプトの支援機能については以下のとおりだ.

  • fish -n として -n オプションを付けてスクリプトを実行すると、fish はスクリプトを実行せずに文法チェックだけしてくれる.

  • shellcheck のような fish 用支援コマンドは無い.

  • VS Code には fish専用の支援機能が(何通りか)ある.使いたい人は調べてみよう.

  • Emacs の flycheck には fish対応はない.ただし fish -n 機能を使った emacs lisp を書いて対応させることはできる .下記のような emacs lisp を emacs の設定ファイルに書くことになるだろう.
 1;; fish 用 Flycheck checker
 2;;
 3;; 前提:
 4;;   - fish は /usr/bin/fish にある
 5;;   - fish-mode を使って .fish ファイルを開く
 6;;   - flycheck がインストールされている
 7
 8(with-eval-after-load 'flycheck
 9  (flycheck-define-checker fish-fish
10    "A fish syntax checker using fish -n."
11    :command ("/usr/bin/fish" "-n" source)
12    :error-patterns
13    ((error line-start
14            (file-name) " (line " line "): " (message)
15            line-end))
16    :modes fish-mode)
17
18  (add-to-list 'flycheck-checkers 'fish-fish))

コマンドの連続実行

コマンドの連続実行等について 連続実行,grouping の部分で学習したが,あらためてここで少し解説しよう.

文法 解説
コマンド1 ; コマンド2; コマンド3 コマンド1 を実行後,コマンド2 を, さらにコマンド3 を… 実行する.
( コマンド1 ; コマンド2 ) 上とほぼ同様なのだが,
( ) の中のコマンドを「サブシェル」のもとで実行する(グループ化: 通常のシェルの場合).
サブシェルで行った環境変更はもとのシェルに影響しない.
よって,状態を一時的に変更して作業するときに便利.
コマンド1 && コマンド2 AND 実行.
コマンド1 を実行してみて,コマンド1 が成功したならばコマンド2 を実行する.
コマンド1 ¦¦ コマンド2 OR 実行.
コマンド1 を実行してみて,コマンド1 が失敗したならばコマンド2 を実行する.

グループ化の例:
例えば,

1 (cd /tmp; ls -a) 

とすると,サブシェルは /tmp ディレクトリに移動して作業を行うが,作業が終わってサブシェルが終わり、戻ってきた元のシェルでのカレントディレクトリはもとのまま、である. こうしておけば、一時的な作業に伴う副作用が少なく、勘違いが減って間違いも経るだろう.

グループ化の例 2:
グループ化したコマンドの標準出力等は 合わさって出てくる ので、これが便利なことも多い. 例えば,

1 (\ls /tmp; \ls /var) | less 

とすると,/tmp/var の2つのディレクトリにあるファイルのリストを一度に閲覧できる.


シェルの変数 補足

変数の設定: シェル変数 で学んだように、

シェルは変数を持つことが出来る. そしてもちろん、シェルスクリプトの中でもこうした変数が使える.

注1: 変数設定のとき,変数名と「=」の間にスペースを入れると失敗するぞ.
注2: 変数を参照するときは ${変数名} ({} で囲む)とした方が,より安全だぞ.

引数 補足: 引数を処理する shift コマンド

shift というコマンドを実行すると,一つ目の引数の内容が消去され,二つ目以降が前にずれる. 言い換えると, $1$2 の内容が入り, $2$3 の内容が入り… と いった動作になる. また,この結果としてオプションの数 $# は 1 減る.

4. シェルの計算機能 補足

  sh, bash の場合: 計算機能 $(( )) を使う.
ただし整数計算しかできないし、潜在的にまずい動作があるので避けたほうが良い..

機能としては、整数の計算式を $(()) で囲むとシェルによって計算されて結果に置き換わる. 下記の例を見よう.

1#!/bin/sh
2a=5
3b=$((a+10)) ; echo $b
4c=$((b*2))  ; echo $c 

  この計算機能は数字の先頭に 0 がついていると 8進数表記と解釈するのでトラブルのもとだ. たとえば、

1echo "$(( 012 + 3 ))"

とすると、計算式中の 012 が 8進数表記であると解釈され、(われわれが普段使う) 10進数での 10 として扱われてしまう. そのため、これを実行すると 13 が返ってくる. 大変に気づきにくいバグのもとなので、この機能はなるべく使わないようにしよう.

6. シェルの制御構文 補足

''case'' … (パターンマッチ)条件分岐

ある「文字列」が用意したパターンとマッチするかどうかで分岐する. 単純な列挙の場合分けに便利. パターンは ¦ で区切って並列表記(or を意味する)することもできる.

具体的な構は文以下の通り.

1case 文字列 in
2  パターン1) コマンド1… ;;
3  パターン2) コマンド2… ;;
4
5  ( 以下, パターンk) コマンドk…;;  を好きなだけ繰り返し )
6
7  *) コマンドN…
8esac

注: *) は「なににでもマッチする」.なお、この指定はしなくても良い.

流れは以下の通り.

  1. 最初に 文字列 を評価して,
  2. 文字列がパターン1 とマッチする → コマンド1… が実行されて case 文は終了する.
  3. マッチしない → 次のパターンとマッチするか (以下繰り返し)
  4. *) に来た場合.無条件で コマンドN が実行されて case 文は終了する.

  実習

次のような内容のスクリプトを作り,実行し,理解しよう.

 1#!/bin/sh
 2
 3echo -n "Please input file name: "
 4read fn
 5
 6case $fn in
 7  *.txt )
 8    echo "$fn is text file.";;
 9  *.c )
10    echo "$fn is C program file.";;
11  * )
12    echo "Hey, what is $fn ? You know it?";;
13esac

''for'' … あてはまる変数の繰り返し

パターンやリストの中のものを列挙してなにかしたいとか、単純に繰り返すというときは for を使うと良い. ただし,Ubuntu などの dash (= /bin/sh)ではこの機能は使えない.この機能を使う場合は /usr/bin/bash などを指名しよう.

具体的な構文は

1for 変数 in パターンorリスト
2do
3  コマンド…
4done

という感じで、動作としては以下のようになる.

  1. まず,in に続くパターンorリストを展開して,リストを生成する.
  2. そのリストの要素を順番に in の前の 変数 に代入して,そのたびに dodone の間のコマンド… を実行する.

  実習

次のような内容のスクリプトを作り,実行し,理解せよ.

1#!/usr/bin/bash
2
3for x in {0..10}
4do
5  v=`echo "$x * 0.31415" | bc -l`
6  echo "sin($v) = " `echo "s($v)" | bc -l`.
7done

6d-2. 条件式

● ファイルに関する条件式 補足

条件式 意味
-f 名前 その名前の通常ファイルが存在するならば真.
-r 名前 その名前の読み込み可能なファイルが存在するならば真.
-w 名前 その名前の書き込み可能なファイルが存在するならば真.
-x 名前 その名前の実行可能なファイルが存在するならば真.
-s 名前 その名前のサイズが 0 より大きなファイルが存在するならば真.

● 文字列に関する条件式

条件式 意味
-z 文字列 文字列の長さがゼロならば真.
-n 文字列 文字列の長さがゼロでなければ真.
文字列1 != 文字列2 文字列が異なれば真.

● 数値に関する条件式

ちなみに、条件式の判定には整数しか使えない. 注意しよう.

条件式 意味
数値1 -ne 数値2 数値が異なれば真.
数値1 -gt 数値2 数値1 > 数値2 ならば真.
数値1 -ge 数値2 数値1 ≧ 数値2 ならば真.
数値1 -lt 数値2 数値1 < 数値2 ならば真.

● その他の条件式

条件式 意味
\( 条件式 \) 条件式をグループ化する.

例: ( 条件1 and 条件2 ) or 条件3 という条件を上のグループ化で扱いたければ、

1\( 条件1 -a 条件2 \) -o 条件3

と書くことになる.


  1. flycheck のシェルスクリプト用もろもろのインストール手順は,まず、checkbashisms というプログラムを OS にインストールする.Windows の場合は cygwin に入れるのが楽かな.次に flycheck, flycheck-bashisms package を emacs に入れる.あとは emacs の設定ファイルに適切に書き込めば良く、それについては flycheck-bashisms のweb に書いてある. ↩︎

レポート No.7

  注意

近年はセキュリティ上の懸念から,実行形式のプログラムなどをメールに添付するとそのメールそのものの受信を受信側サーバが拒絶したりする. そういうことを避けるため,レポートをファイルで提出するときはそういった懸念のあるファイル形式のものではないようにしよう.

まあ要するに,レポートは pdf ファイルにして送るのが良い ということだと思っておこう.

以下の課題について、自らの将来のスキルアップに繋がるように調査と考察を行い,
     学籍番号-氏名-07.pdf
というファイルとしてレポートを作成し、 webフォーム から教官宛に提出しよう.

なお,レポートを $\TeX$ 等で作成したものを印刷した「紙媒体」を教官に直接手渡す形で提出してもよいが、物質によるレポート提出は常に破損や紛失の可能性があるのであまりお勧めはしないぞ.

課題

  1. 引数1 に「金額」を,引数2 に「日数」を入れると, その金額でその日数だけお金を借りたら最終的にいくらになるかを計算するシェルスクリプトを作成してみよう.
    ただし,金利は 10日で 1割の複利としておこう.
    ちなみに,read コマンドを使わずにシェルスクリプトの引数をきちんと処理する方法が望ましい.

  2. ファイルを消去するシェルスクリプトを作成してみよう.
    ただし,次のように「バックアップを用意する」ような動作をするようにしてみよう.

    1. 実行時にはまず、ファイルのバックアップファイルがあるかどうか調べ,
      • 無い場合は作成する.
      • 有るが、その数が3つ以下の場合は新たにバックアップファイルを作成する.
      • 有り、かつ、その数が3つより多い場合は、一番古いバックアップファイルを消去し、新たにバックアップファイルを作成する.

    2. 以上のバックアップ作業を行った後,ファイルを消去する.

  3. 普段使っているエディタに、flycheck のようなプログラミング言語文法チェック機能がないか調べてみよう. 特定のエディタを使っていないという人は、今おそらく一番広く使われているエディタである Visual Studio Code について調べてみよう.

  4. これまでの授業課題でシェルスクリプトを利用して解決できる課題がないか考えよう.もしもあるならば実際にやってみよう.