概要
タイトルの通りBashシェルスクリプトの書き方のおさらいメモです。
動作確認にDocker Desktop for Windowsと[ubuntu official image] (https://hub.docker.com/_/ubuntu)を利用しました。
環境
- Windows 10 Professional
- Docker Desktop for Windows 2.0.0.3 (31259)
- Ubuntu 18.04.2
参考
- [Bash Reference Manual] (https://www.gnu.org/software/bash/manual/bash.html)
- [Bash Scripting Best Practices] (https://sap1ens.com/blog/2017/07/01/bash-scripting-best-practices/)
- [Best Practices for Writing Bash Scripts] (https://kvz.io/blog/2013/11/21/bash-best-practices/)
バージョン
ubuntuのバージョン
# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
bashのバージョン
# echo $BASH_VERSION
4.4.19(1)-release
シェル構文 (Shell Syntax)
クォート (Quoting)
シングルクォート (Single Quotes)
シングルクォート '...'
で囲まれた文字は、パラメータ展開やコマンド置換は行われません。
$ message="World"
$ echo '$Hello ${message}!'
Hello ${message}!
$ echo '$(date "+%Y/%m/%d")'
$(date "+%Y/%m/%d")
シングルクォートで囲まれた文字にシングルクォートを含めることはできません。後述のエスケープキャラクタでエスケープすることもできません。
$ echo 'in 80's'
ダブルクォート (Double Quotes)
ダブルクォート "..."
で囲まれた文字は、パラメータ展開やコマンド置換が行われます。
パラメータ展開やコマンド置換をしたくない場合は後述のエスケープキャラクタを使います。
$ message="World"
$ echo "Hello ${message}!"
Hello World
$ echo "$(date "+%Y/%m/%d")"
2019/04/28
Escape Character
バックスラッシュ \
に続く1文字を文字の通り解釈します。
$ echo "Hello \${message}!"
Hello ${message}!
$ echo "\$(date "+%Y/%m/%d")"
$(date "+%Y/%m/%d")
シェルコマンド (Shell Commands)
ループ (Looping Constructs)
whileループ
while
はtest-commandの終了ステータスが1(為)である限りconsequent-commandsを繰り返し実行します。
while test-commands; do consequent-commands; done
例
i=0; while [ $i -lt 5 ]; do echo $i; ((i++)); done
i=0
while [ $i -lt 5 ]
do
echo $i
((i++))
done
条件式の評価の部分 [ $i -lt 5 ]
は、test [ $i -lt 5 ]
や test $i -lt 5
という書き方もあります。
デモ) ファイルを読み込む
その1) 半角スペース区切り
1 2
2 3
3 4
4 5
5 6
while read c1 c2
do
echo "$c1 + $c2 = $((c1 + c2))"
done < input.txt
その2) カンマ区切り
1, 2
2, 3
3, 4
4, 5
5, 6
IFSで区切り文字を指定します。IFSはシェル変数で文字列を分割する区切り文字が設定されています。デフォルトは半角スペース、タブ、改行になります。
while IFS=, read c1 c2
do
echo "$c1 + $c2 = $((c1 + c2))"
done < input.txt
forループ
wordsを展開しそれぞれの値に対してcommandsを実行します。値はnameにバインドされます。
for name [ [in [words …] ] ; ] do commands; done
例
for i in 0 1 2 3 4; do echo $i; done
for i in 0 1 2 3 4
do
echo $i
done
forループには次のような算術式を使った書き方もあります。
for (( expr1 ; expr2 ; expr3 )) ; do commands ; done
例
for (( i=0; i<5; i++ )); do echo $i; done
for (( i=0; i<5; i++ ))
do
echo $i
done
デモ) ディレクトリの一覧
$ for dir in `find $(pwd) -mindepth 1 -type d`; do echo $dir; done
条件 (Conditional Constructs)
if
test-commandsの終了ステータスが0(真)の場合、consequent-commandsが実行されます。1(為)でelifが後続する場合はmore-test-commandsが実行され終了ステータスを判定します。
if test-commands; then
consequent-commands;
[elif more-test-commands; then
more-consequents;]
[else alternate-consequents;]
fi
例)
test
コマンドを使う書き方。
$ if test $var = "A"; then echo "var is A"; else echo "var isn't A"; fi
test
はコマンドなので単体で使用できます。
$ a="ABC"
$ b="ABC"
$ c="abc"
$ test $a = $b; echo $?
0
$ test $a = $c; echo $?
1
test
コマンドの別名である[ ... ]
を使う書き方もあります。
$ if [ $var = "A" ]; then echo "var is A"; else echo "var isn't A"; fi
デモ)ファイル・ディレクトリの判定
ファイル・ディレクトリの存在チェック
if [ -e input.txt ]; then echo "file is exists"; else echo "file is not exists"; fi
- ファイルの存在チェックは
-f
- ディレクトリの存在チェックは
-d
- シンボリックリンクの存在チェックは
-L
ディレクトリのシンボリックリンク
if [ -d dir1 -a -L dir1 ]; then echo "dir is symbolic link"; else echo "dir is not symbolic link"; fi
case
wordに一致する最初のパターンのcommand-listを実行します。同じcommand-listを実行するパターンが複数ある場合は|
で区切って指定します。
case word in
[ [(] pattern [| pattern]…) command-list ;;]…
esac
例)
var=9
case $var in
[0-9])
echo "var is numeric"
;;
[a-z])
echo "var is lower case"
;;
[A-Z])
echo "var is upper case"
;;
+|-|/|%)
echo "var is symbol"
;;
*)
echo "default"
;;
esac
一般的な慣習として、デフォルトケースは一番最後に*
として定義します。
It’s a common idiom to use ‘*’ as the final pattern to define the default case, since that pattern will always match.
(( 算術式 ))
(( 算術式 ))
は、算術式を評価し終了ステータスを返します。終了ステータスは、式の結果が0以外のときは0(真)、それ以外のときは1(為)になります。
$ i=5; while ((i--)); do echo $i; done
[[ 条件式 ]]
[[ 条件式 ]]
は、条件式を評価し終了ステータスを返します。終了ステータスは、評価が真(true)のときは0(真)、為(false)のときは1(為)になります。
$ f="apple"; if [[ $f == [aA]pple ]]; then echo "match"; else echo "unmatch"; fi
match
シェルパラメータ (Shell Parameters)
変数の割り当て
変数の割り当ては変数=値
で行います。値を定義しなかった場合はnull文字が設定されます。
${#変数}
と書くと文字列の長さを取得できます。
$ message="Hello World"
$ echo "$message"
Hello World
$ echo "${#message}"
11
$ message=
$ echo "$message"
$ echo "${#message}"
0
変数の属性 (Attributes )
declare
はビルトインコマンドで、変数の定義と同時に属性を設定することができます。
-r
オプションは変数を読み取り専用にします。
$ declare -r message="Hello Wolrd"
$ message=""
bash: message: readonly variable
readonly
コマンドでも読み取り専用の変数を定義できます。
$ readonly message="Hello World"
変数の定義と読み取り専用化は別々に行うこともできます。
$ message="Hello World"
$ readonly message
属性の確認
-p
オプションを指定すると属性と値を表示します。
$ declare -p message
declare -r message="Hello World"
数値
-i
オプションを指定すると数値(intger)として定義されます。
数値として定義すると後述する算術式を使わなくても数値計算が行えます。
$ declare -i num=0
$ num=num+10
$ echo $num
10
数値の場合、${#変数}
は桁数を返します。
$ declare -i num=1000
$ echo ${#num}
4
配列
-a
オプションを指定すると配列として定義されます。
添え字に@
を指定すると配列全体を参照します。
$ declare -a array=(a b c)
$ echo "${array[@]}"
a b c
$ echo "${#array[@]}"
3
$ array[0]=A
$ echo "${array[@]}"
A b c
配列をfor文で走査する場合は次のように記述します。
for i in ${array[@]}
do
echo "$i"
done
添え字を使いたい場合は次のようにfor文を記述します。
添え字の変数は先頭に$
を付けなくても構いません(付けても動きます)。
for ((i = 0; i < ${#array[@]}; i++)) {
array[i]=${array[i]^^}
echo "$i=${array[i]}"
}
シェル展開 (Shell Expansions)
ブレース展開 (Brace Expansion)
$ echo a{1,2,3}
a1 a2 a3
ダブルクォーテーション内のブレース展開はされません。
$ echo "a{1,2,3}"
a{1,2,3}
シーケンス式 (sequence expression)
シーケンス式 {x..y}
を使って連番を生成できます。
for i in {1..10}; do echo $i; done
{x..y..incr}
という書き方で増分を指定できます。
for i in {1..10..3}; do echo $i; done
数値以外に文字も指定できます。
for i in {a..f}; do echo $i; done
シェルパラメータ展開 (Shell Parameter Expansion)
基本形式 (The basic form)
parameterの値が展開されます。
${parameter}
${paramter:-word}
変数 parameter
が未定義かnull文字の場合、代わりに word
が展開されます。
$ now=
$ echo ${now:-$(date)}
Sun Apr 28 16:27:40 UTC 2019
${parameter:=word}
変数 parameter
が未定義かnull文字の場合、代わりに word
が展開されparameter
に代入されます。
$ now=
$ echo ${now:=$(date)}
Sun Apr 28 16:27:40 UTC 2019
$ echo $now
Sun Apr 28 16:27:40 UTC 2019
${parameter:?word}
変数 parameter
が未定義かnull文字の場合、word
の展開された結果が標準エラーに出力されます。word
が未定義の場合は、代わりのメッセージが出力されます。
$ now=
$ echo ${now:?}
bash: now: parameter null or not set
$ now=
$ echo ${now:?"is null"}
bash: now: is null
${parameter:+word}
変数 parameter
が定義されている(未定義かnull文字以外)場合、word
が展開されます。それ以外の場合は空文字が展開されます。
$ now=$(date "+%Y/%m/%d")
$ echo ${now:+"today is $now"}
today is 2019/04/30
部分文字列展開 (Substring Expansion)
${parameter:offset}
$ var=0123456789abc
$ echo ${var:5}
56789abc
${parameter:offset:length}
$ var=0123456789abc
$ echo ${var:5:3}
567
文字数 ${#parameter}
parameter
の文字数を展開します。
$ var=20190429
$ echo ${#var}
8
パターンに一致する部分を削除
parameterからwordに一致する部分を削除します。
${parameter#word}
parameter
の左側からword
で指定するパターンに最短一致する部分を削除して展開します。
${parameter##word}
parameter
の左側からword
で指定するパターンに最長一致する部分を削除して展開します。
$ var=123123123abc456456456
$ echo ${var#12*3}
123123abc456456456
$ echo ${var##12*3}
abc456456456
${parameter%word}
parameter
の右側からword
で指定するパターンに最短一致する部分を削除して展開します。
${parameter%%word}
parameter
の右側からword
で指定するパターンに最長一致する部分を削除して展開します。
$ var=123123123abc456456456
$ echo ${var%45*6}
123123123abc456456
$ echo ${var%%45*6}
123123123abc
パターンに一致する部分を置換
parameterからpatternに一致する部分をstringで置換します。
${parameter/pattern/string}
最短一致
$ var=123123123abc456456456
$ echo ${var/[[:digit::]]/*}
*23123123abc456456456
${parameter//pattern/string}
最長一致
$ var=123123123abc456456456
$ echo ${var//[[:digit::]]/*}
*********abc*********
コマンド置換 (Command Substitution)
バッククォート
従来からある記法です。
$ NOW=`date "+%Y/%m/%d %H:%M:%S"`
2019/04/28 05:29:56
$( )
新しい記法です。
$ NOW=$(date "+%Y/%m/%d %H:%M:%S")
2019/04/28 05:31:27
算術展開 (Arithmetic Expansion)
$(( 算術式 ))
$ x=10
$ y=5
$ echo $x $y
10 5
$ echo "x + y = $(( x + y ))"
x + y = 15
Bash組み込みコマンド (Bash Builtin Commands)
echo
- オプション
-n
: 改行(newline)が抑制されます。 - オプション
-e
: エスケープキャラクタが有効になります。
echo [-neE] [arg …]
$ echo -n "abc"; echo "def"
abcdef
$ echo "Hello\tWolrd"
Hello\tWolrd
$ echo -e "Hello\tWolrd"
Hello Wolrd
printf
- オプション
-v var
: 出力結果を変数varに格納します。
printf [-v var] format [arguments]
echo "+-----+-+-+-+----------------------------------------------------+"
echo "| no |f|d|s|file |"
echo "+-----+-+-+-+----------------------------------------------------+"
declare -i i=1
for name in $(find /etc)
do
f=""
if [ -f "$name" ]; then
f="o"
fi
d=""
if [ -d "$name" ]; then
d="o"
fi
l=""
if [ -L "$name" ]; then
l="o"
fi
printf "| %3d |%s|%s|%s| %-50s |\n" $i ${f:-" "} ${d:-" "} ${l:-" "} $name
((i++))
done
echo "+-----+-+-+-+----------------------------------------------------+"
source
filenameで指定したシェルスクリプトを読み込みます。.
はsourceの別名です。
source filename
補足
vim
Ubuntuにvimをインストールする
# apt-get update
# apt-get install -y vim
vimのカラースキーマをセットする
使えるカラースキーマを確認
# ls -1 /usr/share/vim/vim*/colors/
README.txt
blue.vim
darkblue.vim
default.vim
delek.vim
desert.vim
elflord.vim
evening.vim
industry.vim
koehler.vim
morning.vim
murphy.vim
pablo.vim
peachpuff.vim
ron.vim
shine.vim
slate.vim
torte.vim
zellner.vim
.vimrcに下記の行を追加
colorschemeに使用するカラースキーマ名を設定
syntax on
colorscheme blue
カラースキーマは[Vim Colors] (https://vimcolors.com/)で探せます。