シェルスクリプトあれこれ


最近、開発案件で触れたシェルスクリプトの書き方についてチートシート的なものを残します。
※書くこと多すぎるので、随時更新して追記していきます。

## お品書き
上から適当に書いていきます（順番に意味はありません）
- 本記事の注意書き
- シェルスクリプトを実行する方法
- スクリプトの文頭にある変な文字列ってなに？
- 変数に値を代入/参照する、文字列を結合する
- コマンドライン引数の値をスクリプト内で受け取る
- スクリプト実行中にユーザからの入力を受け取りたい
- スクリプトが正常or異常終了したかを確認（終了ステータス(exit-status)の確認）
- スクリプトの強制終了
- 関数の作り方
- 日時取得
- タイムアウトを設定する
- シェルスクリプトの名前を取得する
- ログ出力、時刻とプロセスID付きでコンソール/ログファイルへ
- if文 -条件分岐-
- パターンマッチ（部分一致）
- while do文_ループ
- ssh -リモートアクセス-
- expectで対話形式の処理を自動化、遠隔操作、多段sshなど
- case
- shift
- シェルスクリプトのオプションを表示させる方法
- ショートオプション、ロングオプション
- 正規化
- printf表記
- ヒアドキュメント
- 外部ファイルの読み込み
- true/false(boolren)

 
## 本記事の注意書き
1. 便宜上、紹介するスクリプト例には、必ずしもshebangを載せないことに留意してください。
2. 備忘として記載するので、例えばコマンドの細かい仕様などは説明はしません。コピペして使えるを目指します。そのため、詳細な仕様を所望する場合は、適宜検索してみてください。
3. ./スクリプト名でシェルスクリプトを実行できない場合、そのファイルの実行権限がない可能性があります。対処方法を示しますので、以下を確認してください。

### ファイルの権限確認方法
まずは以下をターミナルに打ち込んでください。
```shell
ls -ltr | grep 実行権限を確認したいシェルスクリプト名
```
作成されたてほやほやのスクリプトの実行権限は以下のようになっているはずです。
```shell
-rw-r--r--　
```
現状、左から所有者に読み書き権限が有り/所有グループに読み権限が有り/その他に読み権限が有りとなっています。rwxという順でそのファイルに対して許可されていることが明記されるようになっていますが、このファイルには、xがないです。つまり、このファイルを実行する権限がないことを意味しています。そのため、以下のコマンドで実行権限をパーミットしてあげてください。

```shell:権限付与コマンド
chmod 777 スクリプト名
```

コマンドの意味を一応説明します。777は、全権限付与です。便宜上、777としていますが、この限りではないことに留意してください。ファイルの権限周りについては以下の記事がいい感じに説明してくれています。興味があれば参考にしてみてください。

> Linuxの権限確認と変更(chmod)（超初心者向け）
https://qiita.com/shisama/items/5f4c4fa768642aad9e06

## シェルスクリプトを実行する方法
前提条件：
pwdで、スクリプトが保存されているディレクトリにいることを確認していること。
実行方法：
Terminalに　./スクリプト名 と入力してEnterキーを押下
```shell:例
＞＞cat test.sh
#!/usr/bin/env bash
echo "hello world!"

＞＞./test.sh
hello world!
```

## スクリプトの文頭にある変な文字列ってなに？
シェルスクリプトの一行目に"必ず"記述する #! から始まる行を shebang(シーバン) と呼びます。なぜ書くのか？ですが、コイツは、インタープリタ（通訳）を担ってくれています。機械は人間が書いたコードをそのまま理解できないので、通訳者が必要です。インタープリタは、プログラムを実行する方法の1つで、ソースコードを1命令ずつ解釈して実行するプログラムを指します。
例えば、C言語はコンパイル型(全翻訳してから実行)で、Pythonはインタ-プリタ型です。他方で、shebangは、#!/bin/shとか、#!//bin/bash、・・・とか色んな書き方がありますよね。その中で、#!/usr/bin/env bashを使う理由としては、ほぼどのOSでも、確実にbashを呼び出してくれるらしいというのが理由です。#!/bin/shでも大丈夫ですが、シェルには色々な種類があり、これだとどのシェルが呼ばれるかわかりません。
そのため、どのシェルでも動くように書くという観点から、私はこの書き方をしています。#!/bin/bashという書き方もありますが、古いバージョンのbashが呼ばれる可能性があります。なので、#!/usr/bin/env (language) で記載しております（これが正解ではない）。
ちなみに、例えばpythonのスクリプトの先頭行には、#!/usr/bin/env python と書くことができます。


より細かく知りたい方は、以下のようにbashとshについて説明されている記事が色々ありますので、参考にしていただければと思います。
>・シェルスクリプト（Shell Script）の作り方・構文まとめ
https://go-journey.club/archives/5107
・「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典　/usr/bin/env
https://wa3.i-3-i.info/word14585.html

## 変数に値を代入/参照する、文字列を結合する
- 変数に値を代入
以下で、変数に値を代入できます。文字列はクウォートで囲みます。各要素はくっつけます。(空白なし)
変数名=代入したい値
- 変数を参照
以下で、変数を参照できます。\\$をつけない場合、文字列(aa)がそのまま出力されていることがわかります。
\\$変数名

- 文字列を結合する
以下で、変数/文字列を結合できます。
\\$変数1\\$変数2"文字列"

```shell:例
＞＞cat test.sh
aa="こんにちは"
echo $aa
echo aa

str1="hello"
str2=" world!"
echo $str1$str2"を出力しました"

＞＞./test.sh
こんにちは
aa
hello world!を出力しました
```

## コマンドライン引数の値をスクリプト内で受け取る
位置引数で、./スクリプト名 引数1 引数2 ・・という風にコマンドライン引数を入力します。
```shell:例
＞＞cat test.sh
echo ${1:-}
echo ${2:-}

＞＞./test.sh 無量空処 アンパンマングミ
無量空処
アンパンマングミ
```

上記の例では、\\${1:-}に引数1の無量空処、\\${2:-}に引数2のアンパンマングミが渡されます。
echoは受け取った値をコンソール出力するために記載しているため、echoは特に必要ありませんし、\\$1 とだけ、ただ書いてもコマンドライン引数を受け取れます。

以下のようにすることで、変数に初期値を設定できます。コマンドライン引数には何も値がありませんがスクリプトを実行するとAAAが出力されました。
```shell:例
＞＞cat test.sh
echo ${1:-AAA}

＞＞./test.sh
AAA
```

## スクリプト実行中にユーザからの入力を受け取りたい
read コマンドを使います。以下の文言を用いることで、ユーザからの入力を受け付けることができます。
read 変数名

```shell:例
＞＞cat test.sh
echo "文字を入力してください。"
read input
echo "右記の文字が入力されました："$input

＞＞./test.sh
文字を入力してください
＞＞アンドロイド
右記の文字が入力されました：アンドロイド
```

## スクリプトが正常or異常終了したかを確認（終了ステータス(exit-status)の確認）
テストなどでスクリプトが正常終了したのか、それとも異常終了したのかを確認したいことがあるかと思います。またこの値を終了ステータスと呼び、これは任意の値に設定することもできます。
以下の文言で確認することができます。特殊変数\\$?に0(正常)、それ以外(異常)の数値が入ります。(コマンドやエラーの種類によって 0 以外)
```shell:例
echo $?
```

例を示します。
- 正常パターン
test.shがあるディレクトリで、cat test.shを実行します。その後、echo \\$?を実行すると、0が出力されます。これは、catで指定したスクリプトが存在するため正常にコマンドがパスされたとみなし、0を返します。
```shell:例
＞＞cat test.sh
#!/usr/bin/env bash
echo "hello world!"

＞＞echo $?
0
```

- 異常パターン
今度は存在しないファイル対してcatを実行します。その後、echo \\$?を実行すると、1が出力されます。これは、catで指定したスクリプトが存在しないため異常とみなし、1を返します。
```shell:例
＞＞cat 存在しないファイル名　
cat: 存在しないファイル名
: No such file or directory

＞＞echo $?
1
```

終了ステータスを任意の値に設定する方法としては、たとえば、exit 1として、スクリプトを終了し、echo $?で確認すると、正常に終了した場合でも終了ステータスとして、設定した1が出力させることができます。

## スクリプトの強制終了
以下の文言をスクリプト内の任意の場所に書いておくことで、スクリプトを強制終了できます。
```shell:例
exit 数値
```

下の例では、exit 0よりも下の行は実行されていないことがわかります。
```shell:例
＞＞cat test.sh
echo "hello world!"
exit 0
echo "こんにちは、世界！"

＞＞./test.sh
hello world!
```

## 関数の作り方
以下で関数を設定し、関数を呼びます。また引数を関数に渡します。渡したい値が複数ある場合は、\\$1,\\$2,・・とるすことで複数の値の受け渡しが可能です。
```shell:例
関数名 () {
  echo 処理１ "$1"
}
関数名　渡したい値
```

実際に使ってみます。
```shell:例
＞＞cat test.sh
show_date () {
  echo "$(date '+%Y/%m/%d %H:%M:%S')" "$1"
}
value="パンダ"
show_date $value

＞＞./test.sh
2022/12/17 19:15:41 パンダ
```

## 日時取得
以下で日時（yyyy/mm/dd hh/mm/ss）を出力できます。/や:で出力される値を整形しています。
```shell:例
＞＞cat test.sh
echo $(date '+%Y/%m/%d %H:%M:%S')

＞＞./test.sh
2022/12/17 22:10:41

%Yだけだと以下です。
＞＞cat test.sh
echo $(date '+%Y')

＞＞./test.sh
2022
```

## タイムアウトを設定する
コマンドやシェルスクリプトを制限時間付きで実行させます。制限時間を超えたらタイムアウトして処理を強制終了させます。timeoutコマンドで、コマンド実行にタイムアウトの設定ができます。以下のコマンドでtimeoutによるスクリプトの強制終了を実現できます。
```shell:例
timepout 引数1：制限時間（デフォルトの単位は秒） 引数2：実行するコマンドを指定
```
実際に使っていきます。
```shell:例
＞＞cat test.sh
timeout() {
    time=$1
    command="/bin/sh -c \"${@:2}\""
    
    #以下の{exit 数値}で、1を指定してあげると、リターンコードとして、1を返します
    #（timeoutのデフォルト値は、124）
    expect -c "set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof; catch wait result; exit [lindex \$result 3]"
    # expect -c "set timeout $time; spawn -noecho $command; expect timeout { exit 124 } eof; catch wait result; exit [lindex \$result 3]"
}

timeout 1 sleep 3
```

スクリプトの処理が終了後、echo \\$?をたたくと、1が出力されます。
これは、sleep 4 コマンドを実行し、4 秒間だけ待った後、終了するコマンドを実行しているため、timeoutの引数1で指定した1秒の制限に引っ掛かることで、エラーが出ます。

一方で、以下の場合は、sleep 2 コマンドにより2秒間だけ処理を待ち、3秒でタイムアウト処理が走るため、echo \\$?では、正常ステータスの 0 が返ってきます。

```shell:例
timeout 3 sleep 2
```

また、以下の場合は、10秒間googleに対してpingし続けます。
一定時間経過しても終了しなかったら、timeoutにより、強制的に終了させます。

```shell:例
timeout 10 ping www.google.co.jp
```

## シェルスクリプトの名前を取得する
実行しているスクリプト名をスクリプト内部で取得したい時ってありますよね。え、ない？ いいえ、あります。\\$0で、スクリプト名を取得できます。パス名とスクリプト名を拡張子付きで出力します。つまり起動中のシェルスクリプト名がセットされます。
```shell:例
＞＞cat test.sh
echo $0

＞＞./test.sh
./test.sh
```

パス名がいらない時は、basenameをつけてあげます。basenameは、ディレクトリ名とファイル名を含むパス名（/desktop/workspaceなど）から、ディレクトリ部分を除き、ファイル名だけを取得するコマンドです。$0と組み合わせると、良さそうです。
```shell:例
＞＞cat test.sh
MY_BASENAME=$(basename $0)
echo ${MY_BASENAME%}

＞＞./test.sh
test.sh
```

パス名も拡張子もいらない時は以下です。例えばログを吐き出すファイル名として使いたいとかだと、.shまではいらない。とかあると思うので、正規化して拡張子を消してあげます。正規化表現の\%.\*で、"."(ドット)以下の文字列を除外します。
```shell:例
＞＞cat test.sh
MY_BASENAME=$(basename $0)
THIS_SCRIPTS_NAME=${MY_BASENAME%.*}
echo $THIS_SCRIPTS_NAME

＞＞./test.sh
test
```

## ログ出力、時刻とプロセスID付きでコンソール/ログファイルへ
while do、readを使って、出力を1行ずつ読み込んで、コンソール/ファイルに書き込んでいきます。whileで最終行まで行っても、readが空白行の処理せず、処理が残るので、timeoutで強制終了させています。timeoutで指定する終了ステータスを、0 に指定することで、異常終了に見せないようにできます。
```shell:例
＞＞cat test.sh
#!/usr/bin/env bash

###日時とプロセスIDを取得
change_format () {
  printf '[%s] %s: %s\n' "$$" "$(date '+%Y/%m/%d %H:%M:%S')" "$1"
}

###timeoutの設定
timeout() {
    time=$1
    command="/bin/sh -c \"${@:2}\""
    #終了ステータスは、0 を指定
    expect -c "set timeout $time; spawn -noecho $command; expect timeout { exit 0 } eof; catch wait result; exit [lindex \$result 3]"
}

###本スクリプト名を拡張子を除いて取得
MY_BASENAME=$(basename $0)
THIS_SCRIPTS_NAME="-"${MY_BASENAME%.*}


###ログをコンソールおよびファイルに出力（標準/エラー共に）
#ログファイルの生成、ログファイルは、スクリプトと同一階層にlogディレクトリを生成し、そこに書き込んでいきます。
TIME_YYMMDD=$(date '+%Y%m%d')
LOG_FILE_NAME=$TIME_YYMMDD$THIS_SCRIPTS_NAME
LOG_OUT=./log/$LOG_FILE_NAME.log

exec &> >( while IFS= read -r line && logline=$line; do change_format "$logline"; done | tee -a $LOG_OUT)

echo "ログ取得！　コンソールおよびファイルに書き込まれます。"

###readが最後の行を読み込めないので、timeoutで終了する
timeout 1 sleep 3

＞＞./test.sh
[1912] 2022/12/18 09:17:37: ログ取得！　コンソールおよびファイルに書き込まれます。
```

ログファイルへの出力（20221218-test_log.logが生成されます）

```shell:例
＞＞cat ./log/20221218-test_log.log 
[1912] 2022/12/18 09:17:37: ログ取得！　コンソールおよびファイルに書き込まれます。
```

時刻を付与しない、あるいは、コンソールとファイル出力、どちらか片方に出力する方法は、以下の記事などを参考にしてみてください。
>・Bashシェルスクリプトでログ出力をシンプルに実現する方法
https://genzouw.com/entry/2020/01/06/120027/1845/

## if文 -条件分岐-
文字列が一致したかどうかによって、処理が変わる条件分岐です。今回用いる例では、変数AAの値が、ピーナッツの場合、ピーナッツが入力されました。を出力し、ぶどうの場合、ぶどうが入力されました。を出力します。それ以外の場合は、ピーナッツでも、ぶどうでもありません。を出力します。条件分岐内の処理の、= の部分は、 -eq と記述しても等しいかを判定してくれます。ただし、-eqは数値の比較にしか使えないので、文字列を比較しようとすると以下のエラーが出ます。
```shell:エラー
integer expression expected
```
それ以外の elseの部分に条件を付け足したい場合は、elifを使用します。注意点は、右記の部分にしっかりと空白を入れることです。空白がないとエラーがでます。[ この文字列の左と右に空白が欲しい ]

```shell:例
echo 'ピーナッツか、ぶどうと入力してください：'
#readでユーザからの入力を受け取る
read AA
if [ $AA = "ピーナッツ" ] ; then 
  echo 'ピーナッツが入力されました。'
elif [ $AA = "ぶどう" ] ; then
  echo 'ぶどうが入力されました。'
else
  echo 'ピーナッツでも、ぶどうでもありません。'
fi
```
>そのほかにも、AND/OR/NOTなどの条件式を使いたい場合は、以下の文献を参考にしてみてください。
https://www.pasonatech.co.jp/workstyle/column/detail.html?p=7450

## パターンマッチ（部分一致）
if文の応用です。grepコマンドを活用することで、指定した文字列が判定したい変数に含まれるかを判定します。

```shell:例
echo 'XXXパンの、XXXの部分を埋めて入力してください'
read AA
if [ `echo $AA | grep 'パン'` ] ; then
  echo -e 'OK：'"${AA}"'は、パンという文字列が含まれます。\n'
else
  echo 'NG：'"${AA}"'は、パンという文字列が含まれません。'
fi
```

## while do文_ループ
while 文は「ある条件が成り立っている間のみ繰り返し処理を実行する」といった、不定回の繰り返し処理を行う場合に使用するループ制御文のことを指します。以下のように記述することで、ユーザから はんぺん と入力されるまで入力を促し続けます。 はんぺん と入力すると、ループを抜けます。

```shell:例
echo 'はんぺんと入力するまで、処理がループされます。'
read input
while [ "$input" != "はんぺん" ]
do
  echo "ng：${input}ではなく、はんぺんと入力してください。"
  read input
done; echo "ok：${input}と入力されました"
```

## ssh -リモートアクセス-
sshは、Secure Shell（セキュアシェル）の略称で、リモートコンピュータと通信するためのプロトコルです。認証部分を含めネットワーク上の通信がすべて暗号化されています。-pで22番portを指定しています。なくても動きます。ちなみに、SSHサーバーの初期設定は、22番ポートです。以下の文言をシェルスクリプト内に記載して実行すると、アクセスできます。アクセスが成功するとパスワードを求められるので入力することでログインできます。
ユーザによってsshできるとかできないを設定できるので、アクセスできない場合は、それを確認してください。また、そもそもIPのセグメントが違うとか、ホスト名/IPアドレス間違いでも、もちろんsshできないので、その場合は、ping [ホスト名 or IPアドレス] などで、疎通性があるのか確かめて見てください。
```shell:例
ssh -p 22 <ユーザ名>@<ホスト名orIPアドレス>
```

## expectで対話形式の処理を自動化、遠隔操作、多段sshなど
expectは、機械側からの問いに対してユーザが入力返答する、対話形式の処理を自動化するコマンド/モジュールです。対話式の実行環境を提供してくれるため、sshや多段sshして、別の端末にログインして、処理を実行すると言った用途で使えます。
例えば、以下のようにスクリプト内で表現することで、1度踏み台となる端末に対してsshして、その踏み台端末から別の端末にsshするといったことができます。処理ごとにコメントアウトを記載しているので適宜参考にしてください。
```shell:例
#expect -c" 処理 "というように処理を囲む
#expectでマッチさせる文字列は""で囲む必要があり、
#特殊文字である\をクウォートの前につけることで""内で表現される""を
#クウォートとして認識させてあげている。
expect -c "
set timeout 3
##遠隔端末にsshする
spawn ssh -p 22 ユーザ名@ホスト名

##遠隔端末側からコンソールに出力される文言
expect \"password:\"
send \"パスワードを入れる\"
expect \"[ユーザ名@ホスト名 ディレクトリ]#\"

##多段ssh
send \"ssh -p 22 ユーザ名@ホスト名\"
expect \"password:\"
send \"パスワードを入れる\"

interact
"
```

expectについて知りたい方は以下の文献などで調べてみてください。
>・Linuxの対話がめんどくさい?そんな時こそ自動化だ！-expect編-
https://qiita.com/ine1127/items/cd6bc91174635016db9b
・expectを使って自動化してみよう。
https://qiita.com/Mskmemory/items/013dff1a76e58fcdc364


以下のようにすることでログを取るのにも使えます。ssh先でのコマンド投下とかもログ取れます。（本記事の、に記載のログ出力の書き方でもssh先での処理のログも取れます）
col xbは整形に使っています。関数の部分には出力形式を整形するための処理を書いてみてください。引数として、\\$loglineを関数に渡して、整形します。teeコマンドは、標準入力から受け取った内容を、標準出力とファイルに書き出すコマンドです。パイプで繋げて使用します。ヒアドキュメントを使って範囲を定めていて、EOPまでをログ出力します。

```shell:例
expect <<EOP 2>&1 | while IFS= read -r line && logline=$(echo $line | col -xb); do 関数 "$logline"; done | tee -a ${ログファイル名}

処理を書く

EOP
```

## case
caseは、特定のキーワードが入力されたら、そのキーワードに一致する場合の処理を実施します。以下の例では、引数1の部分に、おかえりを入れると、ただいまが出力。おはようの場合は、おはようが出力。それ以外の場合は、おかえり or おはよう を入力してねが出力されます。
```shell:例
＞＞cat test.sh

case "$1" in
  "おかえり")
    echo "ただいま"
    ;;
  "おはよう")
    echo "おはよう"
    ;;
  *)
    #それ以外は * で表現
    echo "おかえり or おはよう を入力してね";;
esac

＞＞./test.sh ただいま
おかえり

```

## shift
shiftは、ある引数を読み込み、1つずらして、次の引数を読み込んでいくという処理を実現します。

```shell:例
＞＞cat test.sh
#コマンドライン引数を受け取って、処理するごとに次の引数を読み込んで、表示します
while [ "$1" != "" ]
do
  echo $1
  shift
done

＞＞./test.sh シェル 最高
シェル
最高
```

## シェルスクリプトのオプションを表示させる方法（getopts
getoptsとcaseを使い、オプションを作ります。getoptsは、bashのシェルスクリプト内でオプションを解析するコマンドです。これによって、シェルスクリプトの引数をオプション付きで指定できます。
例えば、./test.sh -h で実行すると以下の文言が出力されるようにできます。
```shell:例
Usage: $(basename $0) [-h] ./test.sh --引数1　で入力してください。
```
これに加え、-h以外のオプションを付与して実行すると、"-hをつけて実行し、実行方法を確認してください。" が出力されるようなコードとなっています。

```shell:例
＞＞cat test.sh
###-hオプションをつけて実行した場合、本スクリプトを実行する際の引数tipsを提示るする。
#表示するものはヒアドキュメントを使用しています。
show_usage() {
  echo 'Usage: $(basename $0) [-h] ./test.shのオプションによって、helpを表示しています。'
}


###-hオプションを受け取る。
#getopts
while getopts "h-:" cmd_opts; do #受け取ったオプションを、cmd_optsに代入
    case "${cmd_opts}" in
        h) #hの場合の処理
            show_usage
            exit 0
            ;;
        *) #上記以外
            echo "-hをつけて実行し、実行方法を確認してください。"
            exit 1
            ;;
    esac
done
```

また、かなり説明を割愛しますが、getoptsを用いることでoptargとoptindという変数を扱えるようになっています。
１：optarg
optargに、option argumentへのポインタが設定されるため、optargを参照することでoption argumentを取得できます。つまり、オプションに指定した" h"やなどの値は変数 OPTARG に自動的に設定されるわけです。
２：optind
optindはgetoptが次に処理するargv配列のindexで、初期値は1です。よって上記したgetoptの最初の呼び出しはargv[1]から解析をはじめます。index = 1に設定することで、再びargv[1]からオプションの解析を実行することができます。

>詳しくは以下を参考にしてください。
https://qiita.com/tobira-code/items/24e583c30f07c4853f8f

## ショートオプション、ロングオプション
オプションにはショートオプションとロングオプションというものが存在します。ハイフンが1つか2つかの違いがあります。

- ショートオプション
-オプション名

- ロングオプション
--オプション名


## 正規化
正規化とは、一定のルールに基づいて、ものごとを整えることを指します。いくつか正規化表現方法を記載しますが、詳しくは以下を参考にしてください。
> ・シェルスクリプトの正規表現の詳細解説（令和最新版）〜 基本正規表現(BRE)と拡張正規表現(ERE)
https://qiita.com/ko1nksm/items/53abc144558b9bb5629f

ここでは、%、#、//、* の特殊文字(メタ文字)を使った正規化表現を示します。
```shell:例
＞＞cat test.sh
AAA="あいうえお=パンダ 123"
echo $AAA
#うしろから最長一致を削り取る（この場合、=任意の文字でマッチさせている）
AAA_ver1="${AAA%%=*}"
echo $AAA_ver1 

＞＞./test.sh
あいうえお
#→　=より、後ろが取り除かれている。% が1つだと、最短一致


#あいうえお=パンダ 123 から、=文字列 を取り除き、残った文字列（あいうえお）と同じ文字を
#あいうえお=パンダ 123から除外する。つまり、=パンダ 123 が残る。
＞＞cat test.sh
AAA_ver2="${AAA/${AAA%%=*}/}"
echo $AAA_ver2

＞＞./test.sh
=パンダ 123


# 正規化対象/文字列/で、//内の文字列と一致する部分を正規化対象から除外
＞＞cat ./test.sh
AAA_ver3="${AAA/パンダ/}"
echo $AAA_ver3

＞＞./test.sh
あいうえお= 123


#前から最短一致を削り取る
＞＞cat test.sh
AAA_ver2="${AAA_ver2#*パ}"
echo $AAA_ver2

＞＞./test.sh
ンダ 123
# ## が2つだと、最長一致
```

## printf表記
シェルスクリプトにおけるprintfコマンドは、変換指定子を含むようなフォーマット文字列を利用し、文字列を整形しながら出力することできるコマンドです。使い方は、
```shell
printf 引数1：変換文字列 引数2・・n：変換指定子に代入する値 
```
となります。変換指定子は、変換指定を示す%の後ろに記述します。
以下に例を示します。
```shell:例
＞＞cat test.sh
#日時をフォーマット化して表示します。
printf '%s\n' "$(date '+%Y/%m/%d')"

＞＞./test.sh
2022/12/19


＞＞cat test.sh
printf '%s: %s\n' "$(date '+%Y/%m/%d %H:%M:%S')"

＞＞./test.sh
2022/12/19 16:46:31: 
```

s は、書式の引数を文字列と解釈して表示します。
\+ は、dateのパラメータです。フォーマットで日時を出力するには、+ のあとにフォーマットを指定する必要があります。（日時日付の取得の仕方は本ページで紹介しています）

引数の文字列に対して変換指定子が少ないですが、その場合、繰り返し再利用を
してくれるため、エラーにならずに綺麗に整形されて出力できています。

## ヒアドキュメント
ヒアドキュメント（here document）は、コマンドが要求する入力を、ファイル内に直接記述できる機能のことです。繰り返し同じ内容を書かなきゃいけない時などに便利です。先頭で指定した終端の文字列が現れると終了となるため、任意の文字列で入力内容を囲みます。例は、EOPとしていますが、なんでもokです。
```shell:例
＞＞cat test.sh
 cat << EOP
1：hello
2：hello
EOP

＞＞./test.sh
1：hello
2：hello
```

以下のように、echoを2回書いて、'シングルクウォート'で囲む場合と、上記は、同じような出力が得られています。同じことを書くのは手間なので、効率化されていますね。
```shell:例
#ヒアドキュメントを使ったものと同じ出力になる。echoや’を何度も書くのは非効率
echo '1：hello'
echo '2：hello'
```

## 外部ファイルの読み込み
外部のファイルを読み込むには、読み込み先ファイルの名前を、読み込み元ファイル内のsourceコマンドで定義してあげます。exportは、環境変数を設定するためのコマンドです。
外部のファイルの変数を使いたい時に、それを実現する方法を示します。
パスワードとかを外部ファイルにconfとして置いておくと、管理が楽になったりします。

・読み込み先ファイル
```shell:例
＞＞cat conf.txt
#EXTERNAL_VARIABLEという名前で変数を設定する
export EXTERNAL_VARIABLE="外部ファイルだよん"
```

・読み込み元ファイル
```shell:例
＞＞cat test.sh
#!/usr/bin/env bash
#設定ファイルから変数を取得する
source ./conf.txt
#外部ファイルから変数を読み込んで、echoで表示する
echo "$EXTERNAL_VARIABLE"

＞＞./test.sh
出力結果：外部ファイルだよん
```

しっかりと外部ファイルの変数を扱えていることがわかります。

exportコマンドについては以下の記事が参考になります。
>・【shell】exportコマンドの使い方
https://www.zunouissiki.com/shell-export/

## true/false(boolren)
boolren型は、真(true)/偽(false)、はい/いいえ などの二者択一条件を表現するために使用されるデータ型のことを指します。たとえばif文で、その変数に値が代入されている場合をtrueとし、引数が入力されたかどうかを判定することなどに活用できます。
```shell:例
read AA
if [ $AA ] ; then
    echo -e '${AA} が入力されました\n'
else
    echo 'ユーザからの入力がありません'
fi
```

## むすび
- シェルスクリプトの書き方あれこれを記載してきました。（雑ですいません笑
- 内容は随時更新しいていく予定です。