それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

bashでコマンドの終了ステータスの意味と見方

終了ステータスは重要。

じつはあまり顧みられない、終了ステータスですが「条件分岐」の条件になってる。そのことは以前書きました。今回はこの「異常終了」のコードを詳しく見ていきます。

bashでは次のように定義されています。

  • $? == 0 が正常終了
  • $?> 0 がエラー終了

になります。ただし、bashでは汎用的なエラーコードの指針が決まっています、またエラー終了はコマンドごとに意味があります。

Bashにおける終了コードの意味

マニュアルの抜粋からです。Exit Codes With Special Meanings

http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF

数字 意味 コメント
1 各種エラー 何でもかんでも放り込む。ゼロ除算など.letの計算不能など
2 使用方法の誤り ユーザーの所有権エラーなどが含まれる。
126 実行権限エラー 実行権(x)のエラー
127 コマンドが無い typo、コマンド見つからないとき
130 Ctrl+C で止めた Ctrl+C(シグナル2)で止めると 128+2で130 になる

*1

bashではエラーを 1-255の数値で返すことになっています。その返す数値に上記の表の指針が定められています。

コマンドごとに終了コードに意味をもたせることがある。

bashの解釈とは別に、エラー終了コードに意味を与えているコマンドが有ります。

幾つかのコマンドについて見ておきましょう。とくにgrep は行が見つからない「正常終了」も「1」を返すので注意が必要ですね。

grep の場合

EXIT STATUS
     The grep utility exits with one of the following values:

     0     One or more lines were selected.
     1     No lines were selected.
     >1    An error occurred.

ping の場合

EXIT STATUS
     The ping utility exits with one of the following values:

     0       At least one response was heard from the specified host.

     2       The transmission was successful but no responses were received.

     any other value
             An error occurred.  These values are defined in <sysexits.h>.

ls の場合

DIAGNOSTICS
     The ls utility exits 0 on success, and >0 if an error occurs.

コマンドごとの意味

すべてをここに書き出すのは到底無理なのです。が、man で調べられます。

先程の例のgrepであれば、man grep して STATUS をマニュアル内部を探せば出てきます。ぜひ一度見てみてください。

exit ステータスコードを 0 以下 255 以上にするとどうなるのか

私達が関数やコマンドファイルを作るの時の戻り値を自由に決められるとは言え、無茶をするとどうなるのでしょうか。見ておきたいと思います。ここで使うexit コマンドは関数内部で使われ、終了しステータスコードを返すために使われます。関数内部であれば return とほぼ同じ意味ですが、 exit はそれより強くその場で実行を止めます。

takuya@Desktop$ (  exit 255 ) ; echo $?
255
takuya@Desktop$ (  exit 256 ) ; echo $?
0
takuya@Desktop$ (  exit -1  ) ; echo $?
255

あ・・・・0じゃないのにゼロになる・・・

256以上の終了コードは、 mod 256 で丸められます。 なのであまり大きいステータスコードを使っても意味が無いと思います。

takuya@Desktop$ (  exit 512 ) ; echo $?
0
takuya@Desktop$ (  exit 513 ) ; echo $?
1
takuya@Desktop$ (  exit 514 ) ; echo $?
2

参考資料

http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF

http://qiita.com/Linda_pp/items/1104d2d9a263b60e104b

ご指摘感謝です!

*1:マニュアルによると128/128+n があるのですが。128 + invalid signal と 128 invalid exit code は、再現できなかったため記述を省略しました。kill 自体が不能でエラーになる。なのと、exit 'a' は数字引数にしろと怒られて終了コードが1だった。

bashの条件分岐 : if 以前の話

bashの使い方:条件分岐の話

if / while / until / case の話をしようと思っています。とても大変なので、できるだけシンプルにストーリーにしてわかりやすく書こうと思っています。

最初は条件分岐の話です。条件分岐はbashの理解に大きな影響を与える話なのですが、書籍でも皆によく読み飛ばされてて残念な感じ。わたしも読み飛ばしてて後で痛い目にあった。

条件分岐の話をうまく書けてるかどうかは、bashの紹介記事や書籍の出来を大きく左右すると思います。

条件分岐以前の話

こんな話が出てきます。

  • 終了ステータス・コード
  • && による条件分岐
  • || による条件分岐
  • if を使わない複数行条件分岐

条件分岐 if 以前の話

bash でif条件分岐をするまえに、知っておくべき必須知識があります。

シェルの条件分岐は終了ステータスで判断するということです。

シェルがコマンドを実行すると、実行結果は終了ステータス・コードで返されます。true / falseではありません。

他のプログラミング言語が true/false で判断するの対し、シェルでは終了ステータスが0か否かで判断されます。

大事なので2回言いました。

終了ステータスについて

終了ステータスは、0かそれ以外で判断されます。

終了ステータス 終了で意味 条件での意味
0 正常終了 true
1 異常終了 false
2 異常終了 false
- 中略 false
N 異常終了 false

は 正常終了で、正常終了なのでtrue と判断されます。0以外は異常終了です。通常は正の整数で表現されます。

とくに、1がFalseで0がTrue になるところは特徴的なので忘れないでください。絶対に忘れないでください。

終了ステータス 終了で意味 条件での意味
N == 0 正常終了 true
N >0 異常終了 false

終了ステータスの確認

終了ステータスは、$? の変数で確認できます。。 $? に直前に実行されたコマンドの実行結果が格納されます。

つねに終了ステータス0を返す /bin/true を用いて調べてみましょう。

この1行で書く書き方を覚えておいてください。2行で書くのは不便なのでここから先は1行で書きます。

例:正常終了(1)
$ /usr/bin/true
$ echo $? #=> 0
例:正常終了(2)・・・1行記述
$ /usr/bin/true ; echo $? #=> 0
例:異常終了
$ /usr/bin/false; echo $? #=> 1
例:コマンドが見つからない
$ NoExistCommand ; echo $? 
-bash: NoExistCommand: コマンドが見つかりません
127

終了ステータスを確認する方法を覚えておく。

紹介した終了ステータスを覚えておく方法は、覚えておくと便利です。

次の形で確認するとお覚えておくといいでしょう。

$ COMMAND ; echo $?

シェルスクリプトif の条件がただしいか。これらの目的のため、切り出して確認することも多いです。

このようにシェルスクリプトでは、if の中身だけを実行することが可能です。

例:if 条件を確認する
$ [[  $name = takuya ]] ; echo $?

原始的な条件分岐&&||

まだif は使いません。

&& で 終了ステータスを活用する AND条件を書くことが出来ます。

一番シンプルな条件分岐が && による条件分岐です。今回はif の条件分岐に入る以前の話です。if は使いません。

&& だけでも十分なコマンドの分岐を書くことが出来ます。

&& と終了ステータスを使って、コマンドを続けて実行する方法を見ておきます。

COMMAND1 && COMMAND2

これは、COMMAND1が実行され、正常終了(戻り値0)になったら、COMMAND2 が実行されます。

また、 COMMAND は 複数行で構成することもできるので { } を使って次のように書くことも出来ます。

COMMAND1 &&  { COMMAND2-1; COMMAND2-2; }

次のサンプルでは、ディレクトリの *.jpg を列挙して移動します。これはjpg ファイルがない場合で、ls *.jpg が失敗すると、&& 以降 のコマンドは実行されません。。

この書き方は testコマンドと併せて使うと便利です。この程度なら、まだ if 無くても大丈夫です。

test コマンドは、条件分岐のチェックパターンでまた紹介します。

例:&& でコマンド組み合わせる
ls *.jpg && mv *.jpg /Users/takuya/Pictures 
例:&& とtestの組み合わせ。
# ファイルが有れば中身を表示する
test -e /etc/passwd && cat /etc/passwd

|| でコマンドをOR実行する

OR でコマンドを実行することが出来ます。使い方は次のとおりです。

COMMAND3 || COMMAND4

読み方としては、COMMAND3 を実行し、成功したら次の行へ、失敗したらCOMMAND4を実行して次の行へ。という意味です。

繰り返しになりますが、ここで AND / OR と呼んでいるのは「終了ステータス」のAND・ORです。

ファイルなければ作る
test -e my-sample.txt || touch my-sample.txt

|| 失敗したら処理をやめる

|| を使えば「失敗したら処理をやめる」ということ出来ます

takuya ユーザーがいなければexitして処理をやめる
id takuya || exit 

まとめ

if の条件を考えるまえに、以下のことを覚えておくと、スッキリします。迷わずに住みます。

終了ステータスで判断するので、かんたんな条件ならif 無しで書くことが出来ます。また、そのほうがわかりやすいこともあります。

コマンドの終了ステータスコードで「判断する」は大事なので絶対覚えておいてください。

大事なので10回位書きました。

もちろん if を書いたほうが読みやすいので、if を使うほうが良いのでしょうが、if をいきなり使ってしまうと「何を以てTRUEか」という事を見逃しがちです。 &&||で分岐は出来ます。敢えて if を使わないで十分な条件分岐が出来ることを紹介しました。

おまけ

if 文を使わずとも、かんたんな条件分岐なら、 &&{ } を使って書くことが出来ます。

test -e /etc/passwd && { echo /etc/passwd は存在しました; echo ファイルの末尾を表示します; tail /etc/passwd ;}

さらにこれは、{ } 内部で改行が許されるので、次のようにかけます。

test -e /etc/passwd && {
   echo /etc/passwd は存在しました;
   echo ファイルの末尾を表示します;
   tail /etc/passwd ;
}

次の bash の条件分岐 if の記事では、この改行を含めたコードを、 「プログラミング」っぽく if を使うようにしておきたいと思います。

おまけ

この記事で紹介した終了ステータスを使ってANDでみることは、bashrcなどで盛んに行われます。

例:takuyaのbashrc からの抜粋
[ -z "$PS1" ] && return
[ -f ~/.bash_aliases ] && source ~/.bash_aliases
(( $BASH_VERSINFO >= 4 )) && shopt -s globstar

コピペして肥大化したbashrcをよく見るといたるところで使われていると感じると思います。

参考資料

bashの引数の扱い方(関数の引数・位置パラメータ)についての基本をまとめ

位置パラメータについて。

bashの使い方 のシリーズ。今回は、位置変数(位置パラメータ)について見ていきたいと思います。

引数をオプションとして使いたい人は。→ getopts の記事を参考にしてください

ここでは、bash の引数へ配列としてアクセスして関数やスクリプトでどう扱うかを考えていきます。

引数のことは、positinal parameter ・位置パラメタとして扱われます。

引数:位置パラメータとは

位置パラメータは、bashには positional parameter と書かれています。位置変数・位置パラメータ・ポジショナルパラメータなどと呼ぶことが有ります。

位置パラメータを一言で言えば、「引数を格納した配列」のことです。

位置パラメータは配列

配列だけど、ちょっと特殊な配列で、名前がありません。無名な配列です。

関数の引数やシェルスクリプトの引数に使われるので、いちいち名前をつけてないということらしいです。

位置パラメータのサンプル(シェルスクリプト

位置パラメータをどのように使うのか少し見ておきたいとお思います。

位置パラメータを扱う例
#!/usr/bin/env bash 

echo $0 
echo $1
echo $2 
echo $3
上記を実行した結果
$ bash sample01.sh a1 a2 a3
sample01.sh
a1 
a2
a3

位置パラメータへのアクセス

位置パラメータは名前のない配列なので、配列名前の箇所を省略したとてもかんたんな書き方ができてることがポイントです。

変数 意味
$0 実行中のファイル名
$1 1番めの変数
$2 2番めの変数
$N N番めの変数(0-9)だけ
${N} N番めの変数、10番以降は必須
$@ $1..$N までの引数配列すべて
$* $@と同じ。ただしIFS区切り
$# 配列個数:つまり引数の個数
${@: X: n} x番目からN個のスライス

ただし、10番目以降は $10$1 0 と解釈されてしまうので、${10} のようにブレイスで囲う必要があります。

また、$@ の配列は $1..$N までで、$0 は含まれません。

もちろん配列なのでスライスで部分配列を切り出す事もできます。

関数・シェルスクリプトの引数の違い

関数とシェルスクリプトでは、引数の扱いはほぼ同じですが、1点だけ注目すべき違いが有ります。それは$0の違いです。

関数の例 sample02.sh
function my_func(){
 echo $FUNCNAME
 echo $0
}
sample02
sample02.sh の実行結果
$ bash sample02.sh 
my_func
sample02.sh
関数とシェルスクリプトの違い

関数中でも、$0 は 関数の書かれているファイル名です。 シェルスクリプトでは、$0 は自分自身のファイル名になります。

もし関数中で、自分の名前が必要なときは $FUNCNAME を参照します。

あと、interactive shell (RPEL)で実行中は bash になります。

位置変数は1から始まる、いちだけに。と覚えておくと便利なのかもしれません。

シェルで実行していた場合
$ function sample02(){
>  echo $FUNCNAME
>  echo $0
> }
$ sample02
sample02
-bash

名前がありませんが、名前はあります。

位置パラメータには名前がありませんが、わかりやすいように名前をつけてアクセスすることも出来ます。

それが、次の場合の arg です。

自分で別名の変数に保存しておくことも出来ます。

位置パラメータをループする

位置パラメータだけに許された記法があります。それがとても簡便な方法です。

位置パラメータ特有のfor
for arg; do
    echo $arg
done

とりあえず、こうやって簡便にARGVを全部回せる方法があることを知っておくといいと思います。

これの仕組みは、省略記法です。argは単なる変数名で、変数名はなんでも大丈夫です。

for i ; do 
    echo $i;
done

もちろん for .. infor (( i=1;i<max;i++)) ;でループするのもいいと思います。*1

引数をすべて、なくなるまで処理

ファイル名を複数個で引数として受取り、それを逐次処理するような場合には上記のようなループ構造が便利です。 forでは、どこまで進んだかわからなくなる。そのため引数をキュー的に使えたほうが便利です。

古くからの先人は shiftを使って、処理することがあるようです。

while ループであるだけ回す
while [ "$1" ]
do
    echo "$1"
    shift
done

これは、$1 が先頭なのを利用しています、$1 を使い終わったら shift$@を進めています。 shift は引数(位置パラメータ)を処理すると暗黙的に決められています。

上のコードを一つずつ見ていくと次のとおりです。

  • while [ "$1" ] 終了条件 。$1 が空でないとき
  • echo $1 $1 を出力する
  • shift 引数を一つすすめる

わたしは、shiftshift $@ のの意味だと思うようしています。

位置パラメータを使うとループがシンプルになる。

while : ; do shiftfor arg のように 位置パラメータ特有の記述方法をすることでよくある処理が手早く終わらせるられるようになっています。

bashシェルスクリプトと関数はいつも「不定個」の引数を取ることになっています。そのため、ループはあるだけ回すのが基本になっています。他のプログラミング言語が関数を一種の型のように扱い、引数と戻り値を厳密に決めていることとは大きく異なります。

位置パラメータを自分で作れないの?

このような便利な位置パラメータですが、自分で作れないのでしょうか。

配列を処理するときに、位置パラメータを使えると便利そうです。これは出来ます。

set を使います。

set を使えば、位置パラメータをその場で好きな配列(文字列)に上書きすることが出来ます。

set の使用例
set I like youtube.
set "he hates youtube." # クオートも

set で囲むことで、囲った部分を配列に展開し、positional parameter に展開することが出来ます。

位置パラメータを自分で使う サンプル
set I like youtube.
for arg; do
    echo $arg
done
サンプルの実行結果
takuya@Desktop$ set I like youtube.
takuya@Desktop$ for arg; do
>     echo $arg
> done
I
like
youtube.

set で位置パラメータを使うときの注意

set をしてしまうと、スクリプト、関数のもともとの引数は上書きされます。

なぜならその空間で使える位置パラメータは1つだからです。これは名無し変数なので仕方のないことです。

set が分かれば、function の引数が理解できる。

set の使い方がわかったので、関数と見比べ見ると。

関数の引数はその空間でset しているだけだと理解できそうです。

function sample () {
   # ここはset済みしか見えない
}
sample a1 a2 a3 # ここで 引数string "a1 a2 a3"をset している

引数を解析する

引数は配列で、ちょっと特殊な配列で、位置パラメータということがわかりました。

ここで覚えたことを使っていけば、かんたんな引数処理はすぐ書くことが出来ます。

引数があるときは引数を、ないときはSTDINから読む

引数で渡したり、コマンドから渡したり、両方に対応できるシェルスクリプトも作れますね。

# STDINか引数かどちらも同じに使える例
if (( $# >  0   )); then 
   echo $@
else
  echo $( cat - )
fi
上記の実行例。
takuya@Desktop$ bash  test.sh
abc
CTRL+D
abc
takuya@Desktop$ bash  test.sh  aaa
aaa

cat の入力は CTRL+D ( EOF ) で入力終了できるので、実際入力するときはCTRL+Dで入力を終了してください。

引数の処理を使ってコマンドオプションを処理する

-a-b のように指定したオプションが来たときだけ処理をしてそれ以外を捨てることも出来ます。

while :
do
    case "$1" in
      -f | --file)
      file="$2"   # You may want to check validity of $2
      shift 2
      ;;
      -*)
      echo "Error: Unknown option: $1" >&2
      exit 1
      ;;
      *)  # No more options
      break
      ;;
    esac
done

ここで while ::何もしないtrueを返すコマンドです。なので無限ループです。内部でshift や exit /brek をしているので、引数があるだけループします。

つまり、このスクリプトは、以下のような処理をすることが出来るようになります。

sample -f a.txt -f b.txt  -f c.txt

オプションの解析は getopts

もっと細かいオプションの解析をするには getopts が使えます。

getopts はシェル組込み関数でいつでも使えます。ヘルプは help getopts で見ることが出来ます。

私も過去に紹介しているので、そちらも参考にしてください

ちなみに、getopt は /usr/bin/getoptです。

2016-12-28

if の条件が、文字列比較になってたので、数値比較に直した

関連資料

takuya-1st.hatenablog.jp

参考資料

*1: 補足ですが for e in $@ で回したときも $0 は出てきません

bashのcd/pwd/pushd/popdのまとめ

cd コマンドを見直す

cd : change directory のコマンドを少し見直してみたいと思います。

cd コマンドの基本的な使い方

cd コマンドでは非常によく使う書式があって、それは以下のとおりだと思います。

コマンド 意味
cd 何も入力しない cd ~ と同じ cd $HOME される
cd ~ cd $HOME と同じ、~が$HOMEと同じ
cd - 一つ前のディレクトリにもどる
cd .. 一つ上のディレクト
cd /var 指定したディレクトリに移動する

cd - 便利すぎてやばい

cd - と打つと、一つ前のディレクトリに移動します。一つ前のディレクトリがないときは何も起きません。

cd - は何を見ているのか

cd -cd $ODLPWD *1と同じ意味になります。$OLDPWD は一つ前のディレクトリを記憶している環境変数です。

cd - による作業ディレクトリとの移動

cd - さえ知っておけば、楽になることもあります。

cd app/views
cd ../../config
cd - #  app/views
cd - # config

2つのディレクトリを相互に行き来するなら cd - だけで十分です。

pushd / popd / dirs

pushd / popd は ディレクトリを dirs スタックに積んでおいて、 push dirs / pop dirs することで、ディレクトリ行き来することが出来ます。3つ以上のディレクトリを行き来するなら、こちらも便利です。ただ、積み過ぎて混乱することも多いのですが。

pushd / popd / dirs はそれぞれ、次のような動作をします。

  • pushd で、cd したあとにdirs に詰む
  • popd で、dirs から取り出して cd する。
  • dirs で、スタックを確認
    例 /etc/apache2, /var/www, /var/log/apache2 を相互に行き来するとき
takuya@:www$ pushd /etc/apache2
/etc/apache2 /var/www
takuya@:apache2$ pushd /var/log/apache2/
/var/log/apache2 /etc/apache2 /var/www
takuya@:apache2$ popd
/etc/apache2 /var/www
takuya@:apache2$ pwd
/etc/apache2

またpushd / popd には、cd で一つ前2つ前、3つ前に戻ることも出来ます。

dirs 間を移動するだけの例
cd -$1
cd -$2
cd -$3

popd してしまうと消えてしまうので、 cdだけしたいときは上記のようにします。

使い勝手がいいとはいえない popd/pushd

pushd / popd は積んでいくには便利ですが、毎回ペアを使わなくてはいけないです。 cd - と組み合わせしまうと、ちょっと面倒になります。

なので、pushd を使うときは popdをやめて cd -$1 cd -$2 cd -$3 したほうが便利だと思います。

cd -のように履歴を使うほうが楽だと思います。

コマンド履歴のように移動履歴として使うには、少し不便かもしれません

個人的には、 cd -- cd --- みたいなのがアレば最高なんですが。

dirs からの削除

popd -n を使えば、移動せずに「スタックから削除」することが出来ます。

指定したスタックを削除することも出来ます。
popd +2
popd +3

ただ、やはりpushd / popd はシェルスクリプトで省力化するためだともいます。個人的には、interactive shell で多用するものではないような気がします。

そこで自動pushdも考えられるのだけれど。。。

zsh のシェルのオプションには、自動でpushd してくれる便利な機能があります。

bashではaliasするだけで実現できます。

alias cd=pushd

流石にコレは、パニックになりそう。

環境変数CDPATH

CDPATH は、cd コマンドの移動先を検索するパスになります。

CDPAH に登録しておくと、ディレクトリ名をCDPATHから探してくれます。

CDPATHの例
takuya@:www$ CDPATH=.:/var
takuya@:www$ cd log
/var/log

ただしcd 履歴やpushdの代わりに使うのは結構難しいとおもいます。事前設定やシェルスクリプトによる自動展開をつくることになり、そこまで便利な機能でもないかと思います。

ある程度省力化は出来ます。しかし同名のフォルダでかぶってしまうと、指定がめんどくさかったり、CDPATHを毎回確認しなくちゃいけなかったりするので、大量に使うのは不可能です。

本当に毎日のように使う定型業務とくに、仕事でログだけ見る仕事など運用担当者には重宝すると思いますが。

そこで、毎回入力を楽にする。

シェルのオプションには、次のようなオプションが用意されていて、自動的には設定されませんが、自分の任意で使うことが出来ます。

autocd         
cdable_vars    
cdspell        
autocd

autocd は、ディレクトリ名だけが入力されたとき、ディレクトリ名をコマンドのように「実行」します。cd /var/wwwの代わりに/var/www だけで移動できるようになります。ただしREPLとして実行しているときだけです。シェルスクリプトでは使えません。

cdable_vars

cd の引数でパスが見つからなかったら変数としてみなす。 つまり

app_view=app/view
app_ctrl=app/controller
cd app_view
cd app_ctrl

と出来ます。ダラー$が が省略できます。

cdspell

cdspell はスペルミスを見つけて、それっぽいディレクトリに移動してくれます。

takuya@:~$ cd /varr/wwww
/var/www
takuya@:www$ cd /varr/wwww
/var/www
takuya@:~$ cd /vaaaar/wwwwww
-bash: cd: /vaaaar/wwwwww: そのようなファイルやディレクトリはありません

あまり激しく間違うと見つけてくれませんが、数文字程度のtypo なら見つけてcd してくれます。

shopt で設定をOn/Off

shopt で設定をONにするときは

shopt -s cdspell    # set すると覚える

shopt で設定をOFFにするときは

shopt -u cdspell    # unset すると覚える

shopt で設定を確認するときは

shopt cdspell # 何もつけない

この辺はshoptの記事 でもう少し詳しく触れたいと思います。

glob との組み合わせ

一番便利なのはコレじゃないでしょうか。glob つまり * でめんどくさい入力を省略できます。

takuya@:www$ cd /v*/w*
takuya@:www$ pwd
/var/www

まぁ{TAB} を押してもいいのですが、cd 移動がコマンド履歴に残せる便利かもしれません。*2 日本語キーボードなら * が小指で押しやすいので利用は便利だと思います。

cdspell だけじゃなく dirspell もつけたら楽かもしれません

参考資料

おまけ

検索中に見つけた autojump という、移動管理ツールが便利そうです

https://github.com/wting/autojump

*1: 正確には cd $OLDPWD; pwd; です。

*2: shopt

bashの配列のまとめ(定義・代入・参照と取得・ループ)

bash も配列を扱える

シェルスクリプトで配列を扱えるので 配列を扱ってみる。

今回は、サンプルのために、変数名を大文字にしています。もちろん小文字も使えます。

大文字は、おもに環境変数スクリプト間やプロセス間で値を渡すときに使うことが多いようです。

配列の定義

配列の定義には基本的に2つあります。

bash の配列の定義の例

declare ARR=()
declare -a ARR # または
arr=() #無精する場合

配列の定義を改行する場合は、文字列でクォートが手っ取り早い。

arr=("a b
  c
")

配列の削除

定義した配列は削除できます。

unset -v ARR 
unset -v ARR[@]  # または
unset -v ARR[*] # または

配列の初期値を指定した宣言

初期値を指定した宣言をすることで明確になります。

ARR=( a b c d e  )

区切り文字は「空白」文字列の続いたものです。

もっと詳細にいえば、区切り文字は環境変数 IFS で指定されたものになります。

配列の全要素を見る

配列の要素の中身を全部見ることが出来ます。

ARR=(a b c)
echo ${ARR[@]} 
echo ${ARR[*]} # または

配列の要素数

配列の要素数${#変数名}を使うことで取得することが出来る。

配列の要素数の取得例
length=${#ARR[@]}
length=${#ARR[*]} # または

@* の違い

ARR[@]ARR[*] は、ほぼ同等の機能を持つが、一点だけ異なる。

それは区切り文字。@ は 空白で区切るのに対し、 * は IFS で区切る。

@* の違いの例
$ ARR=(a b c d e )
$ IFS=-
$ echo "${ARR[@]}" a b c d e
$ echo "${ARR[*]}" a-b-c-d-e

ただし、ダブルクォテーションをつけない場合は[@]/[*]に違いが現れない。それはIFS変数が作用するから。

クォテーションをつけない場合だと、IFSで指定した区切り文字で出力されたあとに、IFSで区切って次にのコマンドに渡されるので違いはない。

環境変数のIFSで区切り文字を指定することで、出力と入力を使える。

# [*]にダブルクォーテーション比較
$ IFS=-
$ echo  ${ARR[*]}   a b c d e
$ echo "${ARR[*]}" a-b-c-d-e
# ダブルクォーテーションが無しの例さらに
IFS=#
ARR=(a b c )
echo ${ARR[*]} #=> a b c 

これは、bashでは次のように「順に解釈される」と覚ええおくと理解しやすいです。

IFS='#'
ARR=(a b c )
echo ${ARR[*]} #=> a b c 

# 1. echo a${IFS}b${IFS}c # IFS付きで変数処理され
# 2. echo a#b#c    # IFS付の文字列となり
# 3. echo a b c    #IFS=# で区切られた引数になる。コレがコマンドに渡される。 

配列の要素を走査する。

配列の指定番目の値を取り出すには インデックス番号を使う。

配列の要素を見ていく例。
$ ARR=(a b c )
$ echo ${ARR[0]}   # => a
$ echo ${ARR[1]}   # => b
$ echo ${ARR[2]}    # => c
$ echo ${ARR[3]}    #  なにもないので空っぽが出る

配列の逐次処理

配列の要素の中身を取り出して、順番に見ていくには。 for ループを使うのが楽

for e in ${ARR[@]} ; do 
  echo $e
done

配列の要素を削除する

配列の指定番目の要素を削除するのは、 変数の削除と同じく unset を使う。

削除した配列はもとの配列からインデックスが欠損したものとなる。

配列の要素の削除の例
unset -v ARR[1]
削除後の配列の構造
$ for idx in ${!ARR[@]} ; do    echo " [$idx] => ${ARR[$idx]} " ; done
 [0] => a
 [2] => c
削除後の配列の再度採番する

Reインデックスして、リナンバーするには、もう一度定義し直しが早いと思う

$ B=( ${ARR[@]} )
$ for idx in ${!B[@]} ; do    echo " [$idx] => ${B[$idx]} " ; done
 [0] => a
 [1] => c

配列の要素の追加。

要素の追加も、再度配列を再生成する事になりそうですが、Bashには専用の演算子が用意されています。

専用演算子 +=() がイメージしやすくていいと思います。

専用演算子 +=() について
ARR+=(c) # 要素の追加
配列の追加(再生成)の例
$ ARR=(a b)
$ ARR=(${ARR[@]} c) # 末尾に追加した
$ echo ${BRR[@]}
a b c
配列の追加(オペレータ)の例
$ ARR=(a b)
$ ARR+=(c) # 追加した
$ echo ${ARR[@]}
a b c
push / unshift

配列に push / unshift するために要素のインデックスを指定するのは無理なので、配列の再定義を使う

## push 
ARR=(${ARR[@]} 'element') # 末尾に追加(push)した
## unshift 
ARR=('element' ${ARR[@]} ) # 先頭に追加(unshift)した

配列の変数アクセス ${ARR} と $ARR

配列に、{ } をつけずにアクセスした場合、配列の先頭の要素が返ってくる。古くからの人にはおなじみなのかもしれないが、私は迷うので使わないことしてる。

$ echo ${ARR[@]}  # => a b c
$ echo $ARR  # => a

個人的にあまり使わないので、どういう役目があるのか知らない。

要素の切り出し スライス

配列をスライスしたい。こんなのよくあることなので要素のスライスする方法を知っておきたい。

要素をスライスするには、専用の記法 {ARRAY[@]:X:n} を使う。X はインデックス n はほしい個数

配列のスライスの例
$ ARR=(a1 a2 a3 a4 a5)
$echo ${ARR[@]:0:2} #=> a1 a2

ちなみに、-1 などのプログラムで定番のも使える。

配列の最大個数より大きな個数を指定しても、配列の最大個数までしか取り出せない。

どうしても末尾がほほしいときは計算する ${ARR[@]:0:((${#ARR[@]}-1))} このように取り出す。

配列を末尾からスライス:追記

bash 4 からの機能で、配列を末尾から参照することも出来るようになっています。

${ARR[@]: -1} # ' -1 ' スペースをつけて -1 を書く
配列をスライスして先頭だけを消す

先頭を捨ててshift 的なことも出来ます。

ARR=("${ARR[@]:1}")

スライスを応用すれば、こんなことが出来ます。

配列の長さ

配列の長さは $#ARR[@] でアクセスする

ARR=(this is a test of array)
echo ${#ARR[@]}

文:センテンス(words)を自動展開して配列に

配列のデフォルト定義 ARRAY=() は単語を区切るのに使われる。もともと英語はスペースで分けて書くのでそういうもの。

$ARR=( Hello I am takuya  )
$ IFS=,
$ echo "${ARR[*]}"
Hello,I,am,takuya

コマンドの出力結果や、整形された出力などは、かんたんに配列に展開できます。

IFSを使うと 配列の join ( 結合 ) ができる。

${my_arr[@]} だと区切りがスペースになるが、 * を使うと IFS区切り を使うので、IFSを上手に使えば、配列の結合(join) をすることができる。

結合を関数にする例

my_arr=( aaa bbb ccc )
( IFS=, ; printf %s "${a[*]}") ;

これを関数にして、

function join_arr()(  IFS=,; echo "$*" )

なんてこともできる。

function join_arr()(  IFS=,; echo "$*" )

join_arr abc xyz 123 # --> abc,xyz,123

IFSを知れば、配列の定義 a=( aaa bbb ) が、サブシェルとIFSの区切り文字だと気づくので面白い。

ループと併せて使う。for 編

配列はループと併せて使い、繰り返し処理をするために有ります。

そのため、ループと併せて使うのがほとんどだと思います。for ループを知っておけば、十分戦えると思います。

for と併せて使う例 ( for .. in
 for e in ${ARR[@]} ; do echo $e ; done
for と併せて使う例 ( for index in
 for idx in ${!ARR[@]} ; do echo ${ARR[$idx]} ; done
for と併せて使う例 ( C言語スタイル
for (( idx=0; idx< ${#ARR[@]}; idx++ )) ; do echo ${ARR[$idx]} ; done 

while は?

while は、for ループと等価交換可能なので、特に細かい説明必要はないかと思います。

ただ、配列をあるだけ回す無限ループはよく使いそうなので少し例を上げておきたいと思います。

ARR=(a b c)

while (( ${#ARR[@]}  > 0 )) ; do 
  echo $ARR
  ARR=("${ARR[@]:1}")
done

これは、配列を先頭から一つずつ切り出して、先頭の要素を$ARR で参照して、先頭の要素をスライス削除しています。

スクリプトを頭のなかで展開して理解するには。forループとしてはwhileであるだけ回すループのほうがシンプルで理解しやすいかもしれません。

あれ?shift は?位置変数は?

shift は 位置変数配列(コマンドライン引数・関数の引数)のときに使うもので、一般的な配列だけを扱うこの記事には登場させません。それでだろうか、unshift 関数が用意されていない。

連想配列

bash の配列では 連想配列(ハッシュ・dict ) も扱うことが出来ます。それはまた別記事に。

その他

xargs を使ってパイプで書き換えることは出来ません。

2016-12-27 追記

bash の スライスの記述が間違っていました。修正しました。

a=(a b c)
echo ${a[@]: -1}  #=> c

出来ないと書きましたが、出来ました。私の記述方法が誤りでした。

${a[@]:-1} # 動かない
${a[@]: -1} # 動く

スペースが必要でした。

参考資料

Arrays [Bash Hackers Wiki]

2021-02-16

IFS が検索されないので、キーワードを明示的に指定した。

IFS の区切り文字つかった、配列の結合 ( join ) について言及。

bashの強制終了(CTRL+C)検出と後処理/trap

bash でコマンドを実行して強制終了するとき

ターミナルでコマンドを実行して応答がないときに強制終了したい。こようなときは次のコマンドを使います。

Ctrl + C  強制終了

ときどき見かける。どこかで間違った知識が流布している。Ctrl+z を押す人が多い

Ctrl + z 一時停止

一時停止の方がレスポンスが速く、間違ったコマンドを即座に出力停止できることと、やり直す(Undo)のキーなので混同されやすいのだと思われます。 ctrl + z でジョブ(プロセス)を一時停止した場合は再度実行に戻すことが出来ます。 fg / bg コマンドがそれに当たります。 fg / bg はまた別の記事に書きますので参考にしてください。

強制終了を検出したい

Ctrl +C で強制終了したコマンドは、ただ強制終了されると困ることが有ります。

作業中ファイルどうするの。

強制終了されてこまること。それが中間ファイルの後始末を出来ないということです。

そもそも中間ファイルを作らないようにプログラムを作ればいいのですが。気をつけても作ってしまうことが有ります。 後始末を出来たほうがいいこともあります。ネットワーク接続やトランザクションやファイルロック、ジョブキューなど

作業中のファイルを消すためにtrapする

trap することで強制終了後に後処理を済ませることが出来る。

trap の使い方

trap の使い方は、ほぼこの定型文です。

trap コマンド 1 2 3 15 

trap を使ってみる。

さっそく、TRAPを使ってみたいと思います。無限ループするコマンドを用意し、無限ループを強制終了したら、なにか文字列を出力することにしてましょう。

無限ループコマンドtrapつき:例
function last () {
  status=$?
  echo '強制終了しました'
  echo "ステータス: $status"
  echo  in trap, status captured
  exit $status
}

trap 'last'  {1,2,3,15}

i=0
while : ; do
  echo $(( i++ ))
  sleep 1
done
無限ループを強制終了した例
takuya@Desktop$ bash trap.sh
0
1
^C
強制終了しました
ステータス: 130
trap captured

ほかにも kill -HUP PID などで実行中のbash無限ループを止めてみると色々変化を見ることが出来ます。

ただし、 kill -KILL PID でKILLした時は、本当の意味の強制終了 SIGKILLが発生するので trap も発動しません。

シグナルについて

今回はあまりSIGNALには深追いをしないつもりなので、あまり記述しません。

必要箇所だけ触れておきます。

Ctrl + C で送信しているのは SIGINT です。SIGINTとは、signal interrupt キーボードからの強制割り込みによる中断です

この他にSIGHUP があます。 SIGHUPとは signal hangup つまり回線切断です。これはログイン中のユーザーが回線ダウンによりどっか行ったぞ。って意味です。

ちなみに、冒頭の強制終了をCtrl+z 勘違いの話をしましたが、Ctrl+zで放置しててもプロセスが終了するのは、 一時停止後にログアウトでSIGHUPが発生し、プロセスが終了するからです。間違って覚えていた人は、このSIGHUPがあるおかげで暗線に一時停止で誤魔化せていたわけです。

そして、 SIGKILL があります。これは純粋な強制終了で、trap も発動しません。即死技です。

*1

キーボード シグナル 数字 意味
Ctrl + C INT / SIGINT 2 interrupt 強制割り込み中断です。よく使う
HUP / SIGHUP 1 hangup 回線切断による中断
KILL / SIGKILL 9 KILL 即死技です。trapする隙きも与えず終了
Ctrl+Z STP/SIGTSTP 20 stop 一時停止です。

TRAP で終了後処理ができる

trap を使うことで、終了後の後処理が出来ます。シグナルにトラップして捕まえるのです。

trap の例1
trap 'echo hooooooooooooooooooo' 1 2 3 15 
trap の例2
function () {
  echo hoooooo
}
trap 'my_trap' 1 2 3 15 
trap の例3
function () {
  echo $@
}
trap 'my_trap hooo' {1,2,3,15}

わたしは、TRAPするシグナルを列挙するよりブレース展開で囲って書くほうがシグナルがわかりやすくて好きです。

trap を書くときの注意

trap を書くときの注意があります。

できるだけ最初に書く

trap はできるだけ最初に書くべきです。次の例では、無限ループの後ろにTRAPが来ています。trap を設定する前に、無限ループが走ってしまい、Ctrl+Cで止めてもトラップされません。

trap 出来ない トラップ
i=0
while : ; do
  echo $(( i++ ))
  sleep 1
done

trap 'echo trap'  {1,2,3,15}

trap でシグナルの上書きができてしまう。

trap をしていると異常終了をそのまま異常終了出さずに正常終了にしてしまうことが出来ます。もちろんそのための例外処理なので出来て当たり前なのですが。スクリプトを書いていると、エラーになるはずがエラーにならず困っていしまいます。そこでトラップするとき終了コードを維持しておいたほうが無難でしょう。

function last_trap () {
  status=$?
  exit $status
}

trap 'last_trap'  {1,2,3,15}

その他の trap

trap は他にも、正常終了でも毎回実行する事もできます。

正常終了のときにだけ実行されるtrapが作れる
function success () {
   echo $?
}
trap 'success' 0
function failure () {
   echo $?
}
trap 'failure' 1 2 3 15

これは、一見すると、正常終了であるため、スクリプトの本筋でやるべきです。だらべつに何も使う必要はないと思いますが、デバッグ時などに便利です。

実行前実行が出来る DEBUG

debug をつかえば、実行前に仕込むことが出来ます。

function before () {
   echo $?
}
trap 'before' DEBUG

まとめ

trap により異常終了時のファイルの後始末などが出来る。

ただし、ソースコードに jump / goto が増えるのでそんなに多用するべきではありません。

ファイルが存在したら消して新規作成などのスクリプトを本来は書くべきです。

ただし、ファイルのロックの後始末や多重起動防止や排他制御にはあると重宝します。またデバッグにも便利です。

シェルスクリプトは中間ファイルをなるべく作らないほうがいいと個人的には思います。そのためにパイプライン処理があり、 /tmp などの一時記憶もあります。が

参考資料

trap: trap [-lp] [[arg] signal_spec ...]
    シグナルまたは他のイベントをトラップします。

    シェルがシグナルを受け取るか他の条件が発生した時に実行されるハンドラーを
    定義および有効化します。

    ARG はシグナル SIGNAL_SPEC を受け取った時に読み込まれ実行されるコマンド
    です。もし ARG が無い (かつシグナル SIGNAL_SPEC が与えられた場合) または
    `-' の場合、各指定したシグナルはオリジナルの値にリセットされます。
    ARG が NULL 文字列の場合、各シグナル SIGNAL_SPEC はシェルにおよび起動さ
    れたコマンドによって無視されます。

    もし SIGNAL_SPEC が EXIT (0) の場合、ARG がシェルの終了時に実行されます。
    もし SIGNAL_SPEC が DEBUG の場合 ARG は単に毎回コマンドの前に実行されます。
    もし SIGNAL_SPEC が RETURN の場合 ARG はシェル関数または . か source に
    よって実行されたスクリプトが終了した時に実行されます。 SIGNAL_SPEC が ERR
    の場合、-e オプションが有効な場合にシェルが終了するようなコマンド失敗が発
    生するたびに実行されます。

    もし引数が与えられない場合、 trap は各シグナルに割り当てられたコマンドの
    一覧を表示します。

    オプション:
      -l    シグナル名とシグナル番号の対応一覧を表示します
      -p    各 SIGNAL_SPEC に関連づけられた trap コマンドを表示します

    各 SIGNAL_SPEC は <signal.h> にあるシグナル名かシグナル番号です。シグ
    ナル名は大文字小文字を区別しません。また SIG 接頭辞はオプションです。
    シグナルはシェルに対して "kill -signal $$" で送ることができます。

    終了ステータス:
    SIGSPEC が無効か、無効なオプションを与えられない限り成功を返します。

*1: SIGINT など 接頭詞の SIG は SIGNAL の略語で、無しで表記されることもあります。

jq で 条件にマッチするオブジェクトを取り出す where 句的なこと

jq 便利ですよね。

jq 使ってます。みんなあれ整形程度にしか使ってなかったり、絞込にしか使ってない気と思うんですよね

jq である条件を満たすオブジェクトを取り出したい

SQLselect where みたいに select {} where [].name = 'takuya' みたいな jq がかけたら最高なんですよ。

条件マッチしたノードを取り出す例
$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq ' .[] | select(.x) '
{
  "x": 1
}

出来るんです。マニュアルに有りました。ああ。これは最高だ。

単純なところから、見ていく

いきなり select 見ても何のことかわからないので、単純なところから見ていくことにする。

単純な整形

jq を使って整形をするよくある例です。これは jq . と書いてます。つまりカレントノードを出力しろって意味です。

単純な整形の例
$ echo '[{},{}]' | jq .
[
  {},
  {}
]

配列の中身を出す

配列の中を自動で走査して中身を取り出してくれるのが、こちらにになります。 jq .[] は jq の最上位のノードのひとつ下の配列をIterationして中身をくれって事になります。

配列の中身を出力取り出す例
$ echo '[1,2,3]' | jq '.[]'
1
2
3

配列の指定したINDEXを取り出す。

配列のなかに、インデックスを指定すると、指定した番号のものが取り出せる。

配列の指定したINDEXを取り出す例
$ echo '[1,2,3]' | jq '.[1]'
2

さらにこれは、一つだけじゃなくさらに突っ込んだ書き方ができる。

配列の中身の順番を複数指定してしまう例
$ echo '[1,2,3]' | jq '.[0,2,1]' # 順序も
1
3
2
$ echo '[1,2,3]' | jq '.[0,2]' # 選択も
1
3

要素がオブジェクトで ハッシュキーから取り出す

選択対象となる要素がハッシュ(オブジェクト)でその中をStringをキーに取り出していきたいとき。

オブジェクトの中身だけを取り出したい。
$ echo '{ "a":1 , "b":2 }'  | jq '.a'
1
$ echo '{ "a":1 , "b":2 }'  | jq '.b'
2
ネストしたオブジェクトでも大丈夫。
# ネスト 1段 ・ネスト2
$ echo '{ "a": { "x": 1 }, "b":2 }'  | jq '.a'
{
  "x": 1
}
$ echo '{ "a": { "x": 1 }, "b":2 }'  | jq '.a.x' #ネスト 2段
1

配列とオブジェクトの選択の組み合わせ。

ここまで見た配列とオブジェクトの指定をjq で行う場合を組み合わせると次のようになる。

配列とオブジェクトを同時に使う。
$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq '.' # まずは単純に整形
[
  {
    "a": 1
  },
  {
    "x": 1
  }
]
$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq '.[]' # 配列要素ごとにprint 
{
  "a": 1
}
{
  "x": 1
}
$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq '.[].a' # 配列の要素内で検索
1

true/falseに変形(検索条件を書く

ここで、更に検索条件を足して、書き換え処理を行う。map 処理です。

配列の各要素をその値によって書き換えていきます。

jq で要素のオブジェクトを書き換え true/falseにする
$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq '(.[].a==1)'
true
false

最後にselect と組み合わせる。

true / false に変形することが出来たらそれをフィルタする。 map /reduce / filter の概念が理解できてればかんたんです。

$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq '.[] | select (.a==1)' # a == 1 のものだけ
{
  "a": 1
}

$ echo '[ { "a":1 },  { "x": 1 } ]'  | jq ' .[] | select(.x) ' # 'x': object が存在するものだけ
{
  "x": 1
}

個人的感想

jq 便利すぎてやばい。STDIOとパイプをJSONと組み合わせたxargs にかわる新しい概念かもしれない。

参考資料

まにゅあるにあったわ。。

https://stedolan.github.io/jq/manual/#Assignment

bashで関数の定義と実行と削除

bashの関数

bash は 組込コマンド(組込み関数) は $PATH の通っている コマンドの実行 の他に、自分で定義した関数 function を使うことが出来ます。

シェルスクリプトを記述する プログラミング言語としておなじみの機能です。

bashの関数の定義方法

bash の関数定義の方法は、よく見る関数定義と同じですが、記述方法が3つ + 1つあります。

function MY_FUNC () {
  echo hello world
}
function MY_FUNC {
  echo hello world
}
MY_FUNC () {
  echo hello world
}
MY_FUNC ()  for (( idx=0; idx<10; idx++ )); do echo $idx ; done 

このどれもが、関数の定義として作用します。

特に最後の一つは馴染みがないかもしれません。でもちゃんと動く関数定義です。

最後の一つは、省略記法になっていて、for / if / whileなどの制御構造と、[[ expression ]]であれば記述することが可能です

{} は複数コマンドを記述する

もともと bash には { } をつかい、複数行のコマンドを実行するという機能があります。

複数行をまとめて実行する例
takuya@Desktop$ { echo a; echo b; }
a
b

これらは、crontab で複数コマンドを実行するときなどに使うと便利な記述です。

関数定義の後ろにくっついた { } も同じように解釈すると理解が進むと思います。

関数を定義して使う

bash は上から下に順番に実行される、逐次処理するインタプリタなので、関数は定義してから使います。

幾つかのプログラミング言語では、関数の定義と実行は順序無くかけるかもしれません。しかし bash では前から処理されるため、使う箇所より定義が先である必要があります*1

また、呼び出すときは、関数の名前だけを指定して、( )は特につけません。コマンドと同等に使うことが出来ます。

php での関数の後置記述の例
<?php
sample();
function sample(){
 echo 'hello \n';
}
bash での関数定義と呼び出し
function sample(){
 echo "Hello world"
}
sample

関数を削除する

定義済み関数を削除するには unset を使います。

これは、関数も変数として格納されているためです。

定義済み関数を削除する例
function my_sample(){
 echo "Hello world"
}
my_sample
unset my_sample
削除した関数を呼び出した場合
$ my_sample
-bash: my_sample: コマンドが見つかりません

削除した関数を呼び出したら、当然ですが、未定義になっています。

関数は変数から呼び出せる

関数の名前を格納した変数を実行することで、関数を呼び出すことが出来ます。これにより、関数呼び出しを抽象化することが可能になります。

変数に格納した関数名を呼び出す
function my_sample(){
 echo "Hello world."
}
function_name=my_sample
$function_name #=> Hello world.

これは、bash が逐次処理され、変数名が実行前に展開されることに由来すると考えられます。

つまり $function_name は実行前に my_sample に展開され、関数として実行されるのです。

関数の引数

関数は引数を取ることが出来ます。

これらは、通常のシェルスクリプトに与えた引数と同等に扱うことが出来ます。

function arg_sample(){
 echo $@
}
arg_sample a b c d 

関数の引数は、シェルスクリプトに与えた変数と同じなので、getopts で解釈することもの出来ます。このあたりは、bashシェルスクリプトに与える引数getopts について書くときに詳しく書きたいと思います。

関数はコマンドより優先される

関数はコマンドより優先されます。つまりどういうことかというと、 $PATH で指定したコマンドより、関数が優先されます。

つまり、コマンドと同名の関数があれば、関数が実行されるのです

takuya@Desktop$ function ls(){ echo dumy;  }
takuya@Desktop$ ls
dumy

これを使って、一時的や便利のために、コマンドを上書きする関数が作れます。もちろんイタズラにつかうと、とんでもないことになるしセキュリティ懸念がありますが。

alias では実現できない、関数の上書きを function を使って実現することが出来ます。私は find コマンドを上書きして使っています。

alias と function が衝突した場合

alias と fucntion に同名の定義がある場合。alias が優先されます。

alias は 関数への alias も出来てしまうので、関数より優先されるのが自然な動作です。

同名は alias が優先
alias find='echo find from alias'
function find() {  echo find from function ;}
type find
#=>find は `echo find from alias' のエイリアスです
関数への alias の定義の例
function my_func { echo from function $@ ;  }
alias my_func='my_func with alias '

my_func #=> from function with alias

コマンドを上書きするときは、unaliasunset を使って上手に名前の衝突を回避する必要があるかもしれません。

関数の戻り値

function my_func() {
   return 0
}

関数の戻り値は、正常終了で0、それ以外で1以上を返す。

処理を終えるのなら exit 0 で正常終了 exit 1 で異常終了を返してもいいんだけど、自分で書いたぱぱっとスクリプトで return /exit を細かく維持しても・・・

TODO 戻り値の、終了コードについてもう少し詳しく書く。制約とか、文字列を渡す場合のechoとか。

おまけ

javascript の関数定義で見かける、無名関数や即時関数みたいなことも出来ます。

変数の名前空間を汚さないので、使い方によっては便利に使えるかもしれません。

即時関数の例
takuya@Desktop$ (function a() { echo a; }; a )
a
takuya@Desktop$ a
-bash: a: コマンドが見つかりません
takuya@Desktop$

まとめ

  • 関数の定義方法
    • name ()
    • name () {}
    • fucntion name () {}

*1: php が事前にコンパイルが走って、関数一覧を先に処理してくれる. php特殊なんだと思いますが、、、

bash のシンタックスチェックでシンタックスエラーを防ぐ

bash にもシンタックスチェック機能があります。

シンタックスチェックをすることでエラーを未然に防ぐ事ができます。

たとえば、長文のシェルスクリプトを書いているときシンタックス・チェックが有ると、事前にミスが見つかるので便利です。

bashシンタックスチェック
bash -n my_script.sh

ちなみにbash 以外のzsh/dash などでも使えます。

文法ミスで死ぬのは辛いのでチェックしましょう

シンタックスチェックが重要なわけ

bashインタプリタ(死語?) で、特に1行ずつ読み込んで評価・実行する言語になっています。

したがって、文法エラーがあると文法エラーの箇所の直前まで「実行」されてしまいます。

文法エラーの例
echo aaaaaaaaaaa
if (())

fi
上のサンプル(文法エラー)を実行した例
takuya@Desktop$ bash  test.sh
aaaaaaaaaaa
test.sh: 行 11: 予期しないトークン `fi' 周辺に構文エラーがあります
test.sh: 行 11: `fi '
途中まで実行されてしまう。

文法エラーがあっても、bashは1行ずつ評価します。そのためエラー直前まで実行が進んでしまいます。

そして、文法エラーになった箇所で止まってしまいます。

これはステートレスではないスクリプト、たとえばファイルを消す、データベースを書き換えるなど状態が変わるスクリプトを実行しているときに困ります。

途中まで実行してしまい、残りがシンタックスエラーになり未実行で残されてしまいます。非常に困りますね。

このような事故を未然に防ぐためにシンタックスをチェックする習慣を意識した方がいいでしょう。

とくに数十行以上に渡る長いシェルスクリプトを書くときに重要になってきます。

関連資料

http://takuya-1st.hatenablog.jp/entry/2016/04/03/230008

ヘルプ(help) コマンドで関数マニュアルを調べる使い方

bash の help 機能について

bash の組込の機能については man / info でもいいのですが多すぎて大変。

help コマンドを使います。help でダイレクトに調べられます。

bashシェルスクリプトとしての機能やSHELLとしての機能を見たいときには help を使えば見ることが出来ます。

インターネットではlinux での使い方で調べると man がよく出てきますが、bash の機能を見たいときは help を使うと手軽です。

help は組込コマンド

help は bash のシェルで使うと便利です。bash が起動していたらどこでも使えます。

help 知りたい機能のように引数にキーワードを入れるだけでカンタンです。

help の使い方 例
takuya@Desktop$ help cd
cd: cd [-L|[-P [-e]] [-@]] [dir]
    Change the shell working directory.
(略

help の help

ヘルプの使い方も help には書いてあります。

help と打ち込んだらいくらでも見ることが出来ます。ただしキーワードの指定の仕方には少しコツが必要です

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

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

 job_spec [&]                        history [-c] [-d offset] [n] >
 (( expression ))                    if COMMANDS; then COMMANDS; [ el>
 . filename [arguments]              jobs [-lnprs] [jobspec ...] ま>
 :                                   kill [-s sigspec | -n signum | ->
 [ arg... ]                          let 引数 [引数 ...]
 [[ expression ]]                    local [option] name[=value] ...
 alias [-p] [name[=value] ... ]      logout [n]
 bg [job_spec ...]                   mapfile [-d delim] [-n count] [->
 bind [-lpsvPSVX] [-m keymap] [-f >  popd [-n] [+N | -N]
 break [n]                           printf [-v var] format [argument>
 builtin [shell-builtin [arg ...]>   pushd [-n] [+N | -N | dir]
 caller [expr]                       pwd [-LP]
 case WORD in [PATTERN [| PATTERN]>  read [-ers] [-a array] [-d delim>
 cd [-L|[-P [-e]] [-@]] [dir]        readarray [-n count] [-O origin]>
 command [-pVv] command [arg ...]    readonly [-aAf] [name[=value] ..>
 compgen [-abcdefgjksuv] [-o optio>  return [n]
 complete [-abcdefgjksuv] [-pr] [->  select NAME [in WORDS ... ;] do >
 compopt [-o|+o option] [-DE] [nam>  set [-abefhkmnptuvxBCHP] [-o opt>
 continue [n]                        shift [n]
 coproc [NAME] command [redirectio>  shopt [-pqsu] [-o] [optname ...]
 declare [-aAfFgilnrtux] [-p] [nam>  source filename [arguments]
 dirs [-clpv] [+N] [-N]              suspend [-f]
 disown [-h] [-ar] [jobspec ... | >  test [expr]
 echo [-neE] [arg ...]               time [-p] pipeline
 enable [-a] [-dnps] [-f filename]>  times
 eval [arg ...]                      trap [-lp] [[arg] signal_spec ..>
 exec [-cl] [-a name] [command [ar>  true
 exit [n]                            type [-afptP] name [name ...]
 export [-fn] [name[=value] ...] >  typeset [-aAfFgilnrtux] [-p] nam>
 false                               ulimit [-SHabcdefiklmnpqrstuvxPT>
 fc [-e ename] [-lnr] [first] [las>  umask [-p] [-S] [mode]
 fg [job_spec]                       unalias [-a] name [name ...]
 for NAME [in WORDS ... ] ; do COM>  unset [-f] [-v] [-n] [name ...]
 for (( exp1; exp2; exp3 )); do CO>  until COMMANDS; do COMMANDS; do>
 function name { COMMANDS ; } ま>   変数 - 変数の名前とその意味
 getopts optstring name [arg]        wait [-n] [id ...]
 hash [-lr] [-p pathname] [-dt] [n>  while COMMANDS; do COMMANDS; do>
 help [-dms] [pattern ...]           { COMMANDS ; }

(( expression )) のヘルプを見てみます。

まずは、 (( expression )) のヘルプを見てみようと思います。

help⏎ と打ち込むと、閲覧可能ヘルプの一覧が出てきます。この中から ` (( expression )) のヘルプを閲覧したいと思います。

takuya@Desktop$ help

 job_spec [&]                        
 (( expression ))              
# (( expression )) のヘルプを見る:失敗例
$ help  (( expression ))
-bash: 予期しないトークン `(' 周辺に構文エラーがあります

そのまま打ち込むと、(( ))bash の構文として展開されてしまうので、見ることが出来ません。

# クオート処理する
$ help  '(( expression ))'
-bash: help: `(( expression ))' に一致するヘルプ項目がありません。`help help'、`man -k (( expression ))' または `info (( expression ))' を試してください

クオートで囲ってみても、うまく行きません・・・

# キーワードを減らすとうまくいく
$ help  '(('
(( ... )): (( expression ))
    算術式を評価します。

    算術式の規定に基づいて EXPRESSION を評価します。"let EXPRESSION"
    と等価です。

    終了ステータス:
    EXPRESSION の評価値が 0 の場合は 1、それ以外は 0 を返します。

このように、help⏎ で出てきたキーワードから、少し減らすとうまく表示されることが多いようです。

bash の変数についてのヘルプを見てみる

bash には各種変数が有り、そのなかでシェル特有なものが紹介されています。

bash 特有の変数は BASH_XXX のように BASH が接頭詞としてついています。それらは info bash で見ることが出来ます。

bash の変数についての解説を見てみる。
takuya@Desktop$ help var
variables: 変数 - 変数の名前とその意味
    通常の変数名とその使用法。

    BASH_VERSION    Bashのバージョン情報。
    CDPATH  `cd`の引数として与えられたディレクトリを検索する際に
            使用されるコロン (:) で区切られたディレクトリの一覧。
    GLOBIGNORE  パス名を展開する時に無視されるコロン (:) で区切られた
            ファイル名パターンの一覧。
    HISTFILE    コマンドヒストリが保存されるファイル名。
    HISTFILESIZE    ヒストリファイルに保存することができる最大行数。
    HISTSIZE    実行中のシェルがアクセスできる最大ヒストリ行数。
    HOME    ログインディレクトリの完全パス名。
    HOSTNAME    現在のホスト名。
    HOSTTYPE    このバージョンの Bash を実行している CPU の種類。
    IGNOREEOF   シェルがファイル終了 (EOF) 文字を単一の入力として受け
            取った時の動作を制御します。設定されている場合、空白行
            で EOF 文字をその数連続して受け取った時にシェルを終了
            します (デフォルト 10)。設定が解除された場合、EOF で
            入力が終了することを意味します。
    MACHTYPE    Bash が実行されている現在のシステムを表す文字列。
    MAILCHECK   Bash がメールを確認する頻度 (秒単位)。
    MAILPATH    Bash が新規メールを確認するコロン (:) で区切られた
            ファイル名の一覧。
    OSTYPE  このバージョンの Bash を実行している OS のバージョン。
    PATH    コマンドを検索する際に使用されるコロン (:) で区切ら
            れたディレクトリの一覧。
    PROMPT_COMMAND  プライマリプロンプトが表示される前に毎回実行
            されるコマンド。
    PS1     プライマリプロンプト文字列。
    PS2     セカンダリプロンプト文字列。
    PWD     現在のディレクトリの完全パス名。
    SHELLOPTS   コロン (:) で区切られた有効なシェルオプション一覧。
    TERM    現在の端末種類名。
    TIMEFORMAT  `time' 予約語による時間統計情報の表示書式。
    auto_resume null で無い場合、その行に現れたコマンドは、まず現在停止
            されているジョブから検索されます。それで見つかった場合、
            ジョブがフォアグランドになります。値が `exact' の場合、
            コマンドが停止しているジョブの一覧と厳密に一致していなけ
            ればなりません。値が `substring' の場合、コマンドがジョ
            ブの部分文字列に一致しなければなりません。その他の値の
            場合はコマンドが停止しているジョブの先頭部分に一致しな
            ければなりません。
    histchars   ヒストリ展開とクイック置換を制御する文字。最初の文字が
            ヒストリ展開の文字で通常は `!' です。二番目がクイック
            置換で通常は `^' です。三番目がヒストリのコメントで
            通常は `#' です。
    HISTIGNORE  ヒストリ一覧に保存されるコマンドを決める時に使用される
            コロン (:) で区切られたパターンの一覧。

構文についてのヘルプ

for については、構文が複数あるので、それぞれ別個にキーワードで指定し調べる必要があります。

for の ヘルプの例
takuya@Desktop$ help for
for: for NAME [in WORDS ... ] ; do COMMANDS; done
    リストの各要素に対してコマンドを実行します。

    `for' ループではリストの各要素に対して一連のコマンドを実行します。
    `in WORDS ...;' が存在しない場合、`in "$@"' であると見なされます。
    WORDS の要素が NAME の値として代入され COMMANDS が実行されます。

    終了ステータス:
    最後に実行したコマンドのステータスを返します。
C言語スタイルの for (( )) のヘルプ
takuya@Desktop$ help 'for (('
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
    算術 for ループ

    以下と等価です。
        (( EXP1 ))
        while (( EXP2 )); do
            COMMANDS
            (( EXP3 ))
        done
    EXP1、EXP2、および EXP3 は数式です。いずれかの数式を省略した場合、
    値が 1 であるとして評価されます。

    終了ステータス:
    最後に実行したコマンドのステータスを返します。

help で見られないモノ

help では主に関数 とくに組込関数 の使い方が見られます。

条件分岐の書式や、コマンドの書式については、特に触れられていませんでした。

パターンや、条件書式や演算子などの情報については info bash または man bash を使うことで見ることが出来ます。

bashで連想配列(assoc array / hash ) を使う。

bash の配列にも 連想配列が加わりました。

追加されたバージョンは、bash 4.2 からです。

はじめに連想配列とは何なのか

連想配列PHPでの略語)は associative array と呼ばれる機能です。js や php を始めスクリプト系言語ではおなじみですね。

# javascript連想配列(オブジェクト)
obj = {}
obj["name"] = "takuya"
# php連想配列(array)
$arr = []
$arrj["name"] = "takuya"

このように、配列のインデックスに「文字列」を使うことで柔軟な記述とプログラミングができるようになるのが、Associative Arrayです。

他の言語では hashdict などとも呼ばれます。

bash連想配列の機能について

bash にも配列の機能があります。この機能で連想配列が使えるようになっています。

debian lenny や ubuntu 最新版であれば bash 4 になっているんので安心して試すことが出来ます。

bash4 以前では使えません。たとえばMac OSX のデフォルトbash は 3.54 なので使えません。

OSX el capitan の場合は brew install bash が必要でした。

Bash連想配列の書き方

連想配列の書き方は、bash の配列の書きかたとほとんど同じなので、慣れている人は変数宣言してしまえば、特に気にせず使えると思います。

連想配列の宣言方法

連想配列を使うには、変数を連想配列として宣言する必要があります。 declare で変数を宣言するときに -A のオプションが必要です。

# 連想配列の変数宣言の例
declare -A MY_ARRAY

連想配列とキー

キーとなるStringを使ってアクセスします。

連想配列のキーに文字列を指定して、データを格納していきます。この辺はどの言語でも同じなので使うには困りません。

# 連想配列に値を格納する例
MY_ARRAY[name]=takuya

連想配列から値を取り出すのは、bash の配列アクセスとほぼ同じです。

# 連想配列から値を取り出す例
echo ${MY_ARRAY[name]} #=> takuya

配列の長さ

配列の長さも、bash の配列と同じです。 ${#array[@]} または ${#array[*]} でアクセスします。

# 連想配列の長さを取得する例
declare -A MY_ARRAY
MY_ARRAY[name]=takuya
MY_ARRAY[id]=0001

echo ${#MY_ARRAY[@]} #=> 2

配列のキーと値を扱う。

連想配列に、キーと値を入れた場合、これをループで回さないと意味がないですよね。配列はループで使って役に立つわけですしね。

配列のキーの一覧を取り出して中身を見てみましょう

## 配列のキーの一覧を取り出す例
echo ${!MY_ARRAY[@]}
# または
echo ${!MY_ARRAY[*]}

連想配列のキーの一覧を取り出してしまえば、あとはループで回せばいいわけです。

## 連想配列のfor ループの例
for key in  ${!MY_ARRAY[*]} ; do echo $key = ${MY_ARRAY[$key]} ; done
id = 0001
name = takuya

for .. in ループを使って、連想配列のキーを取り出し、キーを変数key に格納し、連想配列にアクセスします。

これで、連想配列で二次元配列を作ったりしてKey-Valueが使えるようになりました。

連想配列の要素を消す。

今度は、要素を削除する方法を知っておきたいと思います。要素を削除できるようになりましょう。

変数の削除に unset を使うのですが、連想配列の要素の削除にも unset が使えます。

# 連想配列の要素を削除する例
unset -v MY_ARRAY[name]

そして、変数自体が要らなくなった場合にも unset ですね。

# 連想配列を削除する例
unset -v MY_ARRAY

最後に

これで、一通り使えるようになりました。

新機能を覚えておけばBash がさらに便利になると思います。

bashシェルスクリプトはどこで使うのと思うかもしれません。しかし、bashrc を書くには bash の機能を知っておくのが一番いいのです。

まさかbash 用のbashrc をPOSIX準拠で書く人はいませんね。

また、bash はどこでも使えるデフォルトシェルです。最初から入っているので低機能だとか古めかしと思われがちですが、bash もやはり進化しているのです。

最後におさらい

bash連想配列の機能をひと目で分かるようにまとめておきます。

declare -A MY_ARRAY
MY_ARRAY[name]=takuya
MY_ARRAY[id]=0001

echo length is  ${#MY_ARRAY[@]} #=> 2

echo show assoc array entries


for key in  ${!MY_ARRAY[*]} ; do 
  echo $key = ${MY_ARRAY[$key]} ;
done


unset -v MY_ARRAY[name]
unset -v MY_ARRAY[id]
unset -v MY_ARRAY

連想配列機能について

Bash Hackers Wiki の記述の邦訳を書いておきました。

Associative arrays (sometimes known as a "hash" or "dict") use arbitrary nonempty strings as keys. In other words, associative arrays allow you to look up a value from a table based upon its corresponding string label. Associative arrays are always unordered, they merely associate key-value pairs. If you retrieve multiple values from the array at once, you can't count on them coming out in the same order you put them in. Associative arrays always carry the -A attribute, and unlike indexed arrays, Bash requires that they always be declared explicitly (as indexed arrays are the default, see declaration). Associative arrays were first introduced in ksh93, and similar mechanisms were later adopted by Zsh and Bash version 4. These three are currently the only POSIX-compatible shells with any associative array support.

Associative arrays( Hash や Dict とも呼ばれる)機能は、任意の文字列(空文字以外)をキーに配列に格納します。いわば、associative arrays によって表における各行に列の<見出し文字列>でアクセスできるようになるものです。associative arrays内部には基本的に順番はありません。つまり単なるKey-Valueストアです。配列から一度にデータを取り出すとき、保存した順で取り出せるとは限りません。associative arraysは、いつも -A を使ってアクセスします。通常の配列インデックスの数字を使うときとの大きな違いです。associative arraysは ksh93 で初めて世に出てきました。そして、その同等機能が、zsh にも、そしてbash4 でも採用されました。そのため、ksh/zsh/bashPOSIX準拠シェルでassociative arraysをサポートしています。

まとめ・ポイント

連想配列が使えるのは次の3つ - ksh - zsh - そして bash 4.

連想配列-Aを使って宣言する。

連想配列は、'' の空文字をキーに出来ない。

配列と使い方は同じ。

TODO:ユニコードサポートを意識すれば日本語も使える??

参考資料

bashで可変変数(抽象化)をする。

bash でも変数名の変数を使えます。

php では variable variable (可変変数)、javascript では obj[name]() などとやります。

関数名や変数名を、変数に確保しておき後で使ったり、動的に変数名をつくって変数を参照するなどするときに使われます。java などは class の reflection をしたりで少々厄介だった記憶があります。

もちろんスクリプト言語(ときに逐次インタプリタ)なのでeval / $() //でも実現出来ますが。圧倒的にかんたんなので知っておくと役立ちます。

a=${varname} で殆どの場合解決するんで余り出番はないかもしれません。

bash の 変数名の文字列 indirect access

少し例を見てみます。変数名を格納した文字列の変数にアクセスする例です。

# 間接的アクセス
a=takuya
name=a
echo my name is ${!name}
# 実行結果
my name is takuya

導入されたバージョン

bash 2

参考資料

http://www.tldp.org/LDP/abs/html/bashver2.html

bashの文字列の追記(append)演算子の紹介

文字列の追加が簡単になっています。

bash 3.1 から 使えます。

a='Hello'
a+=' World'
echo $a # Hello world

PATH などの追記が簡潔になります

たとえばPATHの末尾に文字列を追加するときは次のように書くことが出来ます。

PATH+=:/home/takuya/.bin
いままでとの比較
PATH=$PATH:~/.bin # 昔からの手法
PATH+=:~/.bin # 単純化された現代的な手法

ほんの少しだけらくになります。

なぜこの機能を紹介するのか。

記号が増えると、初見殺しになります。久しぶりに シェルスクリプトを読んだ人には今時のシェルスクリプトの記号をみてチンプンカンプンになってしまいます。

演算子は簡潔な記述で読みやすく便利ですが、知らない人には全く想像つかないものになります。

なので、この機能を紹介しました。

この他にもいくつか記号や演算子を紹介しています。

参考資料

http://www.tldp.org/LDP/abs/html/bashver3.html

bashでif に正規表現を使った文字列マッチ条件分岐

bash の使い方

bash の使い方正規表現編です。

bash正規表現マッチのif も出来ます。

Version 3.2 くらいから、=~ によるマッチ判断ができるようになっています。

正規表現マッチで条件分岐の例

とてもかんたんなマッチングの例を見ておきましょう。

name='<h1>takuya</h1>'
if [[ $name =~ takuya ]] ;
then
  echo match
fi

if の条件のなかに [[ ]] のダブルブラケットを書くのがポイントです。正規表現はクオテーションは不要です。クオートしたら動かないので注意してね。

これで、文字列が存在したらマッチ。文字がアレば何かする事ができます。

すこし複雑な正規表現マッチ

最初の例だと単なる文字列マッチと区別がつかないので、もうすこし正規表現らしいマッチングをしてみます。

電話番号にマッチ
tel='09012345678'

if [[ $tel =~ [0-9]{3}-?[0-9]{4}-?[0-9]{4} ]] ;
then
  echo match
fi

よく見る正規表現で、数字の出現と回数、特定の文字列の有無が判断できます。

ポイントは[[ ]] のダブルブラケットの中で変数と文字列が展開され、マッチングしていることにあると思います。 つまり、[[ $name =~ ^REGEX$ ]] [[ STRING =~ ^REGEX$ ]] に展開され、マッチするか判断されていると考えられます。

このことは、次に紹介する、正規表現を変数に埋める処理に関係します。

またクオートしなかったのも同じ理由であると考えられます。 [[ $name =~ "^REGEX$" ]] のように書いてしまうと クォートを含めた"^REGEX$"正規表現として解釈されてしまうようです。

正規表現を変数に格納してマッチング。

正規表現if の中に書くのは後々のトラブルの種というか、悩みのタネになりそうです。

正規表現を変数に入れてマッチングをしてみたいと思います。

# 正規表現を変数に入れた場合
tel='09012345678'
regex='[0-9]{3}-?[0-9]{4}-?[0-9]{4}'

if [[ $tel =~ $regex  ]] ;
then
  echo match
fi

先程の例の改造版です。ケータイ電話番号の正規表現マッチの処理で、正規表現の部分を変数regex に格納した例になります。

前項の説明を借りると[[ $tel =~ $regex ]][[ 09012345678 =~ [0-9]{3}-?[0-9]{4}-?[0-9]{4} ]] に展開され、その後 [[ ]]の内部でマッチングの処理が行われていると考えると理解しやすいでしょう。

正規表現を使えば OR 記述が少し簡潔になる

if 文の中に文字列で== をいくつも列挙するのは少々しんどい。あとで読みづらいので、正規表現を使うと少し簡潔書くことも出来ます。

まぁ正規表現だから当たり前なんですけどね。簡潔な書き方なのはうれしいですね

正規表現のORを使う例
function match_test(){
  printf "%-18s: " "$1"
  if [[  $1 =~ apple|pen   ]];  then
    echo match
  else
    echo dose not match
  fi
}
match_test "Hello world."
match_test "This is a apple."
match_test "This is pen."
match_test "This is a boy."
# 上記の実行結果
Hello world.      : dose not match
This is a apple.  : match
This is pen.      : match
This is a boy.    : dose not match

大文字・小文字の区別

正規表現を使う上で、重要になるのが大文字小文字の区別になります。Case SensitiveCase Insensitive かは、正規表現を使う上で避けて通れない知識になります。bash では正規表現を使うときにshoptで設定されたシェルのオプションフラグを参照してます。

# シェルのオプションの確認
$ shopt | grep case
nocaseglob      on
nocasematch     off

わたしの場合、正規表現Case Sensitive、GlobではCase Insensitiveで利用しています。ご自身で好きな設定をすることが出来ます。もし設定を常時有効にしたい場合は、下記のコマンドを~/.bashrcに記述しておけば設定を永続化出来ます。shopt の使い方は別に記事を書くのでそちらも参考してください・

# 大文字小文字を区別しない設定 Senstive
shopt -s nocasematch
# 大文字小文字を区別する設定 Insensitive
shopt -u nocasematch

正規表現の他に比較で使えるもの

正規表現マッチの他にも、文字列の比較には色々使えます。

bashでは文字列の比較に次のようなものが使えます。

  • 正規表現(今回紹介したもの
  • glob マッチ
  • == によるマッチ
  • grep(コマンド) によるマッチ

人によってシェルスクリプトでの書き方は異なるでしょうが、わたしは正規表現bashだけで使えるのがとても気に入っています。

後方参照

マッチした文字列の後方参照で再利用する方法は、別の記事にしています。

bashの正規表現マッチで後方参照 - それマグで!

追記

zsh の場合でも 同様にできる。 zsh も進んでますね。

追記

sh の場合は 、 expr を使う expr を使うときの記述例は別エントリに書いた → シェル ( /bin/sh ) での正規表現マッチ。 - それマグで!

まとめ

bash のコードが読みにくいと予断をもってるひとも、コレを見てbashの印象がすこし好転してくれるではないでしょうか。読者がbashを"再"発見されることに期待を寄せて。

2019-08-23

マッチした文字列の取得方法(後方参照)ついてのリンクを追加

2020-06-09追記

zsh についての記述を追加 。 exprについての記述を追加。

参考資料

bash の組込 let による数値計算とインクリメント

bash で数字を計算する let

bash で数字を計算するときに、どのような方法を使いますか?

数字を計算するときに、いくつか選択肢があります。

  • expr
  • let
  • C style

主に、この3つのどれかだと思います。*1

今回はこの3つのうち、let について 見ていきたいと思います。

3つのサンプル

expr $a++
let a++
(( a++))

今回は、LETについて見ておきます。

let の使い方ヘルプの呼び出し方

let は シェル組み込み関数なので、マニュアルを見るときは help コマンドを使います。

help let 

使い方を忘れる(あまりないけど)ときは、help を見るといつでも見ることができる。

let による数値計算

let による数値計算C言語スタイルの数値計算とほぼ同じです。

let a=1
let a++
echo $a #=> 2

代入演算子も使えます

数値を変数に代入する省略した演算子も同様に利用することが出来ます。

a=2
let a*=3
echo $a #=> 6

使える、代入用のオペレータは =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= です。結構多いですね。魅力的です。

三項演算子も使えます。

便利なことに、三項演算子も使ったり出来ます。

let a=1
let  a=(a ==2 ? 1 : 0)
$ echo $a
0

インクリメント・デクリメントが使えます。

a=10
let a++
echo $a #=> 11

複数の式をまとめて記述することが出来ます。

a=10
let a++ a++ a++
echo $a #=> 13

let の help

let: let 引数 [引数 ...]
    数式を評価します。

    各 ARG を数式として評価します。評価は固定長の整数として行われ、桁溢れは
    検査されません。しかし、0 による除算は捕捉されエラーとしてフラグが
    立ちます。次の演算子一覧は同一優先順位の演算子ごとにグループ化されてい
    ます。優先順位は降順になっています。

        id++, id--  変数の後置インクリメント、デクリメント
        ++id, --id  変数の前置インクリメント、デクリメント
        -, +        単項マイナス、プラス
        !, ~        論理およびビット否定
        **      指数演算
        *, /, %     乗算、除算、剰余演算
        +, -        加算、減算
        <<, >>      左および右ビットシフト
        <=, >=, <, >    比較
        ==, !=      等価、不等価
        &       ビット論理積
        ^       ビット排他的論理和
        |       ビット論理和
        &&      論理積
        ||      論理和
        expr ? expr : expr
                条件演算子
        =, *=, /=, %=,
        +=, -=, <<=, >>=,
        &=, ^=, |=  代入

    シェル変数は被演算子として使用できます。変数名は数式内で (強制的に固定長
    整数の) 値に置き換えられます。変数は数式内で使用する時には必ずしも
    整数属性を持っている必要はありません。

    演算子は優先順位の順に評価されます。小括弧でくくられた数式は先に評価され、
    上記の優先順位を上書きするかもしれません。

    終了ステータス:
    ARG の最終的な評価値が 0 の場合 let は 1 を返します。それ以外の場合は
     let は 0 を返します。

関連記事

takuya-1st.hatenablog.jp

*1: expr はプロセス起動になるので、遅いです。遅くかろうが互換性にメリットを見出してるP○SIX原理主義者以外は使う必要がありません。