Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

bashのあまり知られていないけど便利な話

この記事はQiitaで公開されていました

~*を展開する場合はクオートしない

クオートされた文字列の中にある~はそのまま文字として扱われる。扱いは*と同じようにみえる。

$ echo ~/bin
/Users/kadota/bin
$ echo "~/bin"
~/bin

$ echo *.txt
hello.txt
$ echo "*.txt"
*.txt

$*$@の違い

基本的には同じ動きをするが、クオートの中で使うと文字区切りの扱いが変わる。

# star.sh
for i in $*; do echo "'$i'"; done

# star-quote.sh
for i in "$*"; do echo "'$i'"; done

# atom.sh
for i in $@; do echo "'$i'"; done

# atom-quote.sh
for i in "$@"; do echo "'$i'"; done

スペース($IFS)を含む場合の例。

$ bash star.sh 1 "2 3" 4
'1'
'2'
'3'
'4'
$ bash atom.sh 1 "2 3" 4
'1'
'2'
'3'
'4'
$ bash star-quote.sh 1 "2 3" 4
'1 2 3 4'
$ bash atom-quote.sh 1 "2 3" 4
'1'
'2 3'
'4'

間接参照

rcでは変数の値を変数名として使うと便利だけど

% a=foo
% foo=1
% echo $$a
1

bashはそのまま実行すると、$$が先に解決されてPIDになってしまう。

$ a=foo
$ foo=1
$ echo $$a
1136a

正しくはこちら。

$ echo ${!a}

[[[test

シェルで分岐をする場合によく使うこれ。[testと同じコマンドなのでどちらを使ってもいい。

# if test $a -gt 1でも同じ

if [ $a -gt 1 ]
then
    echo $a greater than 1
else
    echo $a less than or equal to 1
fi

だけどこの2つはシェルとは別のコマンドなので、空白文字や空文字の場合に注意しないといけなくてめんどくさい。

$ a=""
$ [ $a = "" ] # unary operater expectedエラー
$ [ "$a" = "" ] # ok
$ a="1 2"
$ [ $a = "" ] # too many argumentsエラー
$ [ "$a" = "" ] # ok

bashを使うのであれば、今は常に[[を使うのが良いと思う。

$ a=""
$ [[ $a = "" ]]
$ a="1 2"
$ [[ $a = "" ]]

正規表現でマッチ

[[=~演算子を使うと正規表現が使える。

text='a b c'
if [[ $text =~ ^a.*$ ]]
then
    echo match
fi

ここでは、安易に正規表現をクオートしてはいけない。クオートすると、正規表現で特別な扱いされる文字が全てバックスラッシュでエスケープされる。シングルクオートとダブルクオートでどちらも同じ。

[[ $text =~ '^a.*$' ]] # \^a\.\*\$と同等

正規表現の途中でスペース等を含ませたい場合は、その部分だけクオートするとよい。

[[ $text =~ ^'a b'.*$ ]]

実際にどのようなエスケープがされるのかは、set -xしておくとエスケープされた後の正規表現を見ることができる。

if&&||

ifの代わりに、&&||が使える。エラー処理はこちらを使った方が読みやすい。

$ test -f file || echo 'file not exist' >&2 && exit 1

配列

簡単な使い方。

# 初期化
a=()

# 末尾に追加
a=("${a[@]}" test)

# 先頭に追加
a=(test "${a[@]}")

# 内容を列挙
echo "${a[@]}"

単純にecho $aすると、先頭の要素しか対象にならない。

配列に特定の要素が存在するか

専用の機能は無いが、[[コマンドの=~演算子が使える。

a=(cafe beef)
if [[ " ${a[@]} " =~ " cafe " ]]
then
    echo found
fi

=~正規表現でマッチさせる演算子なので、スペースで要素の区切りを表現してあげなければならない。

計算する

$((10 + 1))のように書くと外部コマンドなく計算ができる。

$ N=10
$ echo $((10 + $N))
20
$ echo $((1 * 3 - 1))
2

数字の前に基数を書ける。

$ echo $((16#0F + 8#10))
23

((expr))だけの場合は評価をするだけなので、条件式に使うと便利。

$ ((10 > 8#10)) && echo ok
ok

trap

trap は、SIGINT, SIGHUP などの他に以下の擬似シグナルが存在し、それらにハンドラを割り当てられる。

擬似シグナル 意味
ERR コマンド実行結果がエラーなら都度実行される
EXIT コマンドを終了したときに実行される
DEBUG コマンド実行のたびに都度実行される

一時ファイルを必ず削除したい場合はEXITに登録しておくと良い。利用可能なシグナルはtrap -lで表示できる。

変数展開

bashは通常のshと比べてとても多くの展開式が使える。

変数置換

古くからある展開式。

書き方 意味
${v:-text} vが未定義ならtextに置換
${v:=text} vが未定義ならtextをセットして置換
${v:?text} vが未定義ならtextを表示して終了
${v:+text} vが 定義されていたら textに置換

変数の少し変わった扱い方。

書き方 意味
${#v} 文字列の長さ、または配列の要素数に展開
${!v} vの値を変数名として展開

文字列置換

書き方 意味
${v%glob} vの末尾から最短マッチした部分を削除
${v%%glob} vの末尾から最長マッチした部分を削除
${v#glob} vの先頭から最短マッチした部分を削除
${v##glob} vの先頭から最長マッチした部分を削除
${v/s1/s2} vに含まれるs1を1つだけs2に置換
${v//s1/s2} vに含まれるs1を全てs2に置換

bash 4以降から利用可能な式

便利な式だけど、macOS High Sierraはまだ3系なので利用箇所には注意。

書き方 意味
${v^} 先頭の文字を大文字にして展開
${v^^} 全ての文字を大文字にして展開
${v,} 先頭の文字を小文字にして展開
${v,,} 全ての文字を小文字にして展開
${v~} 先頭文字の大文字小文字を反転
${v~~} 全ての文字で大文字小文字を反転

オプションを扱う

色々方法はあるけどgetoptsがいちばん面倒がなくて良いと思う。以下のコードでは、-fに引数がない・不明なオプションが与えられた時に、getoptsがエラーを出してくれる。

while getopts xf: OPT
do
    case $OPT in
    f)    filename="$OPTARG" ;;
    x)    flagx=1 ;;
    esac
done
shift $((OPTIND - 1))

だけど不明なオプションでもエラーとしたくない場合は少しめんどくさい。getopts最初の引数先頭に:をつけるとエラーにならない代わりに、特殊なオプション文字として:または?のどちらかが入っている。

文字 意味
? 不明なオプション文字が使われた
: 引数が必要なオプションだけど無かった

このように使う。

while getopts :xf: OPT
do
    case $OPT in
    :)  echo usage: $(basename $0) [-x] [-f file] >&2
        exit 1 ;;
    x)  flagx=1 ;;
    \?) x=$((OPTIND - 1))
        echo unknown option: ${!x} >&2
        exit 1 ;;
    esac
done
shift $((OPTIND - 1))

不明なオプションだった場合、具体的にどの文字が使われたのか調べる方法は特に用意されてなさそうだったので、上記のように自分で計算する。

先頭のアレ

シバン(shebang)というらしい。

#!/usr/bin/env bash

bashを使う場合、環境によってパスが違うかもしれないのでenvでラップするのをよく見かける。