シェルスクリプト

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

snippets

main

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

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
# [email protected]との違いはabc 123を一つの文字列として使用する点
# -> abc 123
# $*との違いはabc 123を一つずつ使用できる点
# for i in "[email protected]";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 "[email protected]"
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+ =( "[email protected]" )
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 "[email protected]"; 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"
}
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

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

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を叩く

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

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 [email protected]
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

Contents
snippets
main
print log
color
bash
括弧
$系
コマンドライン上で使える表現
リダイレクト
exec
ヒアドキュメントでインデントしたい
ヒアストリングを使う
プロセス置換
EXITシグナルをフックする
bashパラメータ展開
シェルスクリプトを書く前に
使いやすいシェルスクリプト
引数処理
シェルスクリプトでの実行確認
よく使う構文
引数処理
いろいろな実行方法
sourceで実行
.(ドットコマンド)で実行
bash/shで実行
直接実行
ファイル・ディレクトリ確認の演算子
デバッグ
プロンプト表示に行数、ファイル名を出すようにする
Trivia
linuxで、コマンドの引数に -(ハイフン)が指定できるのを見かけるけど、これ何?
TroubleShooting
set -ue でexitするけど止まらない
Tips
同じカラムの最初の一致部分だけ簡単に抜き出す
sedで括弧内の文字列を抜き出す
半角スペースがはいってるファイルから取り除く
太字(Bold)と斜体(Italic)をターミナルで表示する方法
ProjectRoot(プロジェクトルート)を探す
特定ディレクトリの下にある複数ファイルをglobで取得したい
sortコマンドの大文字小文字の順序を制御したい
atqを実行時刻順でソートする
シェルスクリプトの中からSQLを叩く
atコマンド形式の時刻から普通の時刻に変換する
ヘッダーを含めずにsortする
かっこいいカウントアップ
echoでの各ログレベルの色
パイプで先頭のコマンドのプロセスIDを取得する
配列を関数の引数にしたい
処理終了時にビープ音を鳴らしたい
ヘッダを除いてソートする
対話的なコマンドを自動実行する
evalで解決されないようにする
パイプでつながっているときの各戻り値を確認する
入力と同じファイルに出力する
ディストリビューションを判別する
例外処理を行う
今だけ必要なディレクトリ、ファイルを作る
set -ueしているときに変数が定義されているかを判断する方法
set -ueしているときにコマンドの戻り値を評価する方法
実行しているシェルスクリプトのパスを取得する
set -x とコマンドを実行してしまった場合の解除方法
ファイルが存在していて開けるか確認
スクリプトがあるディレクトリパスを取得
予約済みexit code