[ 岡山大学 | 理学部 | 地球科学科 | 地球および惑星大気科学研究室 ]

大気科学演習1

シェルスクリプト

練習問題1ー1

1 から 1024 の数字を順番に表示する

1
2
3
...
1023
1024

ヒント

$ seq 1 1024

練習問題1ー1a

3 から 6 の数字を順番に表示する

3
4
5
6

ヒント1

n + 2
n=0
while [ $n -lt 4 ]; do
  n=$(( n + 1 ))
  a=$(( n + 2 ))
  echo $a
done

シェルスクリプトにおいて = の意味は,右辺の値を左辺に代入する,である(「n = n + 1 は間違ってる」とか言わないように).

ヒント2

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

練習問題1ー1b

5 から 2 の数字を順番に表示する

5
4
3
2

ヒント1

ヒント2

一般項と漸化式,どっちを使ってもよい. どっちを使ってもできるなら,一般項を使う場合と漸化式を使う場合の2通りで書いてみるのが,上達の近道でしょう.

練習問題1ー1c

数列

11
14
17
...
98


練習問題1ー2

数列の和を計算する

ヒント1

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

ヒント2

練習問題1ー2a

数列の和を計算する

答え合わせ


練習問題1ー3

九九の表を作るスクリプトを書く.

以下のような出力ができたらよい.

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

とりあえず,列が揃っていなくてもよい(ちょっとした小技を知っていると列を揃えるのは簡単であるが,小技を知らずに列を揃えるのは面倒だと思う).

ヒント1

echo Hello
echo World

改行しないようにするにはオプション -n をつける

echo -n Hello
echo -n World
echo ' '

ヒント2

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


練習問題1ー4

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

練習問題1ー4a

トリボナッチ数 \[ 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, ...

#練習問題1ー4b

テトラナッチ数 \[ 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, ...

#練習問題1ー4c

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までしか扱えないところ,扱える範囲の外にある数が出てきてしまったのがいけない. 計算してはいけない範囲の計算をさせたのが悪いのである.

扱える範囲がどこまでであるのかは,処理系や使っているコマンドに依存するため,計算してみないとわからない場合もあったりする. 計算機は何も考えずにただ計算して結果を返すだけなので,計算したら必ず結果を見てその妥当性を評価しなければならない.


練習問題1ー5

/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 )

練習問題1ー5a

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

のようなメッセージが表示されたりして,うまくいかない.メッセージを見たら察しはつくと思うが,これはファイルの数が多すぎてダメと言われている.それくらいなんとかしてくれと思わなくもないが,ダメなものはダメなので,違う方法(小分けにして溢れないようにして,足し算する)を考えてください.

ヒント2

次のスクリプトは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 ループの外に出す(そうしないと日毎に出力されてしまう).

ヒント3

#!/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

#練習問題1ー5b

01 253748
02 240542
03 234113
04 216844
05 193093
06 215539
07 245669
08 265116
09 232400
10 222150
11 208600
12 235053

#練習問題1ー5c

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




Last Updated: 2022/10/29, Since: 2019/10/24.
This page is generated by Makefile.rd2html.