それマグで!

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

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

bashの使い方のまとめ記事のインデックス

bash の使い方を1から見直すシリーズ

シェルスクリプトは、もうbashで書いていいよね。bashが動かない環境なんてないんだし。

bash の紛らわしい記号や歴史的経緯によるPOSIXかき分けなどがあって、bashに特化した記事って少ないし断片的なので、色々と調べたことを再構成してまとめて記事にしました。

私自身 [[ / [ の違いが全然わからないので調べ始めました。その結果あれこれ知見が得られたので書きながら復習をしていました。

気づけば、bash の教科書的なものになってしまいました。

何かの役に立てばいいと思って記事のインデックスをまとめておきます。まだ一部書けてないですがそのうち書きます。

はじめに

変数と配列と数値計算

  1. 変数
  2. 配列のまとめ
  3. 組込 let による数値計算とインクリメント
  4. 連想配列(assoc array / hash ) を使う。
  5. 文字列の追記(append)演算子の紹介
  6. 可変変数(抽象化)をする。
  7. 数値計算

変数の展開と置換

条件分岐とif の条件とループ

  1. 条件分岐 - if 以前の話
  2. 条件分岐 - if
  3. 条件判断 - 条件の一覧
  4. コマンドの終了ステータスの意味と見方

関数の定義と引数

正規表現とglob パターン

ループの構文のあれこれ

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

プロセスとジョブ

パイプ

環境変数

コマンドのオプション補完

知ってたら得する話。

関連リンク

参考資料

TODO

各ページに、記事間のリンクの貼ること

fio でHDD/SSDのブロックデバイスのベンチマークを取る

ブロックデバイスのベンチマークを取りたいと思いました。

ぱぱっとやる方法だと、 dd hdparm 等があるのですが、キャッシュなどいろいろ考えることも多いいので。今回は fio を試してみました。

まぁ普段の速度測定は dd でやっちゃうんだけどね。。。ネットワークは dd + ssh で。。。

fio のインストール

debian だと apt で一発ですね。さすがdebian

sudo apt install fio 

fio の確認

無事インストールされていることがわかる。

takuya@:~$ which fio
/usr/bin/fio

fio でテストする方法

fio でテストするためには、同じ条件でディスクを変えてテストすることになります。

このために、条件を一致させるための「ジョブファイル」というのを作るようです。

fio のジョブファイルのサンプル

takuya@:~$ cat myjob.fio
[Sequential-Read] # jobの名前
rw=read           # シーケンシャルでreadする
directory=/home/takuya/mnt/   # ベンチマークで使うディレクトリ
size=100m         # ベンチマークで使用するデータサイズ。キャッシュサイズを考慮して決める。
ioengine=libaio   # 非同期I/Oでテストする。指定しないとsync(同期I/O)になる。

[Sequential-Write]
rw=write          # シーケンシャルでwriteする
directory=/home/takuya/mnt/
size=100m
ioengine=libaio

[Random-Read]
rw=randread       # ランダムでreadする
directory=/home/takuya/mnt/
size=100m
ioengine=libaio

[Random-Write]
rw=randwrite      # ランダムでwriteする
directory=/home/takuya/mnt/
size=100m
ioengine=libaio

あとは実行

ジョブファイルを作ったら、コレを実行する。

takuya@:~$ fio myjob.fio

出力結果

結構あれこれ出力されるが、bw さえ見ておけば比較できると思う。

Sequential-Read: (groupid=0, jobs=1): err= 0: pid=19749: Wed Jan  4 20:35:30 2017
  read : io=102400KB, bw=48393KB/s, iops=12098, runt=  2116msec
Sequential-Write: (groupid=0, jobs=1): err= 0: pid=19750: Wed Jan  4 20:35:30 2017
  write: io=102400KB, bw=291738KB/s, iops=72934, runt=   351msec
Random-Read: (groupid=0, jobs=1): err= 0: pid=19751: Wed Jan  4 20:35:30 2017
  read : io=102400KB, bw=736685B/s, iops=179, runt=142337msec
Random-Write: (groupid=0, jobs=1): err= 0: pid=19752: Wed Jan  4 20:35:30 2017
  write: io=102400KB, bw=256000KB/s, iops=64000, runt=   400msec

読みづらい・・・出力単位をMB にしたい・・・unit を調べてみたけどkb_unit 設定しかなかった・・・

複数HDDのベンチマークを取って比較するジョブ・ファイルの構成

[HDD-A-Random-Write]
rw=randwrite      # ランダムでwriteする
directory=/home/takuya/mnt/
size=100m
ioengine=libaio
[HDD-B-Random-Write]
rw=randwrite      # ランダムでwriteする
directory=/tmp
size=100m
ioengine=libaio

こんな感じに複数作れば一回で終わる。

参考資料

http://qiita.com/smile-0yen/items/991ad53b8411364d9729

https://www.hitsumabushi.org/blog/2015/02/09/2227.html

bashの似てて紛らわしいもの ``バッククオートと $() について

似てて紛らわしいものシリーズ `バッククオートと $() について

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

``$( ) の違いのついて

コマンドの実行結果で置換される記述``$( ) はどちらもほぼ同じものです。

ただし、圧倒的な使いやすさの特徴差があります。それは複数重ねがけするときです。

ネストできる$() とネストが不便な ``

$() は重複して記述が圧倒的に楽です。

 echo $( dirname  $(realpath ~/Desktop/ )   )

同じことを バッククオートで書くとエスケープ地獄です。。。これはしんどすぎる。

 echo ` dirname  \`realpath ~/Desktop/ \`   `

ネストしないならバッククオートの方が読みやすいかも?

ネストしないなら、バッククオートの方が読みやすいかもしれない。

echo `brew --prefix`/opt/openssl
echo $(brew --prefix)/opt/openssl

この辺は使う人の好みなのかもしれないです。

rubyphpjavascript でもバッククオートをよく使うので慣れてる方が読みやすいと感じるのかも?

bashの条件判断の関数名を美しくする

Bashの関数名に?を使う

bash の関数名には ? が使えます。つまりまぁまぁ美しい関数名が作れます

if の中が美しくない例

# sf-pwgenが存在したら alias をいれる
if type  sf-pwgen > /dev/null  2>&1  ; then 
        alias sf-pwgen-alphanum-12char="sf-pwgen -a alphanumeric -l 12 -c 1"
        alias sfpwgen="sf-pwgen -a alphanumeric -l 12 -c 1"
fi 

関数を使うべき

if の条件文の箇所は、ひと目見てわかりやすい関数名にするべきだと思う。

関数にする。
function cmd_is_exists {
  type "$1"  > /dev/null  2>&1
  return $?
}
if cmd_is_exists sf-pwgen ; then 
  alias sf-pwgen-alphanum-12char="sf-pwgen -a alphanumeric -l 12 -c 1"
  alias sfpwgen="sf-pwgen -a alphanumeric -l 12 -c 1"
fi 

条件文だとひと目で分かる関数名にする。

? を関数の名前に使えるので、これをもう少しわかりやすい関数名にすることが出来る。

function cmd_exists? {
  type "$1"  > /dev/null  2>&1
}

よく使いそうなものも関数にしておくと便利

たとえば、数字かどうかのチェック

function is_int?  {
  [[ $1 =~ ^-?[0-9]+$ ]]
}

たとえば、bash の文字列の変数置換も書式を覚えづらいので関数にしておくとか

function upcase {  echo ${@^^}; }
function downcase {  echo ${@,,}; }
function Capitalize {  echo ${@^}; }

たとえば、LinuxOSXか判別し、同じbashrcを使いまわせるようにするなど。

function is_osx? {
    plat_name=$(uname -a )
    plat_name=$(downcase $plat_name)
    [[ $plat_name == darwin* ]]
}
function is_linux? {
    plat_name=$(uname -a )
    plat_name=$(downcase $plat_name)
    [[ $plat_name == linux* ]]
}

if is_osx?  ; then
   source ~/.bashrc.osx
elif is_linux?; then 
   source ~/.bashrc.linux
fi 

このように、関数とその名前を上手に使える。記号だらけで読みにくいシェルスクリプトだって、工夫次第でかなり読みやすいコードになると思う。

参考資料

https://github.com/azet/community_bash_style_guide

bashの正規表現マッチで後方参照

bash正規表現マッチで後方参照。

bash[[ コマンドで、正規表現マッチが使える話は以前書きました。

そのときには、意図的に書きませんでしたが、bash正規表現マッチでは後方参照が使えます。bash rematch の変数に格納されます。

ちなみに、rematchは re:match ではなく、regex matchのre だと思います。

bash正規表現マッチと後方参照

とりあえず、動作例を見てください。

takuya@:~$ [[ takuya =~ taku(.+) ]]
takuya@:~$ echo ${BASH_REMATCH[@]}
takuya ya

後方参照は、BASH_REMATCH

BASH_REMATCH の変数に、配列で返ってきます。コレだけ覚えておけば十分。

配列は、 BASH_REMATCH[0] がマッチ全体で、BASH_REMATCH[n] が後方参照にマッチをした箇所

後方参照は意外に使える。

正規表現マッチだけじゃなく、後方参照で文字列を抜き出すのは意外に使えると思います。

[[ $name =~  $regex ]]
if [[ -v BASH_REMATCH[1] ]]  ; then 
  echo ${BASH_REMATCH[1]}
  str=${BASH_REMATCH[1]} 
if 

などとして、正規表現マッチを使って文字列を抜き出したり加工したり出来るのは重宝しそうですね。

なので、extglobの正規表現っぽい glob パターン を使うのは避け、正規表現をガンガン使ったほうがマシなのではないでしょうか。

extglob でも十分かもしれませんが、extglob はon/off が設定環境依存になるのに対し、正規表現は設定無しでいけます。

grep正規表現で後方参照したくてもできないときに活用してはいかがでしょうか。

参考資料

bash の for と パイプ(xargs) の一番大きな違い

bash の似たものシリーズ

初心者が躓きそうなbash で似て紛らわしいものの違いをはっきりさせようとさせるシリーズ。

bash に於ける xargs / for には違いがあるのか?

あります。結構大きな違いが有ります。

それぞれのループの回しかたを復習しておきましょう。

for ループでコマンド結果を回す
for e in some_command ;  echo $e; done 
xargs パイプでコマンド結果を回す
some_command | xargs -I @ echo @; 

この2つの、コマンドの実行には違いが在るのでしょうか。またコマンドの実行の速度には違いが出るでしょうか?

2つの違い

  1. xargsはサブシェルを起動するので変数の名前空間が違う
  2. コマンドの終了を待つfor と 終了を待たない xargs

こんかいは、この後者の違いについて着目したいと思います。

毎秒カウントを出力するスクリプトを作ります。

まず、終了に10秒かかるスクリプトを作ります。

このコマンドは、毎秒1回カウント回数を出力します。

10.times.loop.sh
#!/usr/bin/env bash

for (( i=0; i<10; i++   )) ; do
  echo $i;
  sleep 1 ;
done

単体で実行すると、1秒ずつカウントします。

0    
# sleep 1
1
# sleep 1
2
....
9
xargs で実行したとき。
takuya@~$ bash  10.times.loop.sh | xargs -I@ echo xargs @
xargs 0
xargs 1
xargs 2
xargs 3
xargs 4
xargs 5
xargs 6
xargs 7
xargs 8
xargs 9

for で実行したとき。

takuya@~$ for  i in $( bash 10.times.loop.sh ) ; do   echo for $i;   done ;
for 0
for 1
for 2
for 3
for 4
for 5
for 6
for 7
for 8
for 9

ブログで書くとおなじみに見えます。

ブログで書くと同じに見えます。が、全然違います。

動画で見てください。

xargs の例

youtu.be

for の例

youtu.be

動作の違い

xargs
xargs は 前のコマンドから出力が出るたびに処理される。
for
for だと、前のコマンドが終了するまで何も出来ない。

for とxargs

それは for はコマンドの実行終了を待って処理しているから。 xargs は出力があるたびに stdin から読み込んで逐次処理をするから。

for i in COMMAND; do  echo $i ;done

この場合、for は COMMAND が終了するまでイテレーションの要素数が決まらないので、何も出来ない。

COMMAND | xargs -I @ echo @

この場合、xargs は前のコマンドから受け取って、入力が来たら逐次処理をする。

これは sed などでも同じこと

パイプライン処理の有能さは、非同期に実行できるところにある。

takuya@~$ bash 10.times.loop.sh| sed -e  "s/\([0-9]\)/sed \1/"
sed 0
sed 1
sed 2
sed 3
sed 4
sed 5
sed 6
sed 7
sed 8
sed 9

もしコレを for で回してしまうと・・・コマンドの終了待ちが出てしまう。

for  i in $( bash 10.times.loop.sh )  ; do
      echo $i | sed -e  "s/\([0-9]\)/sed \1/;
 done ;
## for の引数のコマンドの終了待ちが出る

awk でも同じ

takuya@~$ bash 10.times.loop.sh| awk '{ print gensub(/([0-9])/, "awk \\1",1, $0) } '
awk 0 # 逐次処理される。
awk 1
awk 2
awk 3
awk 4
awk 5
awk 6
awk 7
awk 8
awk 9

gensub は gnu awk の機能名ので osxbsd awk にはないけど。

まとめ パイライン

このことは、パイプライン処理では、10.times.loop.sh の標準出力と xargs 標準入力を繋いでから起動することに起因する。

パイプライン処理などで、STDIN/STDOUTを接続するときは、接続される側する側の両方を起動して繋いでコマンド実行しているからだったと思う。詳しくは exec 族 のC言語関数を見ればわかったと思う

時間のかかる処理の結果をループで回すときは、for よりも パイプライン処理をしたほうが無駄が少なくて嬉しい。

bashの似てて紛らわしいもの [[ / [ / test  はどこが違うの?

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

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

[ / [[ の違い

結論から言います [[ / [ は同じものです。 [[ が新しい書き方で機能強化されています。

[ / test の違い

違いはありません。同じものです。*1

[[ は [ とほぼ同じ機能と目的を持ちます。

個人的な意見ですが、 bashを書く上においては [[ でいいと思います。

というか bash が動かない環境なんてもうなくなったんだし、 [[ でいいと思います。個人的な意見ですが。

[/ [[ のヘルプを見てみます。

ちなみに bash で「返す」といえば終了ステータスのことです。

help [[
[[ ... ]]: [[ expression ]]
    条件式のコマンドを実行します。

    条件式 EXPRESSION の評価結果に基づいて 0 または 1 を返します。
     (略
    終了ステータス:
    EXPRESSION の値に基づいて 0 または 1 を返します。
help [
[: [ arg... ]
    条件式を評価します。

[POSIXの古い書き方*2UNIXの考古学をする人なんかが好きそうです。

機能面でも違う

おさらいです。

  • [ は古い
  • [[ は出来ることが増えた

bash を使う限りにおいて 積極的に [[ を使って構いません。っていうか [[ つかえ*3

[ は古いが役に立つ。

[ 後方互換性(つまり古いシェルスクリプトや sh だけに書かれたもの)を実行するときに今でも使われるようです。

dash / sh などのインタプリタや、POSIX互換原理主義に染まってる方々は使うことが多いようです。

なんで [[ なんてものが導入されたのか

TODO:このへんもう少し詳細に書くべき、いつから、なんのために、どうして、どこで使えるのか

便利だから。

[[ は [ に比べてどの辺が便利?

[[ は && が使える
[[ は || が使える
[[ は -e FILEが使える
[[ は -eq が使える
[[ は = でglob マッチが使える
[[ は =~ の 正規表現マッチが使える
[[ は < で文字列長さ比較ができる

[[[ で出来ることは全てできるのです。*4

[ には出来なかった事が[[にはできるのです。

なぜ [ では &&が出来ないのか?

bash のコマンド記号に解釈されてしまうから。

たとえば、<記号は リダイレクトとかぶります。

command  < finename

たとえば && 記号は コマンドの並列実行とかぶります

command && command

たとえば || 記号は パイプライン処理やコマンドの並列記述とかぶります

command || command

たとえば & 記号は バックグラウンド起動とかぶります

command &

これらはすべて、比較処理で衝突を引き起こします。

[ varname  < varname ]            # STDINリダイレクトに見える
[ varname  > varname ]            # STDOUTリダイレクト に見える
[ command || command ]        # パイプに見える
[ command &  command ]       # バックグラウンドに見える

[ を使うとエスケープ処理が必須!?

&& < >| といった記号がシェル・コマンドでで使われます。

そのため、[ を使うとエスケープ処理が必要になっていました。

## リダイレクトに解釈させない
[ varname  \< varname ]            
[ varname  \> varname ]            

エスケープ処理がとってもイメージしにくいですね!!!!

この<めんどくさい>エスケープ処理から私達を開放してくれる。

エスケープの解放軍は我らが [[ なのです。

エスケープ処理の有無による違いの例
$ [ aaaaa \< aaa ]; echo $?
$ [[ aaaaa < aaa ]]; echo $?

どちらのほうが読みやすいですか。少なくとも初心者には後者だと思います。

ね? [[ でいいでしょ?

存在を認知して使い分けできたら、 test/[ なんて捨てちゃえよ。POSIX互換なシェルスクリプトを書くときだけ[[[ に書き換えたらいいでしょ *5

[ / test の更に困ったこと。

[ には2種類あります。

一つが bash のビルトイン関数 [ 。もう一つが /usr/bin/[ です。

[ はそのファイル名のコマンドでもあります。

2種類の [、1つだけの [[

つまり、 [ は二種類あります。次の2つです。

  • function [
  • /usr/bin/[

それに対し [[ は関数のみです

  • builtin function [[

関数とコマンドどちらが優先されるのでしょうか?

優先度は ビルトイン>ファイル

bashにおける優先度は次のようになります。*6

関数
PATH

そのため通常はビルトイン関数が優先度が高いので ビルトイン関数が使われます。

GNU Bashをメインで使う私達にとって、/usr/bin/[ は互換性のためにあると行っても過言ではないと思います。

[test はほぼ同じものです

[ / test は次のすべてが候補になります。

  • ビルトイン関数 [
  • /usr/bin/[
  • /usr/bin/test
  • ビルトイン関数 test

4つもある・・・互換性のために test / [ がいくつもあって更に混乱を招くことになっているのです。

わけわからんわ。 わたしは [[ でいく!みなさんもどうですか?

わたしは、test は help test で比較条件を見るときだけ使うことにしました。

騙されたと思って [[ だけで書くといいですよ。

[[ を使うときの 一つだけ注意

[[ を使うときは test -a / test -o は使わないこと。これでスッキリしますよ

takuya@~$ [[ 0 = 0 && 1 == 1 ]] ; echo $?
0
takuya@~$ [[ 0 = 0 -a 1 == 1 ]] ; echo $?
-bash: 条件式に構文エラーがあります
-bash: `-a' 周辺に構文エラーがあります
takuya@~$

*1: 強いて言うなら [ がペアとなる ] を最後に必要として、test はいらないところ。

*2: [[ もPOSIX予約語ではあるんだけどね 。

*3:意見には個人差が有ります

*4: -a -o は出来ないけど && || がその代わりに使えるから問題なし。

*5:意見には個人差があります

*6: 正確には alias - hash - function - PATH の順番だったと思います

bashの似てて紛らわしいもの =/==と= 代入&比較の注意点

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

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

=/==と= の違い

=はいくつかのパターンで出てきます。

  • 比較演算子として
    • = で比較
    • == で比較(=と同じ
    • =~ で正規表現マッチ
    • 数値の比較
  • 代入指示子として
    • 変数(文字列)の代入
    • 数値の代入

これらのそれぞれで微妙な差異があるのです。めんどくさいよねー

大きな違い。両端スペース

=のスペースの関係。他のプログラミング言語を触った人はこのへんで、頭が???になると思う。

変数に代入するときはvarname=value のように書く、スペースは許されない。

文字列を比較するときは [[ / [ の中で使われる。スペースの有無は問われない。

変数に代入する場合(文字列)
vaname=value
スペースは許されない
vaname = value # コマンド引数に見えてしまう。
比較で使う場合(文字列
# どちらも可
[[ $vaname = value ]] ; echo $?
[[ $vaname=value   ]] ; echo $?

数値を扱う場合。

数値を扱う場合、文字列の場合と違って= は 代入、 ==は比較と明確に区別されます。

ただし、 let(( )) で記述が異なります。

こちらも=のスペースの関係が異なるので、頭の中をスッキリさせないと混乱します。

変数に代入する場合
let i=0
(( i=0 ))
(( i = 0 ))
let でスペースは許されない(重要
# 次の記述は出来ない
let i = 0
let i= 0
let i =0
(( )) ではスペースは許される
### 次の記述はすべて許される。
(( i = 0 ))
(( i= 0 ))
(( i =0 ))

比較演算子としての=

文字列の比較演算子として使うとき = / == のどちらも同じ役目を果たす。

さらに、= / == の右辺値はglob のパターンとして扱われる。$ から始まる文字列($nameなど)は比較前に変数が展開される。

さらに、glob は shopt extglob で拡張パターンを使うことが出来る。

拡張パターンは正規表現っぽくて反って分かり辛いので、正規表現比較 =~ を使ったほうがわかりやすい。

=/== の比較の例
[[ $name =  takuya ]] ;   echo $?  # == と同じ
[[ $name == takuya ]] ;   echo $?  # = と同じ
[[ $name == $username ]]; echo $?  # 両方に変数が使える。
[[ $name == taku* ]] ;    echo $?  # 右辺値は常にglobと解釈されるです。
[[ a =  [a-z]  ]]    ;    echo $?  # 正規表現ではなくglob です
[[ a =~ [a-z] ]]   ;    echo $?  # これは正規表現

とくに extglob on でのみ許される [[ = [a-z] ]] はshopt 環境に左右されるので怖いね

まとめ

ややこしいよね。とくに =前後のスペースはハマりどころだわ。

出現箇所/opr REPL [[ / [ (( / $(( let
a=0 代入 比較 代入 代入
a = 0 cmd:a args:= 0に解釈 比較 代入 syntax error
a==0 a='=0' 比較 比較 比較
$a=$name 変数展開+代入 変数展開+比較 変数展開+代入 変数展開+代入
a=str 代入 比較 変数展開+代入 変数展開+代入

注意点

この記事においては、コマンドの PS1があると非常に見づらいので、PS1='' にした

$ (( 1==1 ))   #$(( 1==1 )) に見えてしまう。
(( 1==1 ))   #こうした
$ varname=string   #$varname=string に見えてしまう。
varname=string    #こうした

参考資料

  • bash -x による実行により確認した。

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

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

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

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

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

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

ジョブはどこに行った。

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

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

そのタブ本当に必要ですか?ジョブの停止と再開を知らないだけではありませんか?

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

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

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

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

でも、わたしは、tmuxの代わりにジョブ切り替えをメインで使っています。

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

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

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

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 の読み方

複数のコマンドを起動して切り替えて使うのに jobs を使いましょう。

jobs を実行すると次のように、現在実行(停止中)の実行中プログラム一覧が出てきます。

まずはじめに、ジョブを起動して一時停止してみます。

takuya@Desktop$ man bash

[1]+  停止                  man bash
takuya@Desktop$ man vim

[2]+  停止                  man vim
takuya@Desktop$ man gawk

[3]+  停止                  man gawk

man を実行して、表示しているときに Ctrl-Z を押すと、コマンドが一時中断されて、シェルプロンプトに戻ってきます。

この状態で、jobsコマンドを叩きます。すると。。。

takuya@~$ jobs
[1]   停止    man bash 
[2]-  停止   man  vim 
[3]+  停止   man gawk

これら、3つのジョブはすべて一時停止されていることがわかります。

ここから、次のようにすると、コマンドを再開することができます。

fg 番号
  • fg 3 とすれば、 3 つまり man gawk が最前面に来ます。
  • fg 2 とすれば、2 つまり man vim が最前面に来ます。
  • fg 1 とすれば、2 つまり man bash が最前面に来ます。
fg 1 と fg  は同じです。

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

これは、スタックと呼ばれるもので、上に積んだもの(最後に使ったもの)が最上位に来ています。次はこれについて見てきます。

ジョブスタックについて

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

先程のジョブ一覧をもう一度よく見てみます。

takuya@~$ jobs
[1]   停止    man bash 
[2]-  停止   man  vim 
[3]+  停止   man gawk

数字の他に記号がついていることに気づくと思います。これがスタックを示しています。

スタック(最後に使った順)
No.1 +
No.2 -
No.3 なし

単純に、fg bg を繰り返したとき、最初に出てくるものは、このスタック順に取り出されているのです。

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

シェルで実行し、バックグラウンドプログラムを終了するのに、プロセス番号を指定してませんか。jobsをちゃんと理解してない人にありがちです。

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

kill %3

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

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

job 放置したらどうなるの

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

HUPします。

ジョブ使いましょう。

ここまでやってみてどうでしたでしょうか。Ctrl-Zの意味はわかりましたか?ジョブを複数起動して切り替えるという意味がわかったでしょうか。

複数のコマンドを起動するのであれば、ジョブが一番お手軽で便利です。

vim でファイルを編集しながら他のファイルを見たり実行したりするのもお手軽ですし、なにもインストールが必要ありません。bashだけで完結します。

ターミナルのタブ機能、またscreenやtmuxに頼りがちな人は、最初にジョブを習いそびれたのではないでしょうか。

2019-08-23

記述を修正

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

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の何もしない特殊コマンド : コロン

: はコマンドです。

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

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

: は何に使うの?

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

いつもtrueになります。こんなのなんの役に立つの?いえいえ、いつもtrueで文字数が少ない。これが重要なのです。

たとえば無限ループに使います。

無限ループの例*1
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
    結果として成功を返します。

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

同じです。しかし : のようにコメントなどに使うと意味が変わってしまいますよね?

# コメントの例
: ここは何もしない
true ここは何もしない

true は流石に使いづらいよ

なんの役にも立たない?

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

2019-08-23

記述を修正

参考資料

*1: 「 : 」 を強調するため doを改行から始めてますが、普通はdo の手前で改行はしません。

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

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

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

[[ / (( の違い

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

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

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

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

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

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

  • [[ は文字列比較で使われる
  • (( は数値比較で使われる。
[[ 0 == 0 ]] # 文字列として比較
(( 0 == 0 )) # 数値として比較

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

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

どちらも 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の似てて紛らわしいもの (( / $(( の丸括弧

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

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)) と書いたときも、 先に (( が実行され、結果の文字列 に置換されます。その結果echo 2 となりコレが実行されるのです。

コマンドとしての ((

(( はコマンドとして実行されます。

正しく数値計算ができたら終了ステータス0、比較結果がfalse なときも終了ステータス1を返します。

あと、ゼロ除算など数値計算が出来ない時は終了ステータス1を返します。

コマンドなので、コマンドが書ける所なら、どこにでも記述できます。つまり if 文や while などの条件(コマンド)として使えるのです。

コレは間違い
$ echo (( 1 + 1  )) # 間違い echo はコマンドを引数に取らない
コレらは全て正しい記述
## 足し算
$ (( 1 + 1 ))  ; 
## 変数の計算
$  i=0; (( i++ ))  ; echo $i ; 
## 変数の比較
$ (( i = 1 )); (( i > 0 )) && echo $i; 
## while 内で加算と比較
$ i=0; while (( i++ , i < 10 )); do echo $i ; done ;

true と false に注意

(( expression )) の 比較は(( 1 > 0 )) true になるが、終了ステータスは0である。文字は返ささない

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

補足すると、$(( expression ))は展開されるので終了ステータスは存在しない

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

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

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

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

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のwhile/until ループ構文について

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で詰まる人をよく見かけたので注意してほしい。

色々みていたら、 bash を書くときは do で一行浪費するより ; do と書くことのほうが多いようです。*1

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

until を使わずとも、 whileでも同じことです。

while ! <COMMANDS> ; do
  <COMMANDS>
done

until における break や continue も while と同じです。

until使う意味あんの。。。

参考資料

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

*1: ちなみに、昔のブログなどの記述を見てると do を改行するほうが多いような気がした。