# シェルスクリプト

##

https://betterdev.blog/minimal-safe-bash-script-template/

## snippets

### main

https://qiita.com/blackenedgold/items/c9e60e089974392878c8

### print log

```
function print_info() {
  echo -e "\e[1;36m$*\e[m" # cyan
}

function print_notice() {
  echo -e "\e[1;35m$*\e[m" # magenta
}

function print_success() {
  echo -e "\e[1;32m$*\e[m" # green
}

function print_warning() {
  echo -e "\e[1;33m$*\e[m" # yellow
}

function print_error() {
  echo -e "\e[1;31m$*\e[m" # red
}

function print_debug() {
  echo -e "\e[1;34m$*\e[m" # blue
}
```

### color

echo -e "$BIRed"$line"$Color\_Off"

```
####################
### Color Define ###
####################
# Reset
Color_Off="\033[0m"       # Text Reset

# Regular Colors
Black="\033[0;30m"        # Black
Red="\033[0;31m"          # Red
Green="\033[0;32m"        # Green
Yellow="\033[0;33m"       # Yellow
Blue="\033[0;34m"         # Blue
Purple="\033[0;35m"       # Purple
Cyan="\033[0;36m"         # Cyan
White="\033[0;37m"        # White

# Bold
BBlack="\033[1;30m"       # Black
BRed="\033[1;31m"         # Red
BGreen="\033[1;32m"       # Green
BYellow="\033[1;33m"      # Yellow
BBlue="\033[1;34m"        # Blue
BPurple="\033[1;35m"      # Purple
BCyan="\033[1;36m"        # Cyan
BWhite="\033[1;37m"       # White

# Underline
UBlack="\033[4;30m"       # Black
URed="\033[4;31m"         # Red
UGreen="\033[4;32m"       # Green
UYellow="\033[4;33m"      # Yellow
UBlue="\033[4;34m"        # Blue
UPurple="\033[4;35m"      # Purple
UCyan="\033[4;36m"        # Cyan
UWhite="\033[4;37m"       # White

# Background
On_Black="\033[40m"       # Black
On_Red="\033[41m"         # Red
On_Green="\033[42m"       # Green
On_Yellow="\033[43m"      # Yellow
On_Blue="\033[44m"        # Blue
On_Purple="\033[45m"      # Purple
On_Cyan="\033[46m"        # Cyan
On_White="\033[47m"       # White

# High Intensty
IBlack="\033[0;90m"       # Black
IRed="\033[0;91m"         # Red
IGreen="\033[0;92m"       # Green
IYellow="\033[0;93m"      # Yellow
IBlue="\033[0;94m"        # Blue
IPurple="\033[0;95m"      # Purple
ICyan="\033[0;96m"        # Cyan
IWhite="\033[0;97m"       # White

# Bold High Intensty
BIBlack="\033[1;90m"      # Black
BIRed="\033[1;91m"        # Red
BIGreen="\033[1;92m"      # Green
BIYellow="\033[1;93m"     # Yellow
BIBlue="\033[1;94m"       # Blue
BIPurple="\033[1;95m"     # Purple
BICyan="\033[1;96m"       # Cyan
BIWhite="\033[1;97m"      # White

# Bold High Intensty
BIBlack="\033[1;90m"      # Black
BIRed="\033[1;91m"        # Red
BIGreen="\033[1;92m"      # Green
BIYellow="\033[1;93m"     # Yellow
BIBlue="\033[1;94m"       # Blue
BIPurple="\033[1;95m"     # Purple
BICyan="\033[1;96m"       # Cyan
BIWhite="\033[1;97m"      # White

# High Intensty backgrounds
On_IBlack="\033[0;100m"   # Black
On_IRed="\033[0;101m"     # Red
On_IGreen="\033[0;102m"   # Green
On_IYellow="\033[0;103m"  # Yellow
On_IBlue="\033[0;104m"    # Blue
On_IPurple="\033[10;95m"  # Purple
On_ICyan="\033[0;106m"    # Cyan
On_IWhite="\033[0;107m"   # White
```

## bash

### 括弧

#### \[]と

\[]はtestコマンドのalias

は強化版。以下のことができる。

・空白を含む文字列をクォートしなくてもOK（var="abc 123"のとき、 "$var"->$varでいい）

・正規表現が使える

・パターンマッチができる

・ANDやORに&&や||が使える

#### ()と(())

()：配列定義。var=(a b c)

(())は演算に使う。

```
((var+1))
```

#### {}と\{{\}}

{}

・変数展開(${var})

・パラメータ展開({a..c}等)に使う

・コマンドをまとめる

```
{
echo hoge
echo fuga
echo piyo
} > echo.log
```

\{{\}}は使用することなさそう

#### $()と$(())

$()は\`\`と同じでコマンド実行

$(())はインライン演算。(())と違ってインラインで使用できる

### $系

docllar.sh abc 123と実行

dollar.sh

```
#!/bin/bash
echo $0
# -> dollar.sh
echo $1
# -> abc
echo "$*"
# -> abc 123
# $@との違いはabc 123を一つの文字列として使用する点
echo "$@"
# -> abc 123
# $*との違いはabc 123を一つずつ使用できる点
# for i in "$@";do
#     echo $i
# done
# とかできる。
# 基本的には$*よりこっちを使うこと
echo $#
# -> 2 引数の数
echo $$
# 実行時のプロセスID
echo $!
# 前回実行コマンドの実行結果。
echo $?
# 前回実行コマンドの実行結果。成功は0
echo $-
# スクリプト内で有効になっているフラグを表示
echo $_
# -> /bin/bash 実行シェル取得
```

### コマンドライン上で使える表現

!$：前回の引数を取得

```
less /etc/passwd
vi !$
```

service httpd restart

sudo !!

### リダイレクト

標準エラー出力をリダイレクト

`ls /home/bin 2> hoge.log`

標準出力と標準エラー出力をリダイレクト

`ls /home/bin &> hoge.log`

`ls /home/bin >& hoge.log`

`ls /home/bin > hoge.log 2>&1`

teeで標準出力とファイルを両方に出力

ls /home/bin | tee hoge.log

### exec

・出力をファイルに書く

exec &>file とすると以降のコマンド実行結果はすべてfileに書かれる

・任意のファイルディスクリプタを開く

exec 3> file

echo "hoge" >& 3

exec 3>&- # 閉じる

### ヒアドキュメントでインデントしたい

<<-とする

### ヒアストリングを使う

<<<

### プロセス置換

プロセスの結果を一時ファイルのようにコマンドに渡すことができる機能

#### <()を使って一時ファイル不要diff

diff <(sort file1.txt | uniq) <(sort file2.txt | uniq)

### EXITシグナルをフックする

エラーが起きた際に一時ファイルを消して終了したい場合等に使用する

trapコマンドを使う

trap 実行したいコマンド/関数 EXIT

## bashパラメータ展開

```
${parameter#word}先頭から最短一致
${parameter##word}先頭から最長一致
${parameter%word}末尾から最短一致
${parameter%%word}末尾から最長一致

e.g.)
echo ${name#*/}

${va/pattern/str} 一つの文字の置
き換え
${va//pattern/str} すべての文字の置き換え

計算
$((TEST_V+1))

切り取り
${var:pos} 位置から末尾まで
${var:pos:len} 位置から長さまで
```

## シェルスクリプトを書く前に

シェルのオプションを設定する

http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230881/

`set -ue`

しておく

-u 未定義の変数を使った場合Stopする

-e エラーがあったらStopする

また、

`#!/bin/sh -`

とすると、

sh コマンドに、これ以上オプションが無いことを認識させます。

意図しないオプションをセットされることを防ぎます。

local変数を使うときは宣言したあとに使うこと。そうしないと関数の結果を代入したときにおかしくなる

### 使いやすいシェルスクリプト

http://deeeet.com/writing/2014/05/18/shell-template/

## 引数処理

https://translate.google.com/translate?hl=en\&sl=ja\&tl=en\&u=http%3A%2F%2Fqiita.com%2Fb4b4r07%2Fitems%2Fdcd6be0bb9c9185475bb\&anno=2\&sandbox=1

getopt には重大な落とし穴があります。 それは、スペースや特殊文字が引数に含まれていた場合、正しく処理できないということです。 あるシェルスクリプトで getopt を使った場合、空白や特殊文字は絶対に使うな、という注意書きが必要になります。 これを何とかしのごうとするのは容易ではありません。

なので以下のようなものを自前で書く。しかし、-amのような使い方ができなくなる。特殊だが、2のような感じだとそれも解決できる。

**1**

```
PROGNAME = $( basename $0 )
VERSION = "1.0"

usage () {
echo "Usage: $PROGNAME [OPTIONS] FILE"
echo " This script is ~."
echo
echo "Options:"
echo " -h, --help"
echo " --version"
echo " -a, --long-a ARG"
echo " -b, --long-b [ARG]"
echo " -c, --long-c"
echo
exit 1
}

for OPT in "$@"
do
case "$OPT" in
'-h' | '--help' )
usage
exit 1
;;
'--version' )
echo $VERSION
exit 1
;;
'-a' | '--long-a' )
if [ -z "$2" ]] || [[ "$2" = ~ ^-+ ]( -z "$2" ]] || [[ "$2" = ~ ^-+ .md) ; then
echo "$PROGNAME: option requires an argument -- $1" 1> & 2
exit 1
fi
ARG_A = "$2"
shift 2
;;
'-b' | '--long-b' )
if [ -z "$2" ]] || [[ "$2" = ~ ^-+ ]( -z "$2" ]] || [[ "$2" = ~ ^-+ .md) ; then
shift
else
shift 2
fi
;;
'-c' | '--long-c' )
shift 1
;;
'--' | '-' )
shift 1
param+ =( "$@" )
break
;;
-* )
echo "$PROGNAME: illegal option -- '$(echo $1 | sed 's/^-*//')'" 1> & 2
exit 1
;;
* )
if [ ! -z "$1" ]] && [[ ! "$1" = ~ ^-+ ]( ! -z "$1" ]] && [[ ! "$1" = ~ ^-+ .md) ; then
#param=( ${param[@]} "$1" )
param+ =( "$1" )
shift 1
fi
;;
esac
done

if [ -z $param ] ; then
echo "$PROGNAME: too few arguments" 1> & 2
echo "Try '$PROGNAME --help' for more information." 1> & 2
exit 1
fi
```

**2**

```
declare -i argc = 0
declare -a argv =()

while (( $# > 0 ))
do
case "$1" in
-* )
if [ "$1" = ~ 'n' ]( "$1" = ~ 'n' .md) ; then
nflag = '-n'
fi
if [ "$1" = ~ 'l' ]( "$1" = ~ 'l' .md) ; then
lflag = '-l'
fi
if [ "$1" = ~ 'p' ]( "$1" = ~ 'p' .md) ; then
pflag = '-p'
fi
shift
;;
* )
(( ++argc ))
argv =( "${argv[@]}" "$1" )
shift
;;
esac
done
```

## シェルスクリプトでの実行確認

http://usaturn.net/memo/shell\_tech\_yesorno.html

```
function yes_or_no_select(){
PS3="[y/n] "
while true;do
echo "yes or no."
read answer
case $answer in
y | yes)
return 0
;;
n | no)
exit 2
;;
*)
;;
esac
done
}
```

## よく使う構文

### 引数処理

```
for i in "$@"; do
echo "$i"
done
```

## いろいろな実行方法

### sourceで実行

シェル変数の設定値は実行シェルには引き継がれない

### .（ドットコマンド）で実行

sourceと同様

### bash/shで実行

新しくシェルを起動して実行する。シェル変数の設定値は実行シェルには引き継がれる。

### 直接実行

シバンに記載されている別シェルで起動する。シェル変数の設定値は実行シェルには引き継がれる。

http://www.webzoit.net/hp/it/internet/homepage/env/cs/server/os/type/unix/linux/shell/kind/sh\_bash/environment/

### ファイル・ディレクトリ確認の演算子

| 演算子     | 説明                              |
| ------- | ------------------------------- |
| -a ファイル | ファイルがあれば真                       |
| -b ファイル | ファイルがありブロックス特殊ファイルであれば真         |
| -c ファイル | ファイルがありキャラクター特殊ファイルであれば真        |
| -d ファイル | ファイルがありディレクトリであれば真              |
| -e ファイル | ファイルがあれば真                       |
| -f ファイル | ファイルがあり通常のファイルであれば真             |
| -g ファイル | ファイルがありSGID(特殊なアクセス権)であれば真      |
| -G ファイル | ファイルがあり実行グループIDによる所有者であれば真      |
| -h ファイル | ファイルがありシンボリックであれば真（-Lと同じ）       |
| -k ファイル | ファイルがありステッキービットが設定されていれば真       |
| -L ファイル | ファイルがありシンボリックであれば真（-hと同じ）       |
| -O ファイル | ファイルがあり実行ユーザIDによる所有者であれば真       |
| -p ファイル | ファイルがあり名前付きパイプ（named pipe）であれば真 |
| -r ファイル | ファイルがあり読み取り可能であれば真              |
| -s ファイル | ファイルがありサイズが0より大きければ真            |
| -S ファイル | ファイルがありソケットであれば真                |
| -t FD   | FD（ファイルディスクリプタ）が端末でオープンされていれば真  |
| -u ファイル | ファイルがありSUID(特殊なアクセス権)であれば真      |
| -w ファイル | ファイルがあり書き込み可能であれば真              |
| -x ファイル | ファイルがあり実行可能であれば真                |

https://www.server-memo.net/shellscript/file\_check.html

## デバッグ

### プロンプト表示に行数、ファイル名を出すようにする

`export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME:+$FUNCNAME(): }'`

## Trivia

### linuxで、コマンドの引数に -（ハイフン）が指定できるのを見かけるけど、これ何？

答え

UNIXのシェルのコマンドラインでは、ハイフンひとつを標準入出力のことと読み替える習慣がある（絶対ではないが、そういう風にしている場合が多い）。

## TroubleShooting

### set -ue でexitするけど止まらない

```
#!/bin/bash

set -ue

hello() {
    exit 1
    echo "aaaa"
}


main() {
    local h=$(hello)
    echo "bbbb"
}

main "$@"

echo "cccc"
```

exitを含む関数をlocal変数でうけているとだめらしい。localは戻り値が0になるらしい

local h h=$(hello)にすること

https://stackoverflow.com/questions/12840740/return-value-from-subshell-and-output-to-local-variables

## Tips

### ssh先で環境変数を設定するワンライナー

`ssh -t xxx@yyy "bash -c 'export DISPLAY=192.168.1.100:0;bash'"`

https://qiita.com/noexpect/items/e0e68610592874633653

### 同じカラムの最初の一致部分だけ簡単に抜き出す

`awk '!colname[$1]++{print}'`

### sedで括弧内の文字列を抜き出す

`rg "aaa\(" | sed "s/.*aaa(\(.*\)).*/\1/"`

### 半角スペースがはいってるファイルから取り除く

`find $1 -name "* *" -type f -print0 | while read -d $'\0' f; do mv -v "$f" "${f// /}"; done`

### 太字(Bold)と斜体(Italic)をターミナルで表示する方法

まずフォントがBold対応、Italic対応しているか確認する。 `fc-list`で使っているフォントにBold,Italicがあるか

あったら、ターミナルが対応しているか確認する

_太字_

* `echo -e '\033[1mBold\033[0m'` _斜体_
* `echo -e "\e[3mItalic"`

### ProjectRoot（プロジェクトルート）を探す

`basename $(git rev-parse --show-toplevel)`

### 特定ディレクトリの下にある複数ファイルをglobで取得したい

find . -mindepth 2 -maxdepth 2 -type d -exec sh -c 'ls {}/_AAA_ {}/_BBBS_' ; 2>/dev/null

### sortコマンドの大文字小文字の順序を制御したい

https://superuser.com/a/178214

### atqを実行時刻順でソートする

`atq |sort -k 6n -k 3M -k 4n -k 5 -k 7 -k 1`

### シェルスクリプトの中からSQLを叩く

シングルクオートのエスケープができないためあとから置換する方法しかなさそう

```sql
local sql_command=$(echo "select start_time from test_table where start_time <= \'$datetime\' and end_time >= \'$datetime\' limit 1" | sed 's/\\//g'
```

### atコマンド形式の時刻から普通の時刻に変換する

```
convert_date() {
    date  +'%Y/%m/%d %H:%M' -d "$(echo ${1}00 | sed -e "s/^\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3 \4:\5:\6/g")"
}
```

### ヘッダーを含めずにsortする

https://stackoverflow.com/questions/14562423/is-there-a-way-to-ignore-header-lines-in-a-unix-sort/14562674

`your_script | (sed -u 1q; sort)`

これのsedの使い方がかっこいい。sed -u 1qで一行目だけ処理してあとをsortに回す

### かっこいいカウントアップ

`for ((i=0;; i++)); do printf '\r%s' $i; sleep .1; done`

### echoでの各ログレベルの色

INFO (Use default output color)

NOTICE 36 (Front color cyan)

WARNING 33 (Front color yellow)

ERROR 31 (Front color red)

https://github.com/rcmdnk/shell-logger

### パイプで先頭のコマンドのプロセスIDを取得する

```
( tail -f $1 & echo $! >&3 ) 3>pid | nc -l -p 9977
kill $(<pid)
```

```
tail -f $1 > >(nc -l -p 9977) &
wait $!
```

http://stackoverflow.com/questions/1652680/how-to-get-the-pid-of-a-process-that-is-piped-to-another-process-in-bash

これが一番よい気がする

```
PID=$(jobs -rp)
```

### 配列を関数の引数にしたい

http://yomi322.hateblo.jp/entry/2012/07/15/122530

### 処理終了時にビープ音を鳴らしたい

echo ^G

tput bel

http://ja.stackoverflow.com/questions/230/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%8C%E7%B5%82%E4%BA%86%E3%81%97%E3%81%9F%E3%82%89%E9%9F%B3%E3%82%92%E9%B3%B4%E3%82%89%E3%81%97%E3%81%9F%E3%81%84

### ヘッダを除いてソートする

`(head -n +1 sample.txt && tail -n +2 sample.txt | sort -k 2n,3n ) > sorted_sample.txt`

### 対話的なコマンドを自動実行する

expect

```
expect -c "
set timeout 5
spawn env LANG=C /usr/bin/ssh hoge@ServerName
expect "password:"
send "passwordn"
"
```

### evalで解決されないようにする

バックスラッシュでかわす

http://stackoverflow.com/questions/5253782/bash-problem-with-eval-variables-and-quotes

### パイプでつながっているときの各戻り値を確認する

PIPESTATUS

http://kawa0810.hateblo.jp/entry/2015/01/12/213450

### 入力と同じファイルに出力する

1. `echo "`uniq .bash\_history`" > .bash_history`
2. `{ rm .bash_history && uniq > .bash_history; } < .bash_history`
3. `uniq .bash_history | sponge .bash_history`

install moreutils

http://serverfault.com/questions/135507/linux-how-to-use-a-file-as-input-and-output-at-the-same-time

### ディストリビューションを判別する

**1**

OS=$(lsb\_release -si)

ARCH=$(uname -m | sed 's/x86\_//;s/i\[3-6]86/32/')

VER=$(lsb\_release -sr)

**2**

. /etc/lsb-release

OS=$DISTRIB\_ID

ARCH=$(uname -m | sed 's/x86\_//;s/i\[3-6]86/32/')

VER=$DISTRIB\_RELEASE

**3**

OS=$(uname -s)

ARCH=$(uname -m)

VER=$(uname -r)

**4**

```
if [ -f /etc/debian_version ]; then
OS=Debian  # XXX or Ubuntu??
VER=$(cat /etc/debian_version)
elif [ -f /etc/redhat-release ]; then
fi
```

**5**

which yum > /dev/null && { echo redhat; return; }

which zypper > /dev/null && { echo opensuse; return; }

which apt-get > /dev/null && { echo debian; return; }

### 例外処理を行う

```
set -e

trap "
mv /tmp/swap-file original-file
rm /tmp/target-file
" 0
```

0 or EXITは正常終了

ERRは0以外を指す

http://blog.suz-lab.com/2012/09/try-catch.html

### 今だけ必要なディレクトリ、ファイルを作る

mktempを使う

```
temp_file = $( mktemp )
temp_dir = $( mktemp -d )
```

### set -ueしているときに変数が定義されているかを判断する方法

**デフォルト値を使う方法**

```
if [ ${1:-} != "" ]( ${1:-} != "" .md);then
NOWDATE=$1
echo 1
else
NOWDATE=`date '+%Y-%m-%d'`
echo 2
fi
```

http://masasuzu.hatenablog.jp/entry/2013/05/01/zsh\_undefined\_variable

**-vを使う方法（bashのバージョンが4.2以上でなければならない）**

```
if [ -v FOO ]( -v FOO .md) ; then
echo y
else
echo n
fi
```

### set -ueしているときにコマンドの戻り値を評価する方法

http://sousaku-memo.net/php-system/1164

ふたつある

**1つ目の方法set+e**

最初に

set +e

目的のコマンド

set -e

をする方法

**2つ目の方法if文\[...]を使わない**

\[...] の代わりに直接コマンドを書くと戻り値を見てくれる模様

```
!/bin/sh

set -e

# ifでtestも/bin/[も使わない！
if echo "$1" | grep 'a' > /dev/null
then
echo 'has a'
else
echo 'has not a'
fi
```

http://d.hatena.ne.jp/sasaplus1/20120621/1340207996

### 実行しているシェルスクリプトのパスを取得する

script\_dir=$(cd $(dirname $0); pwd)

だとsourceした場合にうまく動かない

bashの場合は以下を使う

`script_dir=$(cd $(dirname $BASH_SOURCE); pwd)`

zshは以下

`$0`

ふたつとも使用出来る形式はこれ

`script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)`

### set -x とコマンドを実行してしまった場合の解除方法

set +x

### ファイルが存在していて開けるか確認

if test -r "$upFN" -a -f "$upFN";then

fi

### スクリプトがあるディレクトリパスを取得

root\_dir=(cd $(dirname $0); pwd)

## 予約済みexit code

| Exit Code | 意味                               | 例                     | コメント                                                    |
| --------- | -------------------------------- | --------------------- | ------------------------------------------------------- |
| 1         | 一般的なエラー全般                        | $ let "var 1 = 1 / 0" | ゼロ除算などのコマンドを継続できない雑多なエラー                                |
| 2         | （Bash のドキュメントによると）シェルビルトインな機能の誤用 | $ empty\_function(){} | キーワードのつけ忘れやコマンド，または権限周りの問題（あと， diffがバイナリファイルの比較に失敗した時 ） |
| 126       | 呼び出したコマンドが実行できなかった時              | $ /dev/null           | パーミッションの問題かコマンドが executable でない時                        |
| 127       | コマンドが見つからない時                     | $ illegal\_command    | $PATHがおかしい時や typo した時などに起こる                             |
| 128       | exitコマンドに不正な引数を渡した時              | $ exit 3.14159        | exitコマンドは 0〜255 の整数だけを引数に取る                             |
| 128+n     | シグナルnで致命的なエラー                    | $ kill -9 $PPID       | 例では， $?は 137（128 + 9）を返す                                |
| 130       | スクリプトが Ctrl+C で終了                | Ctrl+C                | Ctrl+C はシグナル2で終了する = 128 + 2 = 130（上記）                  |
| 255       | 範囲外の exit status                 | $ exit -1             | exitコマンドは 0〜255 の整数だけを引数に取る                             |

http://tldp.org/LDP/abs/html/exitcodes.html
