1 から 1024 の数字を順番に表示する
1 2 3 ... 1023 1024
$ seq 1 1024
3 から 6 の数字を順番に表示する
3 4 5 6
n + 2
n=0 while [ $n -lt 4 ]; do n=$(( n + 1 )) a=$(( n + 2 )) echo $a done
シェルスクリプトにおいて = の意味は,右辺の値を左辺に代入する,である(「n = n + 1 は間違ってる」とか言わないように).
y=$(( x + 1 ))
n=1 y=3 echo $y while [ $n -lt 4 ]; do n=$(( n + 1 )) x=$y y=$(( x + 1 )) echo $y done
n=1 y=3 echo $y while [ $n -lt 4 ]; do n=$(( n + 1 )) y=$(( y + 1 )) echo $y done
5 から 2 の数字を順番に表示する
5 4 3 2
一般項と漸化式,どっちを使ってもよい. どっちを使ってもできるなら,一般項を使う場合と漸化式を使う場合の2通りで書いてみるのが,上達の近道でしょう.
数列
11 14 17 ... 98
数列の和を計算する
n=0 while [ $n -lt 10 ]; do n=$(( n + 1 )) a=$(( 2 * n )) done
n=0 s=0 while [ $n -lt 10 ]; do n=$(( n + 1 )) a=$(( 2 * n )) s=$(( s + a )) done echo $s
数列の和を計算する
九九の表を作るスクリプトを書く.
以下のような出力ができたらよい.
1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
とりあえず,列が揃っていなくてもよい(ちょっとした小技を知っていると列を揃えるのは簡単であるが,小技を知らずに列を揃えるのは面倒だと思う).
echo Hello echo World
改行しないようにするにはオプション -n をつける
echo -n Hello echo -n World
echo ' '
1 1 1 2 1 3 2 1 2 2 2 3
と出力するスクリプトを書き換えることを考える
#/bin/bash i=0 while [ $i -lt 2 ]; do i=$(( i + 1 )) j=0 while [ $j -lt 3 ]; do j=$(( j + 1 )) k=$(( i * j )) echo $k done done
#/bin/bash i=0 while [ $i -lt 2 ]; do i=$(( i + 1 ) j=0 while [ $j -lt 3 ]; do j=$(( j + 1 )) k=$(( i * j )) echo -n $k done echo done
#/bin/bash i=0 while [ $i -lt 2 ]; do i=$(( i + 1 ) j=0 while [ $j -lt 3 ]; do j=$(( j + 1 )) k=$(( i * j )) echo -n ' ' echo -n $k done echo done
Fibonacci sequence (フィボナッチ数)
\[ F_{0} = 0, \ F_{1} = 1 \] \[ F_{n+2} = F_{n} + F_{n+1} \]
c=$(( a + b ))
n=0 c=0 echo $c n=$(( n + 1 )) b=$c c=1 echo $c n=$(( n + 1 )) a=$b b=$c c=$(( a + b )) echo $c n=$(( n + 1 )) a=$b b=$c c=$(( a + b )) echo $c n=$(( n + 1 )) a=$b b=$c c=$(( a + b )) echo $c
n=0 c=0 echo $c n=$(( n + 1 )) b=$c c=1 echo $c while [ $n -lt 10 ]; do n=$(( n + 1 )) a=$b b=$c c=$(( a + b )) echo $c done
トリボナッチ数 \[ T_{0} = T_{1} = 0, \ T_{2} = 1 \] \[ T_{n+3} = T_{n} + T_{n+1} + T_{n+2} \]
0, 0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149, 274, ...
テトラナッチ数 \[ T_{0} = T_{1} = T_{2} = 0, \ T_{3} = 1 \] \[ T_{n+4} = T_{n} + T_{n+1} + T_{n+2} + T_{n+3} \]
0, 0, 0, 1, 1, 2, 4, 8, 15, 29, 56, 108, 208, 401, ...
Fibonacci sequence (フィボナッチ数)
$(( x + y ))
から
$( echo "$x + $y" | bc )
に変更してみる.
計算機内部で数値は2進数を使って表される. 8桁の2進数は0または1を8つ並べたもので(例えば,00000000とか10101010とか),2の8乗すなわち256通りの数値を表すことができる. 8桁の2進数を10進数にすると,00000000は0,00000001は1,・・・11111111は255になる. 各8桁の2進数はそのまま10進数に対応するものとするなら,8桁の2進数で0から255までの整数を表すことになる.
ここで,正の数だけでなく負の数も含めて8桁の2進数で表す場合を考える. 計算機内部にあるのは0と1だけで,マイナスの符号はない. 0と1だけで負の数を表すにおいては,2の補数表現というものが使われる. 2の補数表現では,最上位の桁が符号を表し,最上位が0である00000000から01111111は正の数,最上位が1である10000000から11111111は負の数を表す. 正の数については,そのまま2進数を10進数に読み替えた数を表すものとし,00000000から01111111は0から127までの整数となる. 一方,負の数は,2の補数表現を使うとき,10000000は-128,10000001は-127,・・・11111111は-1を表す. ちょっと不思議な感じに見えるかもしれないが,これはこれで使い勝手がよかったりする. 例えば -128 + 1 は,2の補数表現を用いた場合 10000000 + 00000001 = 10000001 となり,たしかに-127となる. 同様に -1 + 1 は,11111111 + 00000001 = 100000000 となるが,数値を8桁で表すことにしている場合,桁上がりで生じた9桁目が消えて,00000000 となり,めでたく0が得られる.
ここまできたら,フィボナッチ数の計算結果に負の数が出てきた理由もわかったのではないかと思う. フィボナッチ数は正の数と正の数を足し算しているだけなので,負の数は絶対に出てこない. にも関わらず負の数が出てきたのは,計算機の内部で2の補数表現が使われていたからである. 数が小さくて最上位の桁に影響が出ないうちは問題ないのだが,数が大きくなって最上位の桁が1になると,負の数になってしまうのである. 上にあげた8桁の場合で言うと,127 + 1 が 01111111 + 00000001 = 10000000 で-128になってしまう,ということである. もともと,8桁では-128から127までしか扱えないところ,扱える範囲の外にある数が出てきてしまったのがいけない. 計算してはいけない範囲の計算をさせたのが悪いのである.
扱える範囲がどこまでであるのかは,処理系や使っているコマンドに依存するため,計算してみないとわからない場合もあったりする. 計算機は何も考えずにただ計算して結果を返すだけなので,計算したら必ず結果を見てその妥当性を評価しなければならない.
/mnt/raid/skymonitor/pub 以下に保存されている,スカイモニターで撮影された画像の枚数を数える
20161014 255 20161015 1092 20161016 1093 20161017 1362 20161107 351 20161108 426 ...
#!/bin/bash days=$( ls /mnt/raid/skymonitor/pub/2016/10 ) for yyyymmdd in $days ; do echo $yyyymmdd done
実行したら
20161014 20161015 20161016 20161017
#!/bin/bash days=$( ls /mnt/raid/skymonitor/pub/2016/10 ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/2016/10/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done
実行したら
20161014 255 20161015 1092 20161016 1093 20161017 1362
yyyy=2016 mm=10 days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done
#!/bin/bash yyyy=2016 months=$( ls /mnt/raid/skymonitor/pub/${yyyy} ) for mm in $months ; do days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done done
#!/bin/bash years=$( seq 2016 2021 ) for yyyy in $years ; do months=$( ls /mnt/raid/skymonitor/pub/${yyyy} ) for mm in $months ; do days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done done done
ls: cannot access '/mnt/raid/skymonitor/pub/2019/07/20190705/*.jpg': No such file or directory
のようなメッセージが吐き出されたかもしれない.メッセージをみたらだいたいわかると思うが,指定したファイルは存在しないと言われている.ディレクトリはあるけどファイルがない,という場合があるということである.
num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg 2>/dev/null | wc -l )
2016 21682 2017 499525 2018 428194 2019 377719 2020 520706 2021 523357 2022 391684
num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/*/*/*.jpg | wc -l )
のようなことをしたかもしれない.理屈は正しくて,これでうまくいくべきなのであるが,実際には
Argument list too long
のようなメッセージが表示されたりして,うまくいかない.メッセージを見たら察しはつくと思うが,これはファイルの数が多すぎてダメと言われている.それくらいなんとかしてくれと思わなくもないが,ダメなものはダメなので,違う方法(小分けにして溢れないようにして,足し算する)を考えてください.
次のスクリプトは2016年10月の各日の枚数を数える.
#!/bin/bash yyyy=2016 mm=10 days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done
各日の枚数を足して,2016年10月の総枚数を数えるように書き換えてみる.
#!/bin/bash yyyy=2016 mm=10 numnum=0 days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) numunm=$(( numnum + num )) done echo $yyyy $mm $numnum
月の総枚数を格納する変数として numnum を使う. 最初に numnum を 0 にセットして,各日の枚数を足していく. 数え終わったら最後に出力する. 出力は for ループの外に出す(そうしないと日毎に出力されてしまう).
#!/bin/bash yyyy=2016 months=$( ls /mnt/raid/skymonitor/pub/${yyyy} ) for mm in $months ; do days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) echo $yyyymmdd $num done done
やり方は同じで,各日の枚数を足して,2016年の総枚数を数えるように書き換える.
#!/bin/bash yyyy=2016 numnum=0 months=$( ls /mnt/raid/skymonitor/pub/${yyyy} ) for mm in $months ; do days=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm} ) for yyyymmdd in $days ; do num=$( ls /mnt/raid/skymonitor/pub/${yyyy}/${mm}/${yyyymmdd}/*.jpg | wc -l ) numnum=$(( numnum + num )) done done echo $yyyy $numnum
01 253748 02 240542 03 234113 04 216844 05 193093 06 215539 07 245669 08 265116 09 232400 10 222150 11 208600 12 235053
00 115520 01 115316 02 115287 03 115189 04 115201 05 115182 06 115101 07 114881 08 114832 09 114951 10 115016 11 114850 12 114773 13 114538 14 114418 15 114283 16 114377 17 114931 18 115296 19 115806 20 115965 21 115828 22 115743 23 115583