Quantcast
Channel: それマグで!
Viewing all 1780 articles
Browse latest View live

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

0
0

bashの help 機能について

bashの組込の機能については man / infoではなく、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 のシンタックスチェック

0
0

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 '

文法エラーがあって、その直前まで実行が進んでしまい、エラーになった箇所で止まってしまいます。

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

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

このような事故を未然に防ぐためにシンタックスをチェックする習慣が必要です。

関連資料

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

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

0
0

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 ;}typefind#=>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を使って上手に名前の衝突を回避する必要があるかもしれません。

おまけ

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

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

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

まとめ

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

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

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

0
0

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の強制終了(CTRL+C)検出と後処理/trap

0
0

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 capturedexit$status}trap'last'{1,2,3,15}i=0while : ; doecho$(( i++ ))sleep1done
無限ループを強制終了した例
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'12315

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

実行前実行が出来る 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 の略語で、無しで表記されることもあります。

bashの配列のまとめ

0
0

bashも配列を扱える

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

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

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

配列の定義

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

bashの配列の定義の例

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

配列の削除

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

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

これは次のように解釈される。と考えると理解しやすい。

echo${ARR[*]}#=> a b c #echo a#b#c       #IFS=#付きで変数処理され#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=(a b)
$ BRR=(${ARR[@]} c) # 追加した
$ echo ${BRR[@]}
a b c
配列の追加(オペレータ)の例
$ ARR=(a b)
$ ARR+=(c) # 追加した
$ echo ${ARR[@]}
a b c

配列の変数アクセス ${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}")

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

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

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

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

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

ループと併せて使う。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)) ; doecho$ARRARR=("${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]

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

0
0

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 で、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 -$1cd -$2cd -$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日本語キーボードなら * が小指で押しやすいので利用は便利だと思います。

参考資料

おまけ

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

https://github.com/wting/autojump

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

*2: shopt

bash引数を使う位置パラメータについてのまとめ

0
0

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

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

位置パラメータとは

位置パラメータは、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実行中のファイル名
$11番めの変数
$22番めの変数
$NN番めの変数(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を全部回せる方法があることを知っておくといいと思います。

これの仕組みは、私もよくわかっていません。

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

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

ファイル名を複数個で引数として受取り、それを逐次処理するような場合には上記のようなループ構造が便利です。 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)); thenecho$@elseecho$( 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

参考資料


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

0
0

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

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

最初は条件分岐の話です。この条件分岐の話をうまくかけるかかけないかでbashの紹介記事や書籍が理解されるか、その成否を分けると行っても過言ではない。

条件分岐以前の話

こんな話が出てきます。

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

条件分岐 if 以前の話

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

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

シェルがコマンドを実行すると、実行結果は終了ステータス・コードで返されます。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 $?

&&で 終了ステータスを活用する 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 文を使わずとも、かんたんな条件分岐なら、 &&{ }を使って書くことが出来ます。

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 を使うようにしておきたいと思います。

参考資料

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

0
0

終了ステータスは重要。

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

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の解釈とは別に、終了コードに意味を与えているコマンドが有ります。

幾つかのコマンドについて見ておきましょう。とくに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 で調べられます。

man grepして STATUS でマニュアル内部を探せば出てきます

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

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

0
0

bashで if の分岐について

bashで if の条件分岐は、プログラマは、if構文を一見して、すぐに理解できるように書かれていますが、じつはちょっとコツが必要なのです。

bashにおける条件分岐

前回は、bashは条件分岐を「コマンドの終了ステータス・コード」で判別しているという話をしました。

そして、条件分岐につかう構文も「コマンド」でその終了ステータスを見て判断されます。&&を使うとコマンドの成功を条件に続けてコマンドをすることが出来ます。

前回のおさらい

だいじなので前回のおさらいをしておきます。

終了ステータスの確認方法
[[ takuya == taku* ]]; echo $? #=> 0 
終了ステータスを使ったコマンド実行
[[ takuya == taku* ]] &&  echo comand success;

さらに、コマンドは{}で複数書くことが出来るので、複数行を書くことが出来ます。

複数コマンドの実行
[[ takuya == taku* ]] && {
    echo Hello ;
    echo takuya;
    echo  sann;
}

bashの 条件分岐と if 文の比較

bashにおける ifは基本的に、command && { comand ; }の構文と同じことをしています。一点だけ違うところは、 && が成功時(または失敗時)の実行に対し、 if 文は elif 節、else節が使えるというところです。

まずは、普通のif 文と比較して見ていきたいと思います。if と && がほとんど同じ書き方であると理解できると思います。

&& で書いた場合
name=takuya
[[ $name == takuya  ]] && {
    echo Hello ;
    echo takuya;
    echo  sann;
}
上記を if で書いた場合
name=takuya
if [[ $name == takuya  ]] ; then 
    echo Hello ;
    echo takuya;
    echo  sann;
fi

if 文の構文についておさらい

if 文は次のように、if comand ; then ; command ; fiで書きます。改行を入れても大丈夫。

if command ; thencommand;
fi

ポイントは、「条件式もコマンドである」というところです。条件式がコマンドで、その終了ステータスで判断しているという点です。

else を入れた場合
if command ; thencommand;
elsecommand; 
fi
elif を入れた場合
if command ; thencommand;
elifcommand; thencommand; 
else;
    command; 
fi

よくある間違い

bashが「コマンド」をベースにしているので 実行するコマンドがないとエラーになります。

コマンドを実行するので、コマンドの直後でthen の直前に ; がないとエラーになります。

「if がコマンドを実行し結果で判断する」するとおぼえておけば、 ; が必要なわけも理解できると思います。;がないとすべてが引数になってしまいます・・・悲しい

if command ; then# 何も書かないのだめ。コメントだけも駄目fi

よくある間違い2

if commandthencommandfi

;がないと全部引数に見えてしまうお

$ if command  then command ; fi 

上記の例を見れば、 ; then と then の直前に必要な理由がわかります。

then の直前の ; は省略できる。
## then の直前に改行がある# これは動くif commandthencommandfi

bashは改行が来たらコマンドの終了と解釈する(または ; を補完したと解釈してもいい)。そのため、 then を改行から始めると ;が無くても動きます。

この辺の改行に有無も<ゆらぎ>が C言語系のプログラマbashがわかりにくい理由だと思います。

これも「if はコマンドを実行している」と強く意識していれば迷うことはないでしょう。

if 文のコマンドは正確には「コマンドリスト」

if の条件は コマンド リストで書くことが出来る。

takuya@:~$ if echo 1 ;echo 1 ; echo 1 ; then echo true; fi
1
1
1
true

複数コマンドの結果を併せて考えることも出来ます。 &&でコマンドを結合すれば条件を複雑に書くことも出来ます。

次の例は&&でコマンドを結合したことがわかりやすいように { コマンド && コマンド }で括っています。

例:&&で複数コマンドの結果を条件にする
if {  grep takuya *.txt  &&   grep takuya *.php ;} ; then
    echo ok;
fi
#=> ok

まとめ

  • bashの if 文は「終了ステータスで判断する」
  • if 文は「コマンド」を実行している
  • if に書けるコマンドは複数行書くことが出来る。

このことを頭に入れておけば、if の構文が、プログラミング言語と若干違うことに戸惑いを感じること無く、そしてシンタックスエラーをもう二度と出すことはないと思います。

参考資料

  • help if
takuya@:~$ help if
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
    条件に従ってコマンドを実行します。

    `if COMMANDS' を実行します。この終了ステータスが 0 の場合、`then COMMANDS'
    を実行します。そうでない場合は、各 `elif COMMANDS' を順番に実行し、その
    終了ステータスが 0 の場合に、関連した `then COMMANDS' を実行し、if 文が
    完了します。それ以外の場合、 `else COMMANDS' が存在する場合には実行され
    ます。文全体の終了ステータスは、最後に実行したコマンドの終了ステータスか、
    または、テストした条件に true となるものが無い場合は 0 です。

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

bashでのif/while に使える条件の一覧

0
0

bashではいろいろな条件で分岐が出来る。

ifで使えるいろいろな条件を見てみたいと思う。

数値判断系

数字を比較するときは以下を覚えておけばいい。

(( 1 > 0  ))
(( 1 >= 0  ))
(( 1 < 0  ))
(( 1 <= 0  ))
(( 1 == 0  ))
(( 1 != 0  ))
((  0  )) # 終了ステータス 1 /if では false
(( 1  ))  # 終了ステータス0 /if ではtrue
((  ! 0  )) # 終了ステータス 0 /if では true
(( 1 && 1 ))
(( 0 ||1 ))

数値を比較するときは if (( 1!=0 )) ; then echo yes ; filetと全く同じです。

(( ))のdouble parensis での記述方法は また別記事もっと詳細に書きます。

繰り返しになりますが ((もコマンドです。お忘れなきよう。

new スタイル test コマンド [[

testコマンド系です。

[[ -e /etc/pass ]]のようなダブルブラケット [[の記述は 新しいtest のスタイルです。

[[が導入されたので、私も test も [ も使わず [[で書くことが多い。

[[による test コマンドは機能追加されている以外 [と同等です。

そのため、紹介例も [[で書きます。 [ / [[ / testの違いはまた別の記事に書きます。

繰り返しになりますが [[もコマンドです。お忘れなきよう。

[[で導入された新しい書き方
[[ $name = takuya ]]     #  文字列マッチ 
[[ $name == takuya ]]   # 文字列マッチ
[[ $name == taku* ]]     # glob マッチ
[[ $name =~ ta.+ ]]       # 正規表現
[[ '' ]]       # 空文字は exit status 1 で if 中は false 扱い
[[ 'aaa' < 'aaaa' ]]    # 文字列の長さ比較
[[ 'aaa' > 'aaaa' ]]    # 文字列の長さ比較
[[ !$name =~ ta.+ ]]  # not による否定
[[ $var1 ||  $var2   ]]     #  ORによる条件
[[ $var1 && $var2 ]]     #  ANDによる条件
[[で使える昔からの条件:ファイルチェック編
[[ -e <FILE> ]] #  パスが存在するか、存在したら true  / -a でも可だが、-a が AND と混同する恐れあり
[[ -f <FILE> ]] # パスが存在する and 通常ファイルであれば true
[[ -d <FILE> ]] # パスが存在する and ディレクトリであればtrue
[[ -c <FILE> ]] # パスが存在する and テキストファイルであれば true
[[ -b <FILE> ]] # パスが存在する and ブロックファイルであれば true
[[ -p <FILE> ]] # パスが存在する and 名前付きpipe であれば true
[[ -S <FILE> ]] # パスが存在する and ソケット であれば true
[[ -L <FILE> ]] # パスが存在する and Symlinkであれば true
[[ -h <FILE> ]] # パスが存在する and Symlinkであれば true
[[ -g <FILE> ]] # パスが存在する and sgid 付きであれば true
[[ -u <FILE> ]] # パスが存在する and suid 付きであれば true
[[ -r <FILE> ]] # パスが存在する and 読込可能であれば true
[[ -w <FILE> ]] # パスが存在する and 書込可能であれば true
[[ -x <FILE> ]] # パスが存在する and 実行可能であれば true
[[ -s <FILE> ]] # パスが存在する and サイズが0以上であれば true
[[ <FILE1> -nt <FILE2> ]]   # 最終更新日mtimeの比較。<FILE1> が <FILE2> より新しいとtrue
[[ <FILE1> -ot <FILE2> ]]   # 最終更新日mtimeの比較。<FILE1> が <FILE2> より古いとtrue
[[ <FILE1> -ef <FILE2> ]]   # <FILE1> と <FILE2> が同じinodeを指してたら true 
[[ -t <fd>     ]]     #   ファイルディスクリプタなら true
[[ / [で使える昔からのtest条件:文字列
[[ -z $var ]] # 文字列が長さゼロ、つまり空文字 '' なら true
[[ -n $var ]] # 文字列が入っていたら true

ただし、これらは [[を使う限りにおいて [[ !$var ]][[ $var ]]と同じなので明示する意図以外で使わないはず。

[[ / [で使える昔からのtest条件:変数
[[ -v varname ]] # 変数が宣言されていたら true  / 配列の場合は array[n]
[[ -r varname ]] # 変数が未宣言なら true

コマンドの実行結果を判断に使う

繰り返しになりますが、if 文はコマンドの実行結果を使って判断しています。 コマンドの終了ステータスが 0 を見ています。

if command ;  then 
   do_something;
fi
複数行のコマンド実行結果

複数行のコマンドを併せて考えることが出来ます。

command && command 
command || command

if や while にかくと次のようにになります。

if command && command ; then ...
if command || command ; then ...
while command  && command ; do ...
複数コマンドを実行して最終実行で判断
command ; command ;
{ command ; command ; }
( command ; command  );

( )はサブシェルを起動するので速度が遅いのと、変数空間が異なるので注意。

最後に。これら条件の確認方法。

while や if で使う条件のこれらの構文の動作チェックをしたいときは、次のようにチェックすると楽ですよ

[[ -e /etc/passwd ]];  echo $?

結果が 0なら、終了ステータス0なのでtrue になります。

これを幾つか組み合わせることで、bashの条件判断(と比較演算子)で何が起きているのか理解することが出来ます。

(( 0 )) ; echo $? #=> 1

また、別の記事書きますが、数字の比較と文字列の比較についてのよくあるミスも確認することも出来ます。

[[ 1000 < 11  ]] ; echo $?  #-> 0

この記事で紹介した条件は多岐にわたるので、自分のターミナルで試してみると安心して使えます。

参考資料

bashの関数定義には「リダイレクト」も含まれる

0
0

bashの関数定義について

bashの関数定義の記事で触れていないことが有ります。というか意図的に省きました。

bashの関数定義には「リダイレクト」が含まれます。

リダイレクトを含めたbash関数定義

bashにおける関数定義は、正しくは実は次のようになっています。

はい。そうです。リダイレクト先が含まれます。

function FUNCNAME () { COMMANDS } > REDIRECTIONS
function FUNCNAME  { COMMANDS } > REDIRECTIONS
FUNCNAME ()  { COMMANDS } > REDIRECTIONS

リダイレクトも含めると楽なことがある。

以下のような記述は、まとめて書くことが出来る。一行に書くときや、エラーログを統一したいときに便利ね。

before
function MyFunc () {
   some_command1 > my.log
   some_command2 > my.log
   some_command3> my.log
}
after
function MyFunc() {
  some_command1;
  some_command2;
  some_command3;
} >  my.log

そもそもコードブロックをリダイレクト出来る

Bashの関数定義ですこし触れましたが、 {}のコードブロックは関数の定義の一部と考えると理解しやすいです。

リダイレクトについても同じで{ }のブロックのすべてのリダイレクトを一括して指定出来る。

{
  commandA 
  commandB
  commandC
} > /dev/null

これは、if などの条件分岐を書くときにちょっと便利。

if { commandA && commandB } > /dev/null ; then some_command; fi 

コレを使うことで、/dev/null 地獄から脱出できる

コードブロックを上手に使うとこんなことが出来る

phpなど言語からshell 呼び出しをするときにバックグラウンド起動で関数を渡すことが出来る。

<?phpshell_exec("{sleep 100;sleep 100;} > /dev/null 2>/dev/null & ");

phpで複数行のシェル呼び出しをバックグラウンドにする - それマグで!

また if 文の条件をまとめて関数にすることが出来る。

function MyConditions() {  some_command1;  some_command2;} >  /dev/null

if MyConditions ; then 
  do_something;
fi 

私は馬鹿なのでコレくらいしか、利用方法が思い付かないが、実はすごくいい活用方法があるのかもしれない。

参考資料

The classic test command [Bash Hackers Wiki]

bashのwhile/until ループ構文について

0
0

bashの while 文について

bashで書ける while文について触れておきます。

while 文の基本構文

while <COMMANDS> ; do 
  <COMMANDS>
done;

ここでの <COMMANDS>は 複数行のコマンドリストのことを指しています。 COMMANDSに含まれるものは次のとおりです

COMMANDS
( COMMANDS;  )
{ COMMANDS ; }
command ; command;
command && command
command || command
command

ここで書いている command は一般コマンドのことで (( [[[もcommandに含めます。

while の;の書き方

改行で ;が自動的に挿入されることを利用し、while を次のように記述する人も多いみたいです。

while <COMMANDS>
do 
  <COMMANDS>
done

このように、;の代わりに改行コードを書いたwhileでは、 doのぶんだけ1行多くなる。 なので、この1行を減らそうと、改行を削除するももの;の概念に気付かずSyntaxErrorで詰まる人をよく見かけたので注意してほしい。

while の break

while には break が書ける。

while <COMMANDS> ; do 
  <COMMANDS>
  break;
done;

break は while や for のネストの数に応じて何個抜けるか書くことが出来る。 break Nで N段のネストを抜ける。

while <COMMANDS> ; do 
   while <COMMANDS> ; do 
      <COMMANDS>
      break 2;
   done;
done;

while の continue

while文では continue も使える。continue を使うと、現在ループを途中で中断して次のループに入る。

while <COMMANDS> ; do 
  if <COMMANDS>; then 
        continue
  fi 
  <COMMANDS>
done;

untile構文

while と not を使えばいいので、あんまり出番がない。do-until はいまのところ存在しないみたい

until <COMMANDS> ; do
  <COMMANDS>
done

参考資料

http://wiki.bash-hackers.org/syntax/ccmd/while_loop

bashの似てて紛らわしいもの ( / ((

0
0

似てて紛らわしいものシリーズ

bashの記述で初心者泣かせの、似てて紛らわしかったり、どう使っていいかわからなかったり、読み方を間違えてパニックになる記号について

(((

全然意味が違うので、間違えると大変。また見た目は似ているので似たようなものだろうと、タカをくくってると痛い目を見ます。

  • ((は算術計算。
  • (はサブプロセスで実行(サブシェル起動)

とそれぞれ、全然意味が違います。

((は算術計算

C言語のようなスタイルで、「算術計算」や「比較」「変数の加算」が出来ます。((計算結果を表示しません

不可能な計算をすると 終了ステータス1を投げてエラー・メッセージを出します。

((の記述の例
((  i=0, i++ )) 
((  i%10 )) 
(( i > 0 ))
(( i +1  > 0 ))
(( i << 1 ))

((は if 文の中で使われるとは限りません。変数をインクリメント・デクリメントしたり letと同等なので算術計算にも使えます。

他にも

  • 四則計算と剰余* / % + -
  • 数値比較< > <= >= == !=
  • ビット演算 ~ << >> & ^ |
  • インクリメント 系id++ id-- ++id --id
  • 三項演算子num = num ? var : num
    などが使えます。さすがに多いので、すべての例は別の記事にまとめようと思います。

(はサブシェルを起動

(の中に書かれるのは、コマンドです。コマンドはbashとは違う環境で実行される。サブ・プロセスのbash中で起動されます。

変数は引き継がれますが、サブ・プロセス中での変数変更は呼び出し元には影響しません。

これはパイプライン処理で変数の変更を維持できないのと同じこと(プロセス起動による)だと考えられます。

$ echo i=$i ;  ( ((i++));echo i=$i ; ) ; echo i=$i ;
i=1
i=2
i=1

cd なども同じです。

(を使うとよくハマるのが、ワーキングディレクトリの変更です。サブシェルの中での変更は、メインに戻ってきたとには捨てられています。

 $ pwd; ( cd /etc ; pwd ) ; pwd
/Users/takuya
/etc
/Users/takuya

(((を組み合わせたらどうなるのか

(( 1+1 ))
((( 1+1 )))
(((( 1+1 ))))

もうわけがわからないよ!!!

(( 1+1 ))      # (( 1+1 ))
((( 1+1 )))    # ((  ( 1+1 )  ))
(((( 1+1 ))))  # ((  (( 1+1 ))  ))

こんな使い方はやめましょう・・・

参考資料

`

   (list) list  is  executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below).  Vari-
          able assignments and builtin commands that affect the shell's environment  do  not  remain  in
          effect after the command completes.  The return status is the exit status of list.
   ((expression))
          The  expression  is  evaluated according to the rules described below under ARITHMETIC EVALUA-
          TION.  If the value of the expression is non-zero, the  return  status  is  0;  otherwise  the
          return status is 1.  This is exactly equivalent to let "expression".

`


bashの似てて紛らわしいもの (( / $((

0
0

似てて紛らわしいものシリーズ (( / $((

bashの記述で初心者泣かせの、似てて紛らわしかったり、どう使っていいかわからなかったり、読み方を間違えてパニックになる記号について

(( / $((の違い

(( / $((は数字を計算する方法です。どちらも同じ機能を持ちますが・・・

  • ((はコマンド
  • $((は展開表現

$(( {1..10}$varnameと同じく、インタプリタが解釈した時点で評価され文字に置き換わります。

((はコマンド として実行され、終了ステータスを返します。

どちらも数値計算をする点は同じ

(( / $((は、どちらも次のように、代入を解釈したり、四則演算や比較評価をする点においては同じです。次のような計算ができます。

i++
i=0
i=i+1
i%=10
i==9

実行前に解釈される $((

$((実行前に計算されて文字列として結果を返します。 *1

$((の例

$ echo $(( 1 + 1 )); echo $?#=>0
2

間違い

$  $(( 1 + 1 )) # コレは間違い

とくにわけがわからなくなるのが、この間違いの記述。実行前に解釈されて文字列に置き換わる、そして出力結果を実行しようとする。

$  $(( 1 + 1 )) # コレは
$  2 # こうなって
$  2 # コマンドが見つからない・・・

意味としては $(にちかい。 echo $( command )が先にcommandが実行されるように、 echo $(( 1+1))も 先に ((が実行されると考えるといい。

コマンドとしての ((

((はコマンドとして実行され、正しく数値計算ができたら終了ステータス0、ゼロ除算など数値計算ができなければ終了ステータス1を返す。

コマンドなので if 文や while などの条件(コマンド)として使える。

true と false に注意

(( expression )) の expression として比較は true になるが、終了ステータスは0である。

$(( expression )) の expression として比較は true になり、文字列として1が返ってくる

true の扱いについては油断してると頭の中を持っていかれるので注意が必要。

*1:実行前という表現が正しいかわかりませんが、コマンドとして実行される前という意味です。

bashの似てて紛らわしいもの [[ / ((

0
0

似てて紛らわしいものシリーズ [[ / ((

bashの記述で初心者泣かせの、似てて紛らわしかったり、どう使っていいかわからなかったり、読み方を間違えてパニックになる記号について。今回は [[ / ((について

[[ / (( の違い

[[ / (( は if 文の条件判断の中で使われることが多いですが、それ単体でコマンドです。

これらはコマンドなので終了ステータスを持ちます。

  • [[ は new スタイルの testコマンドです。これで文字列比較やります。
  • (( は new スタイルの数値計算です。これで数値比較やります。

どちらも if 文でよく使われる

どちらも、if 文のなかで使われることが多いんですが、違うものです。似ているものだけど違うものです。

一番大きな違いは 以下の点にあります。

  • [[ は文字列比較で使われる
  • (( は数値比較で使われる。

なぜんこんなことを書くのか。

==で数字の一致チェックだと正しく動く、数字文字列比較も数値比較も全くおなじに見えるからです。

どちらも true になる比較が次の例です。

[[ での等価比較
var=0
if [[ $var == 0 ]] ; then
    echo yes
fi
(( での0と等価比較
var=0
if (( $var == 0 )) ; then
    echo yes
fi

大小比較すると明確に違う

var=1000
[[ $var > 11 ]] ; echo $? # exit code 1 つまり false
(( $var > 11 )) ;echo $?  # exit code 0 つまり true
  • [[ の比較は辞書順。1000 > 11 は11 が大きくなる。
  • (( の比較は数値順。1000 > 11 は1000 が大きくなる。

ここは油断すると酷い目に遭うので注意が必要。 原理をしらず==で比較で使ってた。

わたしは数年前に手痛い目にあって覚えた・・・辛い。

組み合わせたらどうなるのか?

組み合わせられない。よくわからないことになる。試したけどちょっと何ががなんだか。

$ (( [[ 0 ]]  ));echo $? #=> 1 / false
-bash: ((: [[ 0 ]]  : 構文エラー: オペランドが予期されます (エラーのあるトークンは "[[ 0 ]]  ")
1
$ [[ (( 1/0 )) ]]; echo $?

0で除算がエラーにならない??もうわけわからんね・・・

bashの何もしない特殊コマンド : コロン

0
0

: はコマンドです。

え?コマンド? と思うかもしれません。はい、コマンドです。記号一文字がコマンドです。

:がコマンド(関数)の証拠
takuya@~$ type :
: はシェル組み込み関数です
:コマンドを実行した結果
takuya@~$ : ; echo $?
0

:は何に使うの?

1文字コマンド :はtrue (終了ステータス0)を常に返します。 /bin/truetrue ( 関数 ) もコレと同じ仲間になります。

いつもtrue 、なので無限ループに使います。

無限ループの例
while : ;
do
     echo 1 ;
     sleep 1 ;
done

その他の活用

次のように、if でシンタックスエラーの防止の為に使われます。

シンタックスエラーになる例
if command ; then
else
     do some thing
fi

if の then の中身が空だと?シンタックス・エラーになります。コレを防げます。

:を使ってシンタックスエラー防止
if command ; then
     : # なにもしない : を使う
else
     do some thing
fi

なぜこんなのが出来たの?

だって、シェルスクリプトは、「終了ステータス」で判断するから。 関数が戻り値を 0 か0以外しか返せません。

だから プログラミング言語でいう 1 / true のかわりに :も使えるようになっています。

他にもこんなことが出来ます。

次の例はtouch と同じです。これは、:が必ず成功し0バイトを出力するので、空ファイルが作成されます。

: > output.txt

コメント代わりに 使うことも!

ヒアドキュメントと組み合わせると、ブロックコメント代わりに使うことも!!

bashのブロック・コメントを擬似的に実現する。
: << 'SKIP'
コメント
コメント
SKIP

なんと : は引数とります。

不思議なことですが、 :に引数を与えることが出来ます。

でも引数を渡しても何も起きません。だから、引数を無視する役目を見出すことが出来ます。 なのでコメント代わりに使えます。

例:引数をコメントとして使う。
$ : ここは引数

コメント代わりに使われることが多い

stack over flow では cronjob のコメントに使ってますね。 コメントアウト代わりに使う人が多いようです。

for command ; : ここの実行を待つ ; do
     : ここは何もしない
done

この例みたいにif 文の中に埋め込みコメントに使えますよーって。めっちゃかっこいいwww

変数の初期化とかにも使えますね。そうだね

参考資料=#変数参照の副作用を利用する

同じようなものに true (関数)があります。

takuya@~$ help true
true: true
    結果として成功を返します。

    終了ステータス:
    常に成功です。

ま、何の役にも立たないことが役に立つときもあるのです。

参考資料

bashの複数タブ間コマンド履歴(ヒストリ)共有とPROMTO_COMMAND変数について

0
0

bashでもコマンド履歴の即時反映をしたい

複数タブでターミナルを使ったり、複数ウインドウでターミナルを使うと、bashの履歴が共有されて無くて、悲しいことがある。

zshの機能で紹介されることも多いですが。それ bashでも出来るよ。

共有方法
export PROMPT_COMMAND="history -a"

以上!

これで、複数ターミナル。タブ、( 設定次第では screen tmux ) でヒストリを共有することが出来る。

Mac OSXの場合は iTerm.appTerminal.appでも共有できるんだな

GNU Screenで使える設定

さらに強力に即時反映をするならば、screen の場合は即時反映をするために

export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"

が必要になる。。。。Enterを打てばすぐに反映される。わたしゃここまでやらないけど

PROMPT_COMMAND とは?

PROMPT_COMMAND は コマンドが実行されて、次のPS1が表示されるまでに実行されるもの。

関数を書けば、関数を実行してくれる。

man によると
       PROMPT_COMMAND
              設定されていると、プライマリプロンプトを出す前に毎回、 この値がコマンドとして実行されます。

PROMPT_COMMANDを遊んでみる

function my_prompt_command (){
     status=$?
     echo command last exit status is $status
}
export PROMPT_COMMAND="my_prompt_command"

ヒストリ共有って便利ですか?

じつは、個人的には、ヒストリ共有は余り使わない。GNUScreenではオフにしてる。複数ウインドウ限りオンにしてます。

私の場合、複数タブやscreen などを起動するときは、「用途ごと」にタブを開くので、コマンド履歴がすべて同じなってしまうとむしろ邪魔で仕方がない。

私の PROMPT_COMMAND='history -a'
history -a

これだけで、十分なことが多い。

history -a ここまでのヒストリを .bash_history をファイルに追記してくれる。

PROMPT_COMMANDで履歴管理の他の使い方

たとえば、間違って入力したコマンドはヒストリに保存したくないなど。

typoした汚いコマンド履歴が残るのが恥ずかしくて嫌な時もあります。そういうときは次のようにすれば回避できます。

function my_prompt_command (){
     status=$?
     echo last_status $?
     if ((  status  == 127 ));  then
          history -r
     else
          history -a
     fi
}
export PROMPT_COMMAND="my_prompt_command"

まぁ、これも、一見すると便利そうだが、間違って入力したコマンドは再編集するほうが圧倒的に多く、保存しないのは不便なので余り役に立たないと思いますが。 こういうことが出来ますよーって程度に見ておいてください。

参考資料

  • help history
history: history [-c] [-d offset] [n] または history -anrw [filename] または history -ps arg [arg...]
    ヒストリ一覧を表示または操作します。

    行番号をつけてヒストリを表示します。操作した各項目には前に`*'が付きます。
    引数 N がある場合は最後の N 個の項目のみを表示します。

    オプション:
      -c    ヒストリ一覧から全ての項目を削除します。
      -d offset    OFFSET 番目のヒストリ項目を削除します。

      -a    このセッションからヒストリファイルに行を追加します
      -n    ヒストリファイルからまだ読み込まれていない行を全て読み込みます
      -r    ヒストリファイルを読み込み、内容をヒストリ一覧に追加します
      -w    現在のヒストリをヒストリファイルに書き込みます。そしてそれらを
        ヒストリ一覧に追加します

      -p    各 ARG に対してヒストリ展開を実行し、結果をヒストリ一覧に追加し
        しないで表示します
      -s    ARG を単一の項目としてヒストリ一覧に追加します

    FILENAME を与えた場合、FILENAME がヒストリファイルをして使用されます。それが
    無く、$HISTFILE に値がある場合その値が使用されます。そうでなければ
    ~/.bash_history が使用されます。

    もし $HISTTIMEFORMAT 変数が設定され、NULL で無ければ、strftime(3) の書式
    文字列として各ヒストリ項目の時刻を表示する際に使用されます。それ以外は
    時刻は表示されません。

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

bashのジョブ(bg/fg)とお手軽kill

0
0

bashのジョブの概念とコントロール

bashのジョブの話とkill の話。

Ctrl+zでプロセスはどこへ行くのか?

takuya@~$ ログアウト
停止しているジョブがあります。
takuya@~$ 

停止してるジョブってなんだよ!!!ってなる人が多い。

ジョブはどこに行った。

bashのプロセスはどこで管理されているのか

「停止してるジョブがでるから」、とタブ開きまくってませんか? screen / tmux でバンバンとタブ開けてませんか?

実は、bashで起動したプログラムは ジョブとして いま使ってたその、bashが管理しています。

また、ジョブはSTDOUT/STDIN を繋いでるだけなので切り替えて使えます。

ジョブは恥だが役に立つ。

ジョブを切り替えて tmux も screen もタブもほとんど使いません。人に言わせるとダサいと揶揄されます。恥ずかしい気持ちにされることが有ります。

でも、わたしは、使っています。周囲にはジョブを切り替えてバンバン使う人を余り見かけないのですが。実はすごく役に立つ。

使用例:ファイルの編集中に実行したい

ファイルを編集しているときに。

vim my_sample.sh

つくった スクリプトシンタックスチェックや、ちょっと実行したいときにどうします?

編集中にちょっと実行したい。

bash my_sample.sh と ちょっと実行したいとき。みなさんはどうしますか?

別タブで開く?tmux 使う? vimproc 使う?それとも根本的にVimをつかわずIDEでやりますか?

実はその殆どの処理は・・・・ジョブ管理で終わるのです。

そのタブちょっと待って、それ job で済むかもよ?

覚えるコマンドはつぎの3つです。

  • fg
  • bg
  • jobs

操作方法を覚える

そしてジョブの操作のために覚えるものは

  • Ctrl+z
  • kill
  • command &

です。

Ctrl+zで ジョブをバックグラウンドに入れて、kill することが出来る。

$ vim my_sample.php 
CTRL+Z
$ php my_sample.php
$ fg 

この切替にいちいちタブを使う必要はありません。

そんなことしなくても ctrl+z で一時停止すればいいのです。

vim中に Ctrl+Z で一時停止

実行中に Ctrl + Z でvimを一時停止し、スクリプトを実行し、fg でもとに戻します。

ジョブコントロールの概念

vimのモードと同じように bashのジョブコントロールには3つのモードが有ります。

f:id:takuya_1st:20170101174002j:plain:w500

動いているものを Ctrl+z  で一時停止へ

一時停止をすると一時停止します。

一時停止から bg で バックグラウンドで起動

複数のプロセスがバックグラウンドにある場合。

$ bg 2  # job 2 をバックグラウンドにする

job の読み方

複数のコマンドを

takuya@~$ jobs(#######←もうちょっとコマンドをわかりやすく
[1]   停止                  man bash
[2]-  停止                  man vim
[3]+  停止                  man gawk

fg 3 とすれば、 3 つまり man gawkが最前面に来ます。 fg 2 とすれば、2 つまり man vimが最前面に来ます。

単にfg と打つば、 + がついてるものが出てきます。。

番号とスタックの両面で管理されています。

ジョブスタックについて

スタック
No.1 +
No.2 -
No.3 なし

fg bg を繰り返したときに出てくるものはコイツラです。

いちいち kill pidしてませんか?

job 管理されているなら、 ps で pid を見なくても kill 出来るんです。

kill %3

でジョブ3を削除できます!便利すぎる。

もういちいち、PID確認しなくていいんですよ。

job 放置したらどうなるの

放置したまま、ログアウトしたらどうなるのか。

HUPします。

Viewing all 1780 articles
Browse latest View live




Latest Images