ちなみにこの「処理が重い」ところを課題として、スクリプトにとどまらずプ ログラム言語として発展していったのが perl/php/... です。ここでは入門として、シェルスクリプトとはどんなものかを概観します。 ちなみにシェルには大きくわけて (bourne) sh 系と csh 系の2系統があり、 引数(パラメータ)を含む変数の扱いかた等、sh 系と csh 系ではかなり構文 (文法)が違います。csh は(対話的に使うには便利なのですが)スクリプト に向いていないということもあり、この文書では主に基本的な sh 系の構文で 説明します。
liweb ではログインシェルが csh (厳密にはtcsh) なので注意が必要です。
$ mkdir $HOME/.backup $ tar cfz $HOME/.backup/backup.tar.gz *となります。
ちなみにオプションの意味は、これくらいならたかがしれていますが、もっと複雑になってくると、いちいち タイプするのは面倒です。そもそも、バックアップにはある程度の時間がかか る可能性もあるので、前のコマンドが終了するまで次のコマンドを入力できま せん。これでは作業効率が上がらない場合がありそうです。で、保存したファイルを取りだすのは c のかわりに x (eXtract) オプション を使うのですが、詳細は略します。また、ファイル名の最後 (suffix) は Windows の lzh や zip のように決まっているわけでは無いですが、慣習とし て tar.gz か tgz にしておくと後でみてすぐわかるので便利でしょう。
- c=ファイルをあらたに作る (Create)
- f=作るファイル (File) を指定する
- z=ファイルを gZip で圧縮する
あと、実は .backup というディレクトリ名にしておくと、 * (wildcard) にマッチしない、ということなのですが詳細は略します。
そこで、シェルスクリプトを作ります。
vi でもいいのですが、初学者に厳しいです。
liweb には nano というエディタがありますので、それを使ってください。
[maechan@liweb ~]$ nano backup.sh
のようにコマンドとして打ち込みます。ここで backup.sh は編集したいファ
イル名です。元々なければ新たにつくられますし、元からあるものであれば、
編集モードとして起動します。
ここでは、backup.shという名前のファイルを作ります。そのファイルの中味は、
mkdir $HOME/.backup tar cfz $HOME/.backup/backup.tar.gz *のように実行するコマンドを並べるだけです。WSHだと、普段メニュー とマウスで行っている作業をVB ScriptやJScriptに翻訳しながらプログラムを 作る必要があります。ところが、シェルスクリプトは普段使っているコマンド を書くだけでいいのです。
ちなみにファイル名の最後の .shは、無くてもスクリプトの実行には関係 ありません。これは DOS/Windows で実行ファイルは .exe でなければな らない、等といった制約があるのとは大違いです。.shを付けなくてもよいの ですが、そうなるとファイル名だけではC コンパイラなどを使って作ったバイ ナリの実行ファイルと区別できないので、最初のうちはつけておいたほうがい いでしょう。もちろん、ファイルサイズが全然違いますし、中を見れば簡単に 区別できますし、これを見分けるための "file" コマンドも存在します。
$ sh backup.shとすれば、あとは勝手に順番にコマンドを実行します。その間にほかの仕 事ができるわけです。でも、折角つくったスクリプトはそれ自体を1つの コマンドのように扱いたいわけで、実は作成したシェルスクリプトを普通 のプログラムと同じ方法で実行することもできます。但しその場合にはい くつか準備が必要です。
#!/bin/shこれはお約束で、「#!」以後に書かれたプログラムでこのスクリプトを実行す るという意味です。あと「#!/bin/perl」「#!/bin/ruby」などもあります。と はいえこの記述は環境によって異なります。というのは#! で指定するのはそ のスクリプトのインタープリタなのですが、そのパス(=それがどの場所にあ るか)はシステムによって微妙にことなるからです。例えば多くのLinux ディ ストリビューションでは、「#!/bin/perl」「#!/bin/ruby」ではなく 「#!/usr/bin/perl」「#!/usr/bin/ruby」になりますし、perl をソースから 作ってインストールした場合には /usr/local/bin/perl である可能性が高い です。ちなみにperlやrubyのパスは whichコマンド等で調べられます。
$ chmod +x ./backup.shとします。この2つの作業を行えば、
$ ./backup.shで実行できるようになります。もしパスがきれていれば($path に含まれ ているディレクトリにおいておけば、あるいはカレントディレクトリを含 んでおけば)"./" は不要で backup.sh だけで実行できます。
$ ./backup.sh > log.txtとすれば、出力結果をlog.txtというテキストファイルに保存できます。また、 定期的に実行するのであれば、スクリプトの中でリダイレクトを指定すること もできます。
echo 'Hello, world'とするわけですが、ちょっと長い文字列を出力したいときや、HTMLファイルへ の加工を行うなら、ヘッダなどを見やすい形で記述したいと思うわけで、そん なときに便利なのが「ヒアドキュメント」です。HTML の外枠をecho コマンド で出力する場合、
#!/bin/sh echo "<HTML>" echo "<HEAD>" echo "<TITLE></TITLE>" echo "</HEAD>" echo "<BODY>" echo "<H1></H1>" echo "<P>" echo "</P>" echo "</BODY>" echo "</HTML>"ですが、ヒアドキュメントを使うと、
#!/bin/sh cat << EOS <HTML> <HEAD> <TITLE></TITLE> </HEAD> <BODY> <H1></H1> <P> </P> </BODY> </HTML> EOS
となります。「<<」の後に指定した文字列が出現する直前まで、コ マンドに対する標準入力として扱われます。上の例では、「EOS」を目印にし ています。ヒアドキュメントを使うと、出力したい文字列をそのまま書けばい いので、スクリプトをすっきりと記述できます。後から文字を追加するのも簡 単です。
もちろん解決手段はあって、シェルスクリプトには引数を渡すこと ができます。引数は、シェルスクリプトからは順番に$1、$2、$3、……として 参照できます。引数の数は「$#」で分かります。また、「$*」とすることで、 すべての引数を一度に参照できます。ちなみに「$0」はスクリプトが呼び出さ れたときの名前が入ります。
例として、簡単なあいさつを行うシェルスクリプトで引数を試してみましょ う。greeting.shという名前で、
#!/bin/sh echo "Hello, $1. This is $0." echo "Hello, $*. This is $0."という内容のファイルを作って chmodコマンドで、直接実行できるようにしま す。このシェルスクリプトを実行すると以下のようになります。
$ ./greeting.sh maechan Hi, maechan. I am ./greeting.sh. Hi, maechan. I am ./greeting.sh. $ ./greeting.sh maeda toshi Hi, maeda. I am ./greeting.sh. Hi, maeda toshi. I am ./greeting.sh. $ sh greeting.sh maechan toshi Hi, maeda. I am greeting.sh. Hi, maeda toshi. I am greeting.sh.
test=one echo $test
というシェルスクリプトを実行すると、
$ sh test1.sh one
となります。シェルスクリプトでは基本的に変数を文字列として扱います。 つまり、
test=1 test=$test+1 echo $test
の実行結果は、
$ sh add.sh 1+1
となります。ちなみに変数を数値として扱いたいときは、declareコマンド を使用します。例えば
declare -i test test=1 test=$test+1 echo $test
というシェルスクリプトを実行すると、
$ bash add2.sh 2
と、今度は整数演算を行った結果が返ってきます。つまり「-i」で、test という変数を整数値として処理することを指定できるのです。ちな みに変数に何も代入されていない場合は、空の文字列が返ってきます。
ちなみに、sh ではできませんが bash の場合は
test=1 test=$[test+1] echo $testと [] で囲むことで、その式を数値として処理することを指定できます。
変数に対するパターンマッチは主に4種類あり、非常に汎用性の高いものです。
たとえば変数 aaa に/home/maechan/script.sh という値を設定しておくと、
aaa=/home/maechan/my.script.sh echo ${aaa##/*/} echo ${aaa#/*/} echo ${aaa%%.*} echo ${aaa%.*}
というスクリプトを実行すると
my.script.sh maechan/my.script.sh /home/maechan/my /home/maechan/my.script
と表示されます。
if 条件文 then 実行文 elif 条件文 実行文 else 実行文 fiという構文です。このうち「elif」はなくてもよいですし、好きなだけ繰り返 すこともできます。また、「else」は使わない、あるいは1回だけ使えます。
ここで注意が必要なのは、条件が式ではなく文 つまり、コマン ドの実行であるということです。すなわち、一般的な真偽によって実行するか しないかを決定するのではなく、実行した文の「終了ステータス」で決定する というメカニズムです。
Linux に限らず unix 系のOSでは、あらゆる実行ファイルが終了時に自 分自身を呼び出したプロセスに対して整数のコードを返します。これが終了ス テータスと呼ばれるもので、普通は正常に終了したときに「0」を、エラーが 発生した場合などはそれに応じた数値を返します。そして、この終了ステータ スが0であることが、すなわち真ということになります。言い換えれば、条件 文が正常に実行を終了すれば、真であるということです。つまり、
if コマンドが正常に終了した then 通常の処理 else エラー処理 fi
という表現が成り立ちます。
とはいえ、これだけでは一般的な条件判断を行うのは面倒なことになります。 ということで、実は testコマ ンドというものが用意されています。このコマンドは、続く条件式を評価して、真ならば0 を、偽ならば1を終了ステータスとして返します。また、 test だけではぱっと見がわかり にくいので、「[]」として使えるようになっています。例えば
if [ $# -eq 1 ] then echo one. elif [ $# -eq 2 ] then echo two. elif [ $# -eq 3 ] then echo three. else echo many. fi
というシェルスクリプトなら、引数の数を教えてくれます(1、2、3、たくさん)。
testコマンドで使える条件式は、manコマンドで調べられます。代表的なものを 以下に表にまとめておきます。
条件式
|
意味
|
n1 -eq n2 | 数値n1とn2が等しい |
n1 -ne n2 | 数値n1とn2が等しくない |
s1 = s2 | 文字列s1とs2が等しい |
s1 != s2 | 文字列s1とs2が等しくない |
-e file | fileが存在する |
-z s1 | s1の長さが0である |
構文は、
for 識別子 in リスト do $識別子を使う文 done
です。これで、リストの内容を1つずつ識別子に代入して、それぞれについ てdoとdoneで囲まれた部分を実行します。
では、簡単な例を示します。以下の内容のスクリプトをgreeting2.shとして作成します。
#!/bin/sh echo hi $* for name in $* do echo hi $name done
これに3つの引数を与えて実行すると、
$ ./greeting2.sh maeda toshi yuki hi maeda toshi yuki hi maeda hi toshi hi yuki
となります。つまり$*を1つずつ処理しているわけです
while 条件文 do 実行文 done
条件文についてはforループと同じ扱いになります。終了ステータスが0な ら真、0以外なら偽ということです。until の場合は逆になります。
以下に例として PATHに設定されている文字列を分解するシェルスクリプトを示します
#!/bin/sh path=$PATH: while [ $path ] do echo ${path%%:*} path=${path#*:} done
実行例は以下の通りです。
$ echo $PATH /home/maechan/bin:/usr/local/netscape:/usr/java/j2sdk1.4.2_02/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/netfront/bin:/usr/local/netpbm:/usr/local/bin/mh:/usr/local/canna/bin:/usr/ucb:/usr/bin:/bin:/etc:/usr/etc:/usr/hosts:/usr/bin/X11:/usr/games: $ ./sp-path.sh /home/maechan/bin /usr/local/netscape /usr/java/j2sdk1.4.2_02/bin /usr/local/bin /sbin /usr/sbin /usr/local/netfront/bin /usr/local/netpbm /usr/local/bin/mh /usr/local/canna/bin /usr/ucb /usr/bin /bin /etc /usr/etc /usr/hosts /usr/bin/X11 /usr/games
#!/bin/bash # by maechan on 30/Jun/2019 # if [ "x$1" == 'x' ] ; then echo '# pwnedpasswords.com leaked-password checker' echo "Usage: $0 <password>" exit 1 fi P=$(echo -n $1 | sha1sum) if [[ ${P} =~ ^([0-9a-f]{5})([0-9a-f]{9}).* ]] ; then head=${BASH_REMATCH[1]} tail=${BASH_REMATCH[2]} R=$(w3m -dump https://api.pwnedpasswords.com/range/$head | grep -i $tail) fi if [ "x$R" == "x" ]; then echo "Not leaked" else echo "Leaked: $R" fi