地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

bashで忘れがちな機能とかいろいろの備忘録

Bash-Final.jpg

背景

シェルスクリプトを書くことが結構あるのでその備忘録代わりに記事を書いてみました。

特殊変数とは

特殊変数とは シェルによって自動的に値が設定される特殊な変数がいくつかあり、それら特殊な変数を参照することにより、様々な情報を取得することができる。

基本機能

bashの基本機能で使いやすい機能をご紹介

文法チェックをしたい : noexec

「-n」をつけて実行することでスクリプト内のコマンドは実行されずに文法のみをチェックしてくれる。

# 文法エラーありパターン
$ bash -n test.sh 
test.sh: 行 5: 予期しないトークン `done' 周辺に構文エラーがあります
test.sh: 行 5: `done'

# 文法エラーなしパターン
$ bash -n test.sh
$ 

実行内容のトレース : xtrace

「-x」をつけることでスクリプトの実行内容を出力できる どのコマンドまで処理が行われたかどういった処理でエラーとなったのかを検出するときに使えます。

bash -x test.sh 
+ LOOP_COUNT=3
++ seq 0 3
+ for i in `seq 0 ${LOOP_COUNT}`
+ ls -la
++ date
+ echo

+ for i in `seq 0 ${LOOP_COUNT}`
+ ls -la
++ date
+ echo

+ for i in `seq 0 ${LOOP_COUNT}`
+ ls -la
++ date
+ echo

+ for i in `seq 0 ${LOOP_COUNT}`
+ ls -la
++ date
+ echo

未定義変数を検出 : nounset

「-u」を指定することで未定義の変数を検出してくれます。未定義変数を許可したい場合には使えないので注意が必要。 都度ifで判定とかが回避策になるのでしょうか?

$ bash -u test.sh 
test.sh: 行 5: LOOP_COUNT: 未割り当ての変数です

「*」等によるパス名展開の無効化 : noglob

「-f」を指定するとパス名展開を無効化できる。

$ bash -f test.sh 
ls: '/*' にアクセスできません: そのようなファイルやディレクトリはありません

終了ステータスが0以外のものが検出した時点でスクリプトを終了

「-e」でコマンドの終了ステータスが「0」以外のときに後続のスクリプトを実行せずに終了します。

bash -e test.sh 
ls: 'aa' にアクセスできません: そのようなファイルやディレクトリはありませ

trapコマンドでシグナルをハンドル

シグナルハンドラも設定できます。 下記を例にします。

なんかしらの処理中にその処理を停止したい時があるとします。 その際に既に生成されているtmpファイルがCtrl + Cで止めるだけだと残っていまい自分で消す必要があります。 trapコマンドを使用すればCtrl + Cで止めたあとの処理を記述することでその処理が行われます。

$ cat test.sh 
#/bin/bash

trap 'rm -f *.tmp' 1 2

touch test1.tmp
touch test2.tmp

# なんかしらの処理
sleep 30

# その後にtmpを削除
rm -f *.tmp

ちなみにtrapコマンドで指定できるシグナル一覧は「trap -l」で確認できます。

$ trap -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

# ちなみにtrapコマンドの書式は下記
trap 'コマンド' [シグナル番号|シグナル名]

パイプで渡したコマンドの終了ステータスを見る

変数PIPESTATUSを確認すれば各コマンドの終了ステータスが確認できます。 以下はgrepコマンドが検索に失敗しているので終了ステータスが1となっています。

$ ls | grep "a" | wc ; echo ${PIPESTATUS[@]}
      0       0       0
0 1 0 # 終了ステータスを表示

デフォルトのシェルを変えたい

使用できるシェルの種類を調べるには、/etc/shells を参照するか、もしくは、chsh -l コマンドを実行します。 シェルを変更するには、chsh コマンドを使用します。

$ cat /etc/shells 
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/bin/rbash
/bin/dash

組み込みコマンド

組み込みコマンドは「help」と打てば見れます その中でもよく使うものだけピックアップ

$ help
GNU bash, バージョン 4.4.19(1)-release (x86_64-pc-linux-gnu)
これらのシェルコマンドは内部で定義されています。`help' と入力して一覧を参照してください。
`help 名前' と入力すると `名前' という関数のより詳しい説明が得られます。
'info bash' を使用するとシェル全般のより詳しい説明が得られます。
`man -k' または info を使用すると一覧にないコマンドのより詳しい説明が得られます。

名前の後にアスタリスク (*) がある場合はそのコマンドが無効になっていることを意味します。

 job_spec [&]                                                     history [-c] [-d offset] [n] または history -anrw [filename]>
 (( expression ))                                                 if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]>
 . filename [arguments]                                           jobs [-lnprs] [jobspec ...] または jobs -x command [args]
 :                                                                kill [-s sigspec | -n 
(省略)

子プロセスの終了を待つ:wait

シェルから起動しているプログラムが終了するのを待つ。PIDを指定すればそのプロセスが終了するのを待ち、引数を指定しなかった場合はバックグラウンドで起動したすべてのプロセスの終了を待ち、終了ステータスとして0を返す。

#!/bin/bash
command1 &
pid1=$!           # command1のプロセスIDを変数pid1に格納
command2 &
pid2=$!           # command2のプロセスIDを変数pid2に格納
command3 &
wait $pid1 $pid2  # command1、command2の終了を待つ
command4

代入不可の変数:readonly

変数の属性を読み込み専用に変更する

変数のスコープを指定:local

シェル関数内において、そのシェル関数とその子関数内でのみ有効な変数を生成する。 変数のスコープ(有効範囲)が関数内であることを除けば、コマンドの意味や書式はdeclareと同じ。

オプション解析をしたい:getopts

while getopts a:h OPT
do
    case $OPT in
        a) SOMEVAL=$OPTARG
            ;;
        h) help
            ;;
        *) help
            ;;
    esac
done

shift $(( $OPTIND - 1 ))
TARGET=$1

ファイルの種別判定

$ alias aliasname='echo foo'
$ type aliasname
aliasname は `echo foo' のエイリアスです
$ function functionname { echo foo; }
$ type functionname

ヒアドキュメントで行頭のタブを無視

ヒアドキュメントを書くときにインデントでタブを入れるが表示には不要なときに使える 「<<」ではなく「<<-」にするだけで使える。 スペースは取ってくれないので注意

$ cat test.sh 
#!/bin/bash

usage()
{
    cat <<- _EOF
    aaa
    bbb
 c # これはスペースなのでだめ
    _EOF

    exit 1
}

usage

$ bash test.sh 
aaa
bbb
 c

余談:組み込みコマンドって何?

組み込みコマンドとはbash自体に実装されているコマンドであり、bashの振る舞いを変更したり、制御構文として機能したりするコマンドが用意されている。 組み込みコマンドと外部コマンドどちらにも存在するコマンドとして有名なのがechoです。基本的にシェルスクリプト内でechoを実行すると組み込みコマンドが呼ばれる仕組みとなっています。これは高速化が目的です。 以下のように外部コマンドと組み込みコマンドの差を見てみました。結果は一目瞭然。組み込みコマンドの方が高速ですね。

# 組み込みコマンド
$ time (for ((i=0;i<10000;i++)) ; do echo "aaa bbb ccc" >/dev/null; done)
real    0m0.095s
user    0m0.054s
sys 0m0.039s

# 外部コマンド
$ time (for ((i=0;i<10000;i++)) ; do /bin/echo "aaa bbb ccc" >/dev/null; done)
real    0m13.287s
user    0m7.575s
sys 0m2.242s

まとめ

意外と忘れがちなことを列挙してみた。 便利な機能はまだまだたくさんあると思うのでぜひとも教えてください

参考リンク

検索ではあんまり出ないbashの便利技 シェルスクリプト (Bash) では組み込みコマンド set を活用しましょう bash - 組み込みコマンド