BASHは,よく使われるスクリプト言語です.(詳細はこちらにある)
計算機クラスタで「ジョブ」を投入するとき,ほとんどのスーパーコンピューターで「bashのスクリプトを与える」ことになっています.
というわけで,BASHのスクリプトを「ジョブ文」とか「ジョブスクリプト」と考える向きも多いでしょうね.
コマンドにも用いられます.プログラムだと思っていたら,
head `which コマンド`
で最初の方を表示したら,最初に
#!/bin/bash
と書いてある,なんてのは全部BASHのスクリプトです.
どうやって起動するかというと,Macで【ターミナル】を起動すると
これで起動しています.
2019年以降, Macのデフォルト設定が変更になり,ターミナルを開くと,BASH ではなく, よく似た ZSH が起動するようになりました.ZSHの場合,こんな感じになります:
どこが違うのか?というと,カーソルの前が $ ではなく % になっている!のが大きな違いです.少し文法が違います. が,まあたいして変わらんので気にしない気にしない.
というわけで,あなたに2通りの選択があります.
いつもBASHを使いたい
LinuxやスパコンではBASHの場合も多いし,以下の説明は全部BASH用に書いてあります. 毎回 BASH が起動するように設定してしまいましょう:
これで,今後はターミナルをひらけば黙ってBASHです.
たまにBASHを使いたい
今後はZSHが主流になるという読みであれば, ZSHのままでも良いですね.それなら, 使いたい時だけ,毎回 exec bash と入力すれば良いのです:
なんでこんなめんどくさいことになったのかというと, bashはオープンソースで開発されているのだが, GPL2ライセンスをGPL3ライセンスに変更してしまったので, Appleが怒った.ということですね.
GPL2の場合:オープンソースの資源に自社の特許つきソフトを加えて商品を開発した.開発商品のソースコードを公開するのであれば,商品にして対価を受け取っても良い.
GPL3の場合:オープンソースの資源に自社の特許つきソフトを加えて商品を開発した.開発商品のソースコードを公開するのであれば,商品にして対価を受け取っても良い.ただし,その特許も公開したものと解釈する(つまり特許料は今後0円, 開発してもお金は取れない)
で・・・GPL3に企業が反発して騒ぎになっている,ということらしい. やっぱGNUはGNUだということか.
BASHの基本的機能
BASHの基本的機能は,
- 1行入力を受け取ると,実行する
というわけで,コマンドを入力すると実行するわけです:
多くの人がここまでしか使いませんが,BASHの機能はずっと多く,ちゃんとしたプログラミング言語になっています.プログラミング言語の基本は,変数に値を代入できることです. つまり
X=12
これは, 変数Xに値12を代入しています.BASHで用いられる変数は,整数と文字列の2種類があります.文字列を代入するには
Y="Hello World"
実際に値を持っているかどうか確かめるには,
echo "$Y $X"
printf "文字列は[%s] 整数は[%d]\n" "$Y" $X
などのコマンドが使えます. 空白を含む文字列は, コマンドの境目の空白と区別がつかないので, "" で囲っていることに注意.
コマンドを実行するbashならではの変数が $? です. これは,直前のコマンドの終了コードを与えます. 普通は正常終了なら0, 他は1とかです.
$PATHも面白い変数です.
echo $PATH
/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Applications/inkscape.app/Contents/MacOS:/opt/ImageMagick/bin:/Applications/Ghostscript.app/bin:/usr/
texbin:/usr/local/bin:/Users/sugimoto/bin:/usr/texbin
これは,PATHに含まれるフォルダーにあるプログラムファイルは, /opt/ImageMagick/bin/display なんて長々と入力しなくても, display と打てば実行できる,という変数です.
変数には実は2種類あります.普通の変数と,その上位である「環境変数」です.普通の変数を export すると環境変数になります:
export X
環境変数の特徴は,起動したプログラムから参照できる点です.例えば, C++なら
std::string PATH=getenv("PATH");
で環境変数 PATH の値を取得できます.export していないタダの変数は, 取得できません.
計算
整数は次のように計算できます.
(( i=0 ))
(( i++ ))
(( i=i-32 ))
文字列は結合したりできます
A="$Y"AHO
echo $A ← Hello WorldAHO と表示されます
繰り返しの制御
繰り返し同じ作業を繰り返すには, for文, while文, until文を用います.
for ((開始設定;終了条件;継続作業));do
コマンド
....
コマンド
done
例えば
sugimoto $ for((i=0;i<3;i++)); do echo $i; done
0
1
2
集合に対するforもあります.
for ELEMENT in SET; do コマンド;...;コマンド; done
例:
for file in *; do echo "FILE=$file";done
FILE=mix3.log
FILE=mix3d
FILE=mix3d.cfg
FILE=mix3d.pid
while 条件;do コマンド;.....;コマンド;done
配列
配列データは, 次のように定義します.
X=( x_1 x_2 ..... x_N)
あるいは成分を直接指定します.
X[2]="Centurion IV"
普通の変数Xがあるときに X[2] を定義すると, X[0]が元の値, X[1]が未定義, X[2]が"Centurion IV"になります.値を利用するときには ${変数名[引数]} です.
配列は,繰り返しの集合として用いることができます.ただし方法で微妙な違いが出ます:
未定義のX[1]でループは起動しません.空白を含むX[2]のせいで,想定通りに動いているのは for x in "${変数[@]}" だけですね.${変数[@]} は空白で分けられてしまい, "${変数[*]}" では, 配列ではなく「全文字列」で1回起動するだけです.X[1]が未定義なため,要素数は2になっています:
${#変数名[@]} 定義された要素の数
なんか,添字が整数の意味がないような気がします.それはその通りで,実はbashの配列は「連想配列」であり,定義域の値から値域の値への写像を定義するだけです.上の例は,単に,定義域の値に,まあ好みで整数を用いただけです.引数を(利点が不明ですが)整数に限定した「普通の配列」は, すでに伝説になりました.というわけで
X["sugimoto"]="BOKE"
などと定義できます.
条件判断
基本の条件分岐は if 文です:
if 条件; then
コマンド;
.....
コマンド;
else
コマンド;
....
コマンド;
fi
条件の一つは((数式))です:
ですが,まあ,bashの主要目的であるデータファイルやフォルダーの処理という点では,[ ファイル関係 ]が便利です.
- [ -f ファイル ] ファイルが存在したらtrue. [ ! -f ファイル ] の場合, ファイルが存在しなければtrue
- [-d フォルダー ] フォルダーが存在したらtrue
- [-x コマンド ] コマンドが実行可能ならtrue
- [ 条件 -a 条件 ] 2条件のAND
- [ 条件 -o 条件 ] 2条件のOR
- [ ! 条件 ] 条件のNOT
もう一つの条件分岐は case 文です.
case 値 in
パターン) コマンド
.....
コマンド
;;
パターン) コマンド
......
コマンド
;;
*) コマンド
....
コマンド
;;
esac
値が変数$Xでも大丈夫です.ただし空白がありがちなら"$X"としておきましょう.最後のesacは, if が fi で終わったように, caseのおしまいがesacってわけです.このパターンが強力無比で,
1 | 2 ) 1か2
1*) 1で始まる文字列
*4* | *5 ) 途中に4があるやつか, 最後が5で終わるやつ
[abc]* ) aかbかcで始まるやつ
などなど,いろんなパターンが使えます.
変数については,別の奇妙な条件文があります.
${X:-"ほげほげ"} 変数Xが未定義なら, ほげほげ,を出力します.
${X:="ほげほげ"} 変数Xが未定義なら, Xにほげほげを代入します
関数
関数は, 次のように定義します:
function BOKE(){
printf "%s\n" "Fuck You $1"
}
これで関数 BOKE() が定義できます.BOKEには引数を与えることができます.引数は, 前から順に$1, $2,.... となっています.よって
BOKE T-54
Fuck you T-54
と出力されます.引数を省略すれば$1="" になっています.もちろん,空白を含む引数は "このような 感じに" 囲わないと1セットとして認識できません.
リダイレクト
コマンドの入出力は,「キーボードで文字を打つ」「ディスプレイに出てきよる」と思えますが,これは「キーボードというファイルから入力」「ディスプレイというファイルに出力」しているだけです.ファイルですので,別のファイルを割り当てることができます.これをリダイレクトといいます.
- キーボード入力をmy_fileファイルから行う: command < my_file
- さらにディスプレイ出力をhis_fileに行う: command < my_file > his_file
- 出力をhis_fileに追記したい: command >> his_file
- いやいや,入力ファイルをここで作成したい(ヒア文書というらしー):
command << @
this.is.file contents
1.22
55
@ - なお,ヒアドキュメントの@は,他の1文字でも良いです.<< + とか, << & とか.その文字はドキュメント内のデータの戦闘文字にできなくなるから注意(あんたがデータとして書いたつもりでも,コンプータが,そこでドキュメントの終わりだと勘違いするでしょ?).
なにぶんファイルですので,あるコマンドの出力を,別のコマンドの入力にすることもできます.これをパイプと言います:
command1 | command2
この縦棒が「パイプ」で,command1 の出力がcommand2の入力になります.凝った例では
command1 < input_for_command1 | command_2 > output_of_command2
こうなってくると,コマンドの群れを一つで扱いたくなりますが,これは () でくくれば良いのです
( command1 | command2 ) < input | ( command3 | command4 | (command5 | command6))
スクリプト
ここまでは, キーボードでパチパチ入力していますが,もちろん,これをファイルに書いておくことも可能です.ファイルに書いた命令は,
. ファイル名 (見えにくいけど・・・ ドット+スペース+ファイル名)
で実行できます.ドットを入力せず,ファイル名だけで実行するように設定するには,ファイルに「実行可能ですよフラグ」をつけなければなりません.これは
chmod +x ファイル名
これで, ./ファイル名 で実行できるようになりました.ファイル名の前の ./ どっと+すらっしゅ,は,ファイルをPATHのどこかにコピーすれば,入力不要です.
bashは簡単な言語ですが, ルールが微妙ですので, 最初は戸惑うでしょうね.
並列処理
お前のノートパソコンには,複数のCPUコアが搭載されているでしょう.せっかくですので,並列計算しましょう.
例えばデータファイルが 00000.dat - 80000.dat まであり,8万個のデータをを全て描画して加工してGIFにしなければならないとします.一発1秒じゃとしても,普通にやったら8万秒じゃけん22時間かかるとよ.最新のMacbookProなら8コア16スレッドとかあるっちゃけん, 16個づつ描画したら, 1時間ちょいで完成すったい!
おれのMacbookAirは2コアだと?じゃLinuxサーバで48スレッドとか使えよ.OSはざっぱ同じ→同じプログラムでOK
とりあえずワーカースレッドで実行する関数を定義しましょう:
TMP=`mktemp -dt myXXX` ← いや単純に一時フォルダー作っただけ worker(){ draw $TMP/$1.pdf my_data/$1.dat ← my_dataフォルダーのデータをPDFに描画する beauty=`printf "%.6d" $1` ← 1.gif と11.gif ではダサいので 00001.gif みたいにする convert -geometry 350x350! \ $TMP/$1.pdf my_image/$beauty.gif ← $TMP/のPDFをmy_image のGIF画像にする } madadayooon(){ test $STEP -lt 80000 }
ほんじゃ,いきましょう
STEP=0 while madadayooon ← madadayooon がtrueになるまで繰り返す do worker $STEP&(( STEP = STEP + 1 ));if madadayooon;then :;else break;fi ← worker & つまりバックグラウンドでworker起動. さらに STEPを1増やす. で, 完了してたらループを抜ける worker $STEP&(( STEP = STEP + 1 ));if madadayooon;then :;else break;fi ..... 利用するスレッドだけ繰り返す worker $STEP&(( STEP = STEP + 1 ));if madadayooon;then :;else break;fi wait ←ワーカースレッドの完了を待つ echo -e "[$STEP]\c" ←現在のステップを表示 done gm convert -delay 5 my_image/*gif animation.gif ←面倒なので動画にする
これでOK!