How to name a commands group in POSIX shell script
シェルスクリプトで複数コマンド群に名前を付けてグループ化して可読性を向上させる方法について検討した。
命名グループ化の検討の概要 |
---|
|
なお,今回の記事で利用したコード類は以下に格納している。
POSIXism/daily/2017/20170730_macro at master · lamsh/POSIXism
Introduction
シェルスクリプトでは,パイプを使うことで複数のコマンドを繋いで処理できる。しかし,パイプをつないで記述されたコードは,全体として何をやっているのか意味がわかりにくくなる。また,複数のオプションを組み合わせたコマンドの記述や,自分がよく知らないコマンドが使われた場合,どういう処理をやっているのかわかりにくくなる。
処理内容をわかりやすくするために,複数の処理を変数や関数にまとめ,適切な名前を与える方法がある。例として,以下にpwd
コマンドが存在するかどうかを判定するコードを掲載する。
command -v pwd >/dev/null && echo found || echo not found
このコードでは,command -v
の処理内容を理解していないと,見ただけでは意味がわからない。しかし,このコードを以下のように関数化して,人がわかるような名前を与えることで,command -v
の意味を知らなくても全体の処理内容を理解しやすくなる。
is_exe_enabled(){
command -v "$1" /dev/null
}
is_exe_enabled pwd && echo found || echo not found
また,関数にまとめるだけでなく,変数にまとめることでも可読性の向上を実現できる。例えば,以下のコードはbashのバージョンが4.3以上であればbindコマンドを実行している。
if bash --version | grep -q -e ' 4\.[3-9]*' -e ' [5-9]\.'"; then
bind 'TAB: menu-complete'
fi
このコードは以下のように,IS_BASH_VER_GE_43
変数に一度代入すれば,どのバージョンを対象としているのかがよりわかりやすくなる。
IS_BASH_VER_GE_43="eval bash --version | grep -q -e ' 4\.[3-9]*' -e ' [5-9]\.'"
if $IS_BASH_VER_GE_43; then
bind 'TAB: menu-complete'
fi
このように,関数や変数などシェルスクリプトには複数の処理に対して別の名前を付ける方法がいくつかある。ここで疑問が生じる。わかりやすくなったのはいいが,それで実行速度が遅くなっていないだろうか?複数の実現方法があるのならば,最も簡単で最も速度の速い方法を採用したい。
そこで,シェルスクリプトで処理をグループ化して可読性をあげるための方法を紹介し,実行速度を計測することで,どの方法がベストであるかを検討した。
なお,コードの可読性の向上に関しては,コメントを書くことで補うこともできる。しかし,コメントで説明した場合,実際のコードとコメントの内容を合わせて読む必要があり,読み手に負担がかかる。コメントを書く前に,コメントが不要になるようにコード自体をわかりやすくすべきだろう。
Method
処理のグループ化には以下の3種類の方法がある。
- Variable(変数)
- Function(関数)
- alias
Variable(変数)
1点目は変数に処理を代入する方法だ。以下のように処理内容を(マクロ)変数に代入し,変数展開により代入された内容を実行する。
## Simple command (without eval)
EXE=pwd
$EXE
## Compound command (with eval)
IS_BASH_VER_GE_43="eval bash --version | grep -q -e ' 4\.[3-9]*' -e ' [5-9]\.'"
if $IS_BASH_VER_GE_43; then
bind 'TAB: menu-complete'
fi
シンプルで記述量が少ないが,いくつか注意点がある。
変数の注意点 |
---|
|
変数に代入して展開する場合,通常のコマンドラインの解釈が異なり,予約語が通常の文字列とみなされてしまう。これを回避する場合,先頭にeval
を記述することで残りの部分をeval
で解釈してやる。
eval
を先頭に使う場合,本来よりもコマンドを余計に1個実行するため,速度が遅くなる懸念がある。
使い方としては,条件判定結果を格納して,条件判定に使うのが良い例だろう。
## 現在のシェルのオプションとしてset -eが有効になっているかを判定
IS_ENABLED_SET_E=$( case "$-" in (*e*) echo true;; (*) echo false;; esac )
if $IS_ENABLE_SET_E; then
echo "set -e is enabled"
fi
条件判定に変数を使う場合,判定結果に応じて0と1の整数を代入して,test
コマンドで[ $VAR = 1 ]
のように記述する方法もある。しかし,個人的にはこの方法は好ましくないと考えている。理由は以下2点だ。
- 人によって条件判定値とその意味が異なる(0と1のどちらが真か不明)。
test
コマンドによる記述が冗長になる。
変数に直接true
,false
を代入して使ったほうが,意図がわかりやすく,また共に組み込みコマンドとしてPOSIXで定義されているので(速度も)安定している。
Function(関数)
2点目は関数を使う方法だ。以下のように複数のコマンド群をグループ化できる。
is_exe_enabled(){
command -v ${1+"$@"} >/dev/null
}
is_opt_enabled()(
EXE="$1"; OPT="$2"
"$EXE" --help 2>&1 | grep -q -- "$OPT"'[[:blank:],=[]'
)
関数を記述する場合,本体部分に波括弧{}
か丸括弧()
を使うことができ,それぞれで挙動が異なる。
括弧の種類 | 説明 |
---|---|
波括弧{} | 現在シェルで実行する。関数内で定義した変数の値,関数定義,set,exportなどの特殊組み込みコマンドの実行結果が,現在シェルにも作用する。 |
丸括弧() | サブシェルで実行する。関数内で定義した変数の値,関数定義,set,exportなどの特殊組み込みコマンドの実行結果が,現在シェルには作用しない。 |
丸括弧()
で定義した場合,その関数は別プロセスとなり現在シェルへの影響を抑制できる。そのため,丸括弧で関数を定義すれば,意図しない変数の変更などを防ぐことができ,保守性が上がる。代わりに,新しくプロセスを作成するので,実行速度が遅くなる懸念がある。
関数を使えば,引数の扱いも柔軟にでき,複雑な処理も可能なので,汎用性が高いと考えられる。
alias
3点目はaliasを使う方法だ。
alias IS_ENABLED_SET_E='case "$-" in (*e*) echo true;; (*) echo false;; esac'
if IS_ENABLE_SET_E; then
echo "set -e is enabled"
fi
aliasには以下の特徴がある。
aliasの特徴 |
---|
|
変数を使うより柔軟な対応ができ,aliasの定義は一度だけでよいので,alias実行はeval
変数を使う場合よりも速度が速い可能性がある。
なお,ここでいうaliasの定義とは以下のようにaliasコマンドでaliasを作成することを指している。
alias ll='ls -l'
変数や関数と異なり,名前に!%,@
を使えるので,工夫すればオブジェクト指向型言語のような,メンバー関数(メソッド)やメンバー変数(プロパティ)を実現できる可能性がある。
ただし,以下の注意点がある。
aliasの注意点 |
---|
|
1点目の引数については,変数と同じであり,エイリアス名の後に渡した内容をそのまま引数と解釈する。
bash expand_aliases
bashにはexpand_aliasesというシェルのオプションが存在する。このオプションはbashでalias展開を有効化するためのオプションとなっており,以下のどちらかのコマンドでalias展開を有効化できる。
bash -O expand_aliases ## enable with startup
shopt -s expand_aliases ## enable after startup
bashは標準では対話シェル以外では,expand_aliases
がオフになっており,aliasが展開されない。
ただし,bashがPOSIXモード(6.11 Bash POSIX Mode – Bash Reference Manual)で起動された場合はaliasが展開される。bashがPOSIXモードで起動される場合は以下となる。
- シンボリックリンクなどで
bash
をコマンド名sh
で実行した場合 - 起動オプションに–posixを付けてbashを起動(
bash --posix
) - setコマンドで有効化(
set -o posix
)
シェルスクリプトでbashのaliasが展開されないパターンとしては,以下2通りが存在する。
- シバンにbashを明示的に指定して実行(
#!/bin/bash
)
cat <<-EOT >alias-shebang.sh
#!/bin/bash
alias c=:
c
EOT
bash ./alias-shebang.sh./alias-shebang.sh: line 3: c: command not found
- シバンのないシェルスクリプトをbashの対話シェルからファイル名で実行
cat <<-EOT >alias-colon.sh
:
alias c=:
c
EOT
chmod +x alias-colon.sh
bash -c './alias-colon.sh'./alias-colon.sh: line 3: c: command not found
ここでは詳細を述べないが,シバンがない方がシェルスクリプトの互換性が高まることが調査の結果わかっている。そのため,aliasを使う場合は,bashの対話シェルからでも起動できるように以下のコードで冒頭に記述する必要がある。
command -v shopt && shopt -s expand_aliases
aliasが使えない場所
aliasはコマンドを実行しても,aliasが定義できなかったり展開されない記述箇所が存在する。
- 関数内で定義した場合は,(たとえ他の関数内でも)関数内では展開できない(関数外なら展開できる)。
sh <<-EOT
func1(){
alias c=:
}
func2(){
c
}
func1
func2
c
EOTsh: line 5: c: comand not found
- 複合文({}, (), if, for, while, until)やリスト(;, &&, ||, &)で定義した場合は,その内部では展開できない(外部では展開できる)。
sh <<-EOT
{ alias c=:; c; }
c
EOTsh: 1 c: not found
このことについて,理由を考察する。
まず1点目の関数内で定義した場合に関数内で使えない理由だ。これは関数は一つの独立した環境であるためだ。
コマンドの実行環境は以下の通りに定義されている。
すぐ上のシェル実行環境(shell execution environment)ではaliasが定義されているが,コマンド(utilityl)に関してはaliasについて定義されていない。上記引用部分の末尾で強調した通り,コマンド内で明確に変更されることが記述されていれば,変更は可能である。しかし,aliasの説明を確認しても,関数内でaliasを定義できるとの文言はない。そのため,関数内でaliasの定義と実行を同時にはできないのだと考えられる。
なお,aliasの説明で書かれている通り,現在シェルでaliasを定義した場合は,現在の実行環境とサブシェルに影響を与えるため,関数内でもaliasを展開できる。
2点目の複合文内でのaliasの定義と展開の同時実行について。これは,複合文内はUtilityと同じように別の環境であるためだと考えられる。POSIXでのUtiltyは以下に記載されている通り,Special Built-in utilityを除く名前が付いているものと定義されている。
複合文は名前は付いていないので,Utilityではないと思うのだが,シェル実行環境とは異なるためこのような挙動になっているのだと思われる。
また,実行後に現在シェルでaliasが有効になっている件については,関数も複合文も同一プロセスであるため,実行後の現在シェルにはaliasの定義が残るのだと考えられる。
複合文でのaliasの定義・展開については一つ懸念がある。それは,複合文内でaliasの定義・展開が行われている外部のシェルスクリプトをdot.コマンドで読み込んだ場合,どうなるかだ。
シェルスクリプトが外部でどのように利用されるかは利用者次第だ。コマンドとして実行される他に,dotコマンドでライブラリーとして読み込んで実行されてもおかしくはない。その場合,複合文内でdotコマンドで読み込んだ場合,この挙動だと確実にalias展開が失敗することになる。しかし,dotコマンドはSpecial Built-In Utilityであるため,また別の挙動を示す可能性がある。
そこで,これらのaliasがどのような挙動を示すか調査した。実際にいろんなシェルでaliasの定義と実行結果について調査し,表にまとめた。検証コードは以下となる。
:
################################################################################
## \file alias.sh
## \author SENOO, Ken
## \copyright CC0
################################################################################
: <<-EOT
aliasが使えないことがあるのでその検証。
複合コマンド内でaliasを定義と実行を同時にできない。
リストもダメなようだ
EOT
is_exe_enabled(){
IS_ENABLED_SET_E=$( case "$-" in (*e*) echo true;; (*) echo false;; esac )
is_command_enabled(){ command -v : >/dev/null 2>&1; }
$IS_ENABLED_SET_E && set +e
if is_command_enabled; then
$IS_ENABLED_SET_E && set -e
command -v ${1+"$@"} >/dev/null 2>&1 && return || return
fi
$IS_ENABLED_SET_E && set -e
IFS=:
for path in $PATH; do
unset IFS
command -p [ -x "$path/"${1+"$@"} ] && return
done
}
echo_alias_result(){
EXIT_STATUS=$?
STR=${1+"$@"}
if [ $EXIT_STATUS = 0 ]; then
echo "Alias run success: $STR"
else
echo "Alias run failure: $STR"
fi
}
EXE=':'
SHS='sh ksh ksh93 pdksh oksh lksh mksh posh ash dash bash zsh yash busybox'
TMP_ALIAS_FILE1='tmp-alias1.sh'
cat <<-EOT >$TMP_ALIAS_FILE1
alias c=:
c 2>&-
EOT
TMP_ALIAS_FILE2='tmp-alias2.sh'
cat <<-EOT >$TMP_ALIAS_FILE2
{ alias c=:; }
c 2>&-
EOT
for sh in $SHS; do
is_exe_enabled $sh || continue
case $sh in busybox) sh='busybox ash'; esac
$sh -c "shopt -q 2>&-" && sh="$sh -O expand_aliases"
echo "$sh"
$sh -c 'alias >/dev/null 2>&-' || continue
## Expected Success
$sh <<-EOT; echo_alias_result 'Normal'
alias c=$EXE
c
EOT
$sh <<-EOT; echo_alias_result 'Define inside, run outside'
{ alias c=$EXE; }
c
EOT
$sh <<-EOT; echo_alias_result 'Define outside, run inside'
alias c=$EXE
{ c; }
EOT
$sh <<-EOT; echo_alias_result 'Include1'
{ . ./$TMP_ALIAS_FILE1; }
EOT
$sh <<-EOT; echo_alias_result 'Include2'
{ . ./$TMP_ALIAS_FILE2; }
EOT
$sh <<-EOT 2>&-; echo_alias_result 'func(){}'
func(){
alias c=$EXE
}
func
c
EOT
## Expected Failure
$sh -c 'alias c=$EXE | c' 2>&-; echo_alias_result '|'
$sh -c 'alias c=$EXE & c' 2>&-; echo_alias_result '&'
$sh -c 'alias c=$EXE; c' 2>&-; echo_alias_result ';'
$sh -c 'alias c=$EXE && c' 2>&-; echo_alias_result '&&'
$sh -c '! alias c=$EXE || c' 2>&-; echo_alias_result '||'
$sh <<-EOT 2>&-; echo_alias_result 'if'
if true
then
alias c=$EXE
c
fi
EOT
$sh <<-EOT 2>&-; echo_alias_result 'for'
for i in 0
do
alias c=$EXE
c
done
EOT
$sh <<-EOT 2>&-; echo_alias_result 'case'
case '' in *)
alias c=$EXE
c
esac
EOT
$sh <<-EOT 2>&-; echo_alias_result 'while'
i=0
while [ \$((i+=1)) = 1 ]
do
alias c=$EXE
c
done
EOT
$sh <<-EOT 2>&-; echo_alias_result 'until'
i=0
until [ \$((i+=1)) = 2 ]
do
alias c=$EXE
c
done
EOT
$sh <<-EOT 2>&-; echo_alias_result '()'
(
alias c=$EXE
c
)
EOT
$sh <<-EOT 2>&-; echo_alias_result '{}'
{
alias c=$EXE
c
}
EOT
$sh <<-EOT 2>&-; echo_alias_result 'func(){}'
func1(){
alias c=$EXE
}
func2(){
c
}
func1
func2
EOT
done
Type | Command | ksh93 | pdksh | oksh | lksh | mksh | posh | dash | bash | zsh | yash | busybox |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Normal | alias c=: c |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | |
Define inside, run outside | { alias c=:; } c |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | |
Define outside, run inside | alias c=: { c; } |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | |
Include1 | { . ./tmp-alias1.sh; } # alias c=: # c |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ||
Include2 | { . ./tmp-alias2.sh; } # { alias c=:; } # c |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ||
func(){} | func(){ alias c=: } func c |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | |
| | alias c=: | c | |||||||||||
& | alias c=: & c | |||||||||||
; | alias c=:; c | |||||||||||
&& | alias c=: && c | |||||||||||
|| | ! alias c=: || c | |||||||||||
if | if true then alias c=: c fi |
|||||||||||
for | for i in 0 do alias c=: c done |
|||||||||||
case | case ” in *) alias c=: c esac |
|||||||||||
while | i=0 while [ $((i+=1)) = 1] do alias c=: c done |
|||||||||||
until | i=0 until [ $((i+=1)) = 2] do alias c=: c done |
|||||||||||
() | ( alias c=: c ) |
|||||||||||
{} | { alias c=: c } |
|||||||||||
func(){} | func1(){ alias c=: } func2(){ c } func1 func2 |
表の見方は,縦軸に実行したコマンドがあり,横軸に実行したシェルが並んでいる。○が付いた欄はalias展開に成功したことを示し,空欄は失敗したことを示す。
調査結果についての考察を以下にまとめた。
aliasの挙動の結果考察 |
---|
|
dot.コマンドで読み込む場合は特殊なようで,多くのシェルでは問題なく複合文内でもaliasを展開できた。しかし,ksh93だけはできなかった。
aliasは名前に使える文字が多く,再帰など興味深く応用が効きそうではある。しかし,関数内や複合文内での定義・実行に難があることがわかった。また,aliasコマンドはPOSIX 2008になるまでのPOSIX 1992,2001,2004ではUPE(User Portability Option)扱いであり,必須ではなかった。実際に,poshのようにaliasコマンドに対応していないシェルが存在する。これらのことから,シェルスクリプト内でのaliasの利用は控えたほうが無難かもしれない。
Result
ここまでで処理に名前を付けてグループ化する3種類の方法について紹介した。
実際にこれらの方法でコマンドを実行して,実行速度を計測した。
実行は以下の8パターンを試した。
- 通常::
- 変数(単独):MACRO_N
- 変数(eval):MACRO_E
- alias:MACRO_A
- 関数(波括弧):MACRO_B
- 関数(丸括弧):MACRO_P
- 複合文(波括弧):{ :; }
- 複合文(丸括弧):( : )
関数の実行方法が変わった際にどれくらい実行速度が変わるかを見たいだけなので,試験用のコマンドは何もしない組み込みコマンドの:
を採用した。
上記の7と8は処理の命名グループ化とは直接は関係ないが,コマンド実行時間がどれくらいかかるかの参考にする良い機会と判断したので,追加した。
なお,使用したシェルは以下であり,Ubuntu 16.04で測定した。
ksh93 Version AJM 93u+ 2012-08-01
pdksh @(#)PD KSH v5.2.14 99/07/13.2
oksh @(#)PD KSH v5.2.14 99/07/13.2
lksh @(#)LEGACY KSH R52 2016/04/09
mksh @(#)MIRBSD KSH R52 2016/04/09
posh 0.12.6
dash January 19, 2003
bash GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
zsh zsh 5.1.1 (x86_64-ubuntu-linux-gnu)
yash Yet another shell, version 2.39
busybox ash BusyBox v1.22.1 (Ubuntu 1:1.22.0-15ubuntu1) multi-call binary.
検証には以下の2個のコードを利用し,./run-time-macro.sh
を実行した。
:
################################################################################
## \file run-time-macro.sh
## \author SENOO, Ken
## \copyright CC0
################################################################################
main(){
init
SHS='sh ksh ksh93 pdksh oksh lksh mksh posh ash dash bash zsh yash busybox'
for sh in $SHS; do
case $sh in busybox) sh='busybox ash'; esac
is_exe_enabled $sh || continue
printf "$sh: "
$sh time-macro.sh
done
}
## \brief Initialize POSIX shell environment
init(){
PATH="$(command -p getconf PATH 2>&-):${PATH:-.}"
export PATH="${PATH#:}" LC_ALL='C'
umask 0022
set -eu
}
is_exe_enabled(){
IS_ENABLED_SET_E=$( case "$-" in (*e*) echo true;; (*) echo false;; esac )
is_command_enabled(){ command -v : >/dev/null 2>&1; }
$IS_ENABLED_SET_E && set +e
if is_command_enabled; then
$IS_ENABLED_SET_E && set -e
command -v ${1+"$@"} >/dev/null 2>&1 && return || return
fi
$IS_ENABLED_SET_E && set -e
IFS=:
for path in $PATH; do
unset IFS
command -p [ -x "$path/"${1+"$@"} ] && return
done
}
main
:
################################################################################
## \file time-macro.sh
## \author SENOO, Ken
## \copyright CC0
################################################################################
## \brief Compare macro for readable code.
## 0. none
## 1. variable
## 2. eval
## 3. alias
## 4. function(){}
## 5. function()()
## 6. {}
## 7. ()
main(){
init
echo "Execution time[s] for $N times"
echo "only body"
run_body
echo "including definition"
run_total
}
## \brief Initialize POSIX shell environment
init(){
PATH="$(command -p getconf PATH 2>&-):${PATH:-.}"
export PATH="${PATH#:}" LC_ALL='C'
umask 0022
set -eu
is_exe_enabled shopt && shopt -s expand_aliases
export N=100000
TAB=$(printf '\t')
N_TEST=$(( 8 + 1 ))
EXE=':'
MACRO_N="MACRO_N='$EXE'"
MACRO_E="MACRO_E='eval $EXE'"
MACRO_A="alias MACRO_A='$EXE'"
MACRO_B="MACRO_B(){ $EXE; }"
MACRO_P="MACRO_P()( $EXE )"
BRACE="{ $EXE; }"
PAREN="( $EXE )"
}
is_exe_enabled(){
IS_ENABLED_SET_E=$( case "$-" in (*e*) echo true;; (*) echo false;; esac )
is_command_enabled(){ command -v : >/dev/null 2>&1; }
$IS_ENABLED_SET_E && set +e
if is_command_enabled; then
$IS_ENABLED_SET_E && set -e
command -v ${1+"$@"} >/dev/null 2>&1 && return || return
fi
$IS_ENABLED_SET_E && set -e
IFS=:
for path in $PATH; do
unset IFS
command -p [ -x "$path/"${1+"$@"} ] && return
done
}
run_body(){
{
printf 'Command\nReal\nUser\nSys\n'
timeit_body '$EXE' "$EXE"
timeit_body '$MACRO_N' "$MACRO_N"
timeit_body '$MACRO_E' "$MACRO_E"
timeit_body 'MACRO_A' "$MACRO_A"
timeit_body 'MACRO_B' "$MACRO_B"
timeit_body 'MACRO_P' "$MACRO_P"
timeit_body "$BRACE" "$BRACE"
timeit_body "$PAREN" "$PAREN"
} 2>&1 | format
}
## \brief Time exe only body
timeit_body(){
EXE_STR="$1"; EXE_VAR="$2"
echo "$EXE_VAR"
\time -p sh <<-EOT
eval "$EXE_VAR"
for i in \$(yes | head -n $N); do
$EXE_STR
done
EOT
}
run_total(){
{
printf 'Command\nReal\nUser\nSys\n'
timeit_total '$EXE' "$EXE"
timeit_total '$MACRO_N' "$MACRO_N"
timeit_total '$MACRO_E' "$MACRO_E"
timeit_total 'MACRO_A' "$MACRO_A"
timeit_total 'MACRO_B' "$MACRO_B"
timeit_total 'MACRO_P' "$MACRO_P"
timeit_total "$BRACE" "$BRACE"
timeit_total "$PAREN" "$PAREN"
} 2>&1 | format
}
## \brief Time exe including definition
timeit_total(){
EXE_STR="$1"; EXE_VAR="$2"
echo "$EXE_VAR"
\time -p sh <<-EOT
eval "$EXE_VAR" ## for alias
for i in \$(yes | head -n $N); do
eval "$EXE_VAR" ## run definition
$EXE_STR
done
EOT
}
format(){
pr -t"$N_TEST"s"$TAB" | sed -e 's/real *//g' -e 's/user *//g' -e 's/sys *//g'
}
main
実行結果を整理したものを以下の表に示した。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
まず,表の見方について説明する。この表は10万回実行した際の経過時間[s]を示している。そのため,100倍して,単位にm(ミリ)をつければ1回あたりにの行速度となる。
(a) Only bodyはコマンドの定義を含めずに,本体部分を実行した際の速度となっている。そして,(b) Including definitionはコマンドの定義を含めた計測時間となっている。なお,aliasは複合文内で定義をすると実行できないため,計測のforループ外で定義する必要がある。処理の負担を平等にするため全てのケースでeval
により定義を実行している。そのため,Including definitionはevalのコマンドの実行分の約0.05 s/10万回の時間が余計にかかっている。また,最後の複合文2個は定義と本体が同一であるため,単純に2回実行することになり,時間がかかっている。2列目の:の列が命名によるグループ化を行っていないケースなので,これが実行速度の基準となる。
調査結果の考察を以下に掲載する。
命名グループ化の速度計測結果の考察 |
---|
|
Summary
シェルスクリプトの可読性を向上させるために,複数のコマンドに名前を付けてグループ化する方法を検討した。その概要を以下に示す。
命名グループ化の検討の概要 |
---|
|
コマンドの実行というシェルスクリプトを書くうえでかなり基本的な部分に注目した検討を行った。実行速度を計測することで,今までなんとなく把握していた,丸括弧によるサブシェルでの実行が遅いということや,変数やalias展開は実行速度が速いということが定量的に分かった。
今後は今回の調査結果を利用して,可読性と実行速度に注意を払ったシェルスクリプトの作成を心がける。