【TIPS】ざっくりわかるシェルスクリプト

ここではシェルスクリプトのBashについてのTIPSを紹介します。C言語やJavaといった高級言語にあってbashにはない部分について補完できる様々な手法をご紹介します。

Bashで普通に動くスクリプトを記述することはできるけど、C言語やJavaなどで記述したり構築したりするにはどうすればよいのか?ということについて、様々なアイディアも含めて、同等の表現方法がいくつかあるので、参考にしてください。

シェルオプション

setコマンドで便利にプログラミングする手法を紹介します。古来から伝わる便利な一行です。

#! /usr/bin/bash

set -ueo pipefail

set -u : 未定義の変数を使用した箇所でスクリプトが正常終了します。変数名が異なる場合も実行できてしまうなどのありがちがバグを未然に防ぐことができます。

set -e : スクリプトの実行中にエラーが発生した場合、エラーの箇所でスクリプトの処理が終了します。通常は、エラーが発生しても実行は中断されず、エラー箇所を特定するのはとても大変ですが、set -e オプションを付けておくとエラー箇所の特定が比較的カンタンになります。

set -o pipefail : コマンド同士の連結にパイプ「|」を使いますが、パイプ箇所でエラーが発生した場合に、パイプで連結したどのコマンドでエラーが発生したのかを特定することができます。

上記3つのオプションを結合すると set -ueo pipefailをなります。通常、行頭のシェバン「#!/usr/bin/bash 」の下に一行空行を置き、その下に set -ueo pipefailを書くと良いです。

変数の型を指定する

シェルスクリプトでは変数の型を指定する必要はありませんが、指定しておくことで、間違った値を代入することがなくなり、しいてはバグが減ります。

変数を数値として宣言する
iオプションを使用します。

declare -i num=1+2
str=1+2

echo $num # => 3
echo $str # => 1+2

変数を配列として宣言する
aオプションを使用します。

declare -a array=( Java Ruby Python )

echo ${array[0]} # => Java

echo ${#array[*]} #=> 3(配列の要素数)

for e in ${array[*]}
do
  echo $e # => Java, Ruby, Pythonの順に出力
done

for i in ${!array{*}}
do
  echo ${array[i]} # => Java, Ruby, Pythonの順位出力
done

変数を定数(読み取り専用)とし、初期化時に値を代入する
-r オプションを使います。

#!/usr/bin/bash

set -ueo pipefail

declare -r num=5;

str=$((num+2));
echo $num # => 5
echo $str # => 7

ローカル変数を定義する

シェルスクリプトはどこで宣言しようとすべてグローバル変数として扱われますが、localをつけることによって、明示的にもローカル変数を定義することができます。

function fn() {
  # 問題のある例:
  local hoge=$(false)
  # $? で直前に実行したコマンドの終了ステータスを参照できる
  echo $? # => エラーが握りつぶされ 0 が返る!
 
  # 問題のない例:
  local hoge2
  hoge2=$(false)
  echo $? # => 正しく 1 が返る
}

インクリメント

インクリメントなどは以下のように書くことができます。

一般的な書き方

#!/usr/bin/bash

set -ueo pipefail 

value=0;
value=`echo "$value+1" | bc`;
echo "valueの値は " $value;

スッキリとした書き方

#!/usr/bin/bash

set -uo pipefail  # eをつけると動きません

declare -i value=0;
((value++));
echo "valueの値は" $value;

if C言語やJavaのような条件式で記述する

メリットは以下のとおりです。
・半角空白を配置する必要がない
・一般的に短い行で記述できるようになる
・条件式の変数に「$」を付ける必要がない
・-gt は >、 -lt は <、-leは<=、で普通に記述できる

一般的な記述

#!/usr/bin/bash

set -ueo pipefail

x=4;
if [ "$x" -gt 2 ] && [ "$x" -le 5 ]; then
  echo "$x は 2 より大きいかつ 5 以下です";
fi

拡張した記述

#!/usr/bin/bash

set -ueo pipefail

x=4;
if ((x>2 && x<=5)); then
  echo "$x は 2 より大きいかつ 5 以下です";
fi

while C言語やJavaのような条件式で記述する

スッキリとした書き方

#!/usr/bin/bash

set -ueo pipefail

declare -i i=0;
while((i++<10));do
  echo $i;
done

for C言語やJavaのような条件式で記述する

スッキリとした書き方

#!/usr/bin/bash

set -ueo pipefail

for((i=1;i<=10;i++));do
  echo $i;
done

doやdoneを使わないもっとスッキリとした書き方

#!/usr/bin/bash

set -ueo pipefail

for((i=1;i<=10;i++)){
  echo $i;
}

grepで該当文字列があったら反応する

# 一般的には以下のようにします。
if cat hoge.txt | grep "Apple" >/dev/null; then
  echo "hoge.txtにはAppleが含まれた行がある"
fi

# --quiet は標準出力に何も書き出さないオプション
if cat hoge.txt | grep --quiet "Apple"; then
  echo "hoge.txtにはAppleが含まれた行がある"
fi
 
# 条件の反転は ! をつける
if ! cat hoge.txt | grep --quiet "Apple"; then
  echo "hoge.txtにはAppleが含まれた行がない"
fi

長い行の改行について

長い行の改行はバックスラッシュを末尾につける

aws --region ap-northeast-1 cloudformation deploy \
  --template-file ./packaged-template.yaml \
  --stack-name example-stack \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides \
    Environment=development \
    EnableDebugLog=true

驚いたことに(僕も驚きました)パイプラインでの改行はエスケープがいらない!

cat access.log |
  # IPアドレスのカラムを取得する
  awk '{print $5}' |
  # 100行目以降のみを集計対象とする
  tail +100 |
  # IPアドレスごとのアクセス数のランキングを集計する
  sort | uniq -c | sort -nr

関数パラメータは変数に格納する

関数に渡された値は $1,$2…というふうにアクセスできます。
とはいえ、関数の中で $1,$2を使うとなにがなんだかわかりにくくなります。
ですので、関数冒頭で変数に格納しましょう。
もちろん忘れずに変数にはlocal 変数をつけましょう。
変数の型がわかっているのであれば(わかっているでしょう)、declare -i などで明示的に変数の型を指定するのが望ましいのです。

function do_something() {
  # まず最初に引数を意味のある命名の変数に取り出す
  local target_dir;
  local action

  target_dir=$1;
  action=$2;
}

スクリプトのデバッグ

Bashは広範なデバッグ機能を提供しています。

デバッグの方法は3種類あります

1.ターミナルの実行時に -x オプションを付与する

$ bash -x helloScript.sh

2.ソースコードの冒頭のシェバンに -x オプションを付与する

#!/bin/bash -x
:
:

3.デバッグの開始点と終了点を決めてデバッグ
デバッグの開始点にコマンド ‘set -x’終了点には ‘set +x’ と書きます。

#!/bin/bash 

set -x
echo "置き換えたいファイル名を入寮して下さい。"
read fileName
set +x

if [[ -f "$fileName" ]]; then
  sed -e "s/Linux/Unix/g" "$fileName";
else
  echo "$fileName はありません。";
fi
$ bash test
+ echo 置き換えたいファイル名を入寮して下さい。
置き換えたいファイル名を入寮して下さい。
+ read fileName
grepfile.txt
+ set +x
This is Unix
This is Windows
This is MAC
This is Unix
This is Windows
This is MAC
This is Unix
This is Windows
This is MAC
This is Unix
This is Windows
This is MAC
$
#!/bin/bash

# デバッグ開始
set -x

var1=`date +%M`

# デバッグ終了
set +x

var2=`ls -1 | wc -l`
var3="DEBUG TEST"

exit 0
$ bash debug3.sh
++ date +%M
+ var1=56
+ set +x
$
ヒント
色々と便利なbashですが、これからも便利な書き方があれば更新してきます。

マルチラインコメント

複数行コメントの使用
bashではさまざまな方法で複数行コメントを使用できます。
次の例に簡単な方法を示します。
‘multiline-comment.sh’という名前の新しいbashを作成し、次のスクリプトを追加します。
ここでは、「:」と「'」でbashで複数行コメントを実現しています。
次のスクリプトは、5の2乗を計算します。

ヒント
「:」と「'」の間は半角スペースを入れます。
#!/bin/bash

: '
次のスクリプトは、
数値の2乗値5を計算します。
'
((area=5*5));
echo "$area";

bashコマンドでファイルを実行します。

$ bash multiline-comment.sh
25
$
ヒント
多くの場合、マルチラインコメントの存在は知られていない。
ほとんどの人は、行頭に「#」をならべて複数行コメントを行う。
それは、過去のメジャーソースコードの冒頭にそうあるからだ。
そう、UNIX/Linuxの開発者のほとんどは、マルチラインコメントを知らないのだ。

今後出てくるであろうファイルの生成に「touch」というコマンドがある。これ実は 「:>ファイル名」で、空のファイルを生成する事ができる。「:」は、”なにもしないことを示す。if文の中で何もしない場合は、以下のように記述する。

if [ “$v” -eq 5 ];then

: # 何もしない
fi

touchは既にファイルがあれば、そのファイルにはさわらない。

:> は既にファイルがあれば、そのファイルさえも空にする。
上記 if 文の中の : は 何もしないことを示す。
マルチラインコメントも同じ「:」から始まり、何もしないことを示している。

文字列からの配列の代入

一般的なwhile read 文。配列の内容を別の配列にコピーしています。

#!/usr/bin/bash

declare -a month_array=("jan feb mar apr");
declare -i number=0; # 変数は数値型
declare -a my_array; # 変数は配列型

while read number;do
  my_array[$number]=${month_array[$number]};
  let number++;
done< <( seq 0 4)

echo ${my_array[@]};

修正したスクリプト。while read がまるごと不要となっていますね。

#!/usr/bin/bash

declare -a month_array=("jan feb mar apr");
# コメントアウト
# declare -i number=0;
# 空白を区切り文字として配列に代入
declare -a my_array=(${month_array// / });

: '
コメントアウト
while read number;do
  my_array[$number]=${month_array[$number]};
  let number++;
done< <( seq 0 4)
'
echo ${my_array[@]};

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

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト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/

書籍の紹介

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

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

NQueen日記 2022/09/15

NQueen日記 2022/09/15