【アルゴリズム 配列準備編】ざっくりわかるシェルスクリプト7

Bashスクリプトで配列の準備

この章では、Bashスクリプトでいくつかのソートアルゴリズムを実行するための、元となるテンプレートを配列で作成します。
他の言語への移植も視野に入れて、言語に特化したライブラリや構文はあまり使わずに、基本的な構文で記述し、アルゴリズムにフォーカスして表現します。
ですので、「もっと速く」「もっと短くコンパクトなソースで」というよりも、「冗長でもわかりやすく」を理念に書いていきます。
この章で使うプログラムソースは以下のディレクトリの01Array.sh を参照してください。

Bashプログラミングによるアルゴリズム 01Array.sh

ファイルの作成

まずは、ファイルを作成します。

$ :> 01Array.sh

シェバンの指定

ファイルを開いてファイルの冒頭一行目にshe-ban(シェバン)を入力します。
この行は、$ which bash で得られる bash コマンドのPATHを入力します。
2行目は空行としておき、3行目からプログラムを書き始めます。

#!/usr/bin/bash 

関数の呼び出し

ではさっそく関数名を決めて入力します。
ここでは execArray という関数名とします。
timeコマンドは、実行したプログラムの処理時間を計測するコマンドです。
exitコマンドは、プログラムの終了を意味します。
「#」で始まる行はコメント行です。「#」以降は無視されますので、プログラムの説明などを書くと良いでしょう。
「10」という数字は、10個の配列を作成するという意味となります。
半角数字で入力してください。
ちまたではどのくらい楽になるのかは不明ですが、行末のセミコロンを省略する人も多いようです。複数行を縮めて表示したいときなどに、適宜、一行にまとめて書く場合のために、行末にはセミコロンを入れます。
どのプログラミング言語にも言えることですが、プログラムはソースの下から上へ向かって積み上げるように追記していきます。

ここでは、timeコマンドを使って処理速度を計測しつつ、execArray関数を、10という数字をパラメータで渡して呼び出しています。
呼び出しているだけで、execArray()関数はまだ書いていませんね笑

#!/usr/bin/bash

##
#
time execArray 10;
exit;

実行結果は以下のとおりです。
bashコマンドの後ろに半角スペースを入れて実行ファイル名を入力します。

bash-5.1$ bash 01Array.sh
test: 行 5: execArray: コマンドが見つかりません

real	0m0.002s
user	0m0.001s
sys	0m0.001s
bash-5.1$

エラーが表示されていますが、ソースに間違いがあるわけではありません。
計測数値が表示されていますね。

項目 意味
real プログラムの呼び出しから終了までにかかった実時間(秒)
user プログラム自体の処理時間(秒)(ユーザCPU時間)
sys プログラムを処理するために、OSが処理をした時間(秒)(システム時間)

execArray関数をまだ書いていませんから、呼び出した関数が見つからないというエラーとなります。
では、エラーが出ないように、execArray関数を書いていきましょう。

execArray()関数の記述

##
# <>execArray()
#
function execArray(){
  setArray $1;
  display;
}
##
#
time execArray 10;
exit;

関数名はfunctionを関数名の前に記入します。
これにより、execArray関数が関数であることが明示的にわかります。
functionを記入することすらも省略可能ではありますが、きちんと記入することで間違いを防ぐことができます。
関数名の後ろには 「()」が付きます。
関数は「中括弧{}」でくくられます。
関数の内容は「中括弧{}」の中に書きます。

execArray()の中身は2つの関数を呼び出しています。
さきほども説明したとおり、execArrayを呼び出したときにパラメータで渡された「10」が $1に入っています。

そして、もうひとつの呼び出し関数は display です。
この関数は、配列の中身を表示する関数で、こちらもまだ記述していません。

  setArray $1;
  display;

setArray関数を呼び出すと同時に、$1という値も渡していることがわかります。

配列を作成

ソースの末尾にあるexit;の一つ前の行の、setArray $1; は、execArray関数同様に、呼び出し関数です。
呼び出し関数は関数名のうしろに「()」や「中括弧{}」がありません。

呼び出し関数

  setArray $1;

関数の実体(関数そのもの)

function setArray(){  

}

setArrayやdisplayといって呼び出しはしたものの、2つの関数ともに、関数の実体をまだ書いていません。
ですので、ファイルを実行してもエラーとなります。
今は気にしないでおきます。

ちなみに、execArray()関数の、setArray $1 ; の「$1」は、setArray()関数に渡された1番目のパラメータとなります。

execArrayを呼び出したときに指定された「10」という数字が渡されることとなります。

time execArray 10;

execArray関数に「10」を渡しているわけです。
execArray関数の実体に渡された1つ目のパラメータ「10」は、execArray関数の中で「$1」として受け取っているわけです。

display関数も、呼び出してはいるものの、関数の実体をまだ書いていないので、これから書くことにします。

配列にランダムな数値を代入

では、以下の関数setArray関数をexecArray()関数の上に追記します。

##
# <> set Array
#
function setArray(){
  nElems=0;
  for((i=0;i<$1;i++));do
      insert `echo "$RANDOM"`;
  done
}

追記するとソースコードは以下のとおりとなります。

#!/usr/bin/bash

##
# <> set Array
#
function setArray(){
  nElems=0;
  for((i=0;i<$1;i++));do
      insert `echo "$RANDOM"`;
  done
}
##
# <>execArray()
#
function execArray(){
  setArray $1;
  display;
}
##
#
time execArray 10;
exit;

なんとなくソースコードらしくなってきました。
execArray()内で呼び出した setArray $1; は、function setArray()を呼び出すと同時に、$1を渡します。
ここの$1は `time execArray 10;で渡した「10」です。

time execArray 10; で、execArray関数に10を渡す。

function execArray() で $1として10を受け取る。

setArray $1; で setArray関数に$1(10)を渡す。

setArray()で$1として10を受け取る。

setArray()に渡された「10」はforループの中の条件 「iが$1よりも小さい間にループを回す」というところで登場していますね。
要するに渡された$1を使ってループを10回すということになります。

forでループしながらinsertという関数を呼び出しています。
insert の後ろに echo "$RANDOM" がパラメータで渡されています。
これは、ランダムに数値を生成して insert関数にパラメータとして値を渡しています。

配列の要素を挿入(作成)する関数の作成

では、以下の関数を setArray()関数の上に追記します。

##
# <> insert
#
function insert(){
  array[((nElems++))]=$1;
}

insert()関数では、insert関数に渡されたパラメータ「$1」をarray[]配列に代入しています。
代入される段階で0で初期化されたnElemsという値がインクリメントされます。
挿入されるたびに配列arrayの添字がひとつずつ増えていくわけです。
では、追記したソースは以下の通りとなります。

#!/usr/bin/bash

##
# <> insert
#
function insert(){
  array[((nElems++))]=$1;
}
##
# <> set Array
#
function setArray(){
  nElems=0;
  for((i=0;i<$1;i++));do
      insert `echo "$RANDOM"`;
  done
}
##
# <>execArray()
#
function execArray(){
  setArray $1;
  display;
}
##
#
time execArray 10;
exit;

表示のための関数を作成

最後に、ソースの一番下で、execArray 10; と呼び出している下の display 関数を追記します。
display関数は、作成した配列の中身を表示する関数です。

################
##
# <>display 
#
function display(){
  for((i=0;i<nElems;i++));do
      echo "$i" "${array[i]}";
  done
  echo "------";
}

insert関数で挿入されるたびにインクリメントされたnElemsの値の数だけループ処理されます。
10の配列を準備するわけですから、nElemsの値は、0から始まって最後は9となります。これで10回分ループします。

念の為に、ソース全体は以下の通りとなります。

#!/usr/bin/bash

################
##
# <>display 
#
function display(){
  for((i=0;i<nElems;i++));do
      echo "$i" "${array[i]}";
  done
  echo "------";
}
##
# <> insert
#
function insert(){
  array[((nElems++))]=$1;
}
##
# <> set Array
#
function setArray(){
  nElems=0;
  for((i=0;i<$1;i++));do
      insert `echo "$RANDOM"`;
  done
}
##
# <>execArray()
#
function execArray(){
  setArray $1;
  display;
}
##
#
time execArray 10;
exit;

作成したプログラムの実行

作成した実行ファイルに間違いがなければ、以下の通り出力されるはずです。

bash-5.1$ bash 01Array.sh
0 1572
1 21316
2 22127
3 23889
4 6243
5 18211
6 24203
7 30049
8 10593
9 4716
------

real	0m0.011s
user	0m0.004s
sys	0m0.006s
bash-5.1$

おお、なにやら表示されました。
10個の配列にランダムに生成された値がひとつひとつ代入されていますね。
ランダムな数値ですから、実行するたびに、代入された値が変化するのがわかると思います。(ランダムだから)
この章では、値を配列に格納できれば完成です。

bashスクリプトで2次元配列は可能か?

array[0]配列には一つの値を入力できます。
array[1]にも、array[2]にも一つの値しか入力できません。

C言語やJavaは2次元配列といって、配列に複数の値を格納することができます。

array[0] 名前 住所
array[1] 名前 住所
array[2] 名前 住所

といった感じです。
Bashスクリプトは2次元配列をサポートしていないので、

array[0] 名前

ということしかできません。

とはいえ、頭の良い人がいるもので、bashスクリプトでも、2次元配列に似た事が実現可能です。
恐ろしいことに、3次元でも4次元でも可能です。

具体的にはevalコマンドを使って実現します。

次の章では、格納した配列の値を比較して、並べ替えたいところですが、ちょっとだけ寄り道をして、

「この章の内容をevalコマンドを使った2次元配列で実現」

します。
これにより、ソートから続く高度なアルゴリズム、ツリー、グラフの構築がBashスクリプトで可能となります。

ヒント
この章で配列が実現できました。次の章ではこの章で作成したソースをevalコマンドを使って2次元配列の実現に挑戦します。

「ざっくり」シリーズのご紹介

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト15
https://suzukiiichiro.github.io/posts/2022-10-07-01-algorithm-recursion-suzuki/
【アルゴリズム キュー】ざっくりわかるシェルスクリプト14
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-queue-suzuki/
【アルゴリズム スタック】ざっくりわかるシェルスクリプト13
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-stack-suzuki/
【アルゴリズム 挿入ソート】ざっくりわかるシェルスクリプト12
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-insertionsort-suzuki/
【アルゴリズム 選択ソート】ざっくりわかるシェルスクリプト11
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-selectionsort-suzuki/
【アルゴリズム バブルソート】ざっくりわかるシェルスクリプト10
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-bubblesort-suzuki/
【アルゴリズム ビッグオー】ざっくりわかるシェルスクリプト9
https://suzukiiichiro.github.io/posts/2022-10-04-01-algorithm-bigo-suzuki/
【アルゴリズム 2次元配列編】ざっくりわかるシェルスクリプト8
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-eval-array-suzuki/
【アルゴリズム 配列準備編】ざっくりわかるシェルスクリプト7
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-array-suzuki/
【アルゴリズム 配列編】ざっくりわかるシェルスクリプト6
https://suzukiiichiro.github.io/posts/2022-09-27-01-array-suzuki/
【grep/sed/awkも】ざっくりわかるシェルスクリプト5
https://suzukiiichiro.github.io/posts/2022-02-02-01-suzuki/
【grep特集】ざっくりわかるシェルスクリプト4
https://suzukiiichiro.github.io/posts/2022-01-24-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト3
https://suzukiiichiro.github.io/posts/2022-01-13-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト2
https://suzukiiichiro.github.io/posts/2022-01-12-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト1
https://suzukiiichiro.github.io/posts/2022-01-07-01-suzuki/

【TIPS】ざっくりわかるシェルスクリプト
https://suzukiiichiro.github.io/posts/2022-09-26-01-tips-suzuki/

書籍の紹介

【アルゴリズム 2次元配列編】ざっくりわかるシェルスクリプト8

【アルゴリズム 2次元配列編】ざっくりわかるシェルスクリプト8

アルゴリズム日記 2022/09/29

アルゴリズム日記 2022/09/29