通常、人々が「シェルスクリプト言語」と言うとき、頭に浮かぶのはbash、ksh、sh、またはその他の類似したLinux/Unixスクリプト言語です。スクリプト言語はコンピュータと対話するもう一つの方法です。グラフィカルなウィンドウインターフェース(WindowsでもLinuxでも関係ありません)を使用すると、ユーザーはマウスを移動してボタン、リスト、チェックボックスなどのさまざまなオブジェクトをクリックできます。しかし、この方法は、ユーザーが毎回コンピュータ/サーバーに同じタスクを実行させたい場合(例えば、写真の一括変換、新しい映画やMP3のダウンロードなど)には非常に不便です。これらすべてのことを簡単かつ自動化するために、シェルスクリプトを使用できます。

一部のプログラミング言語(Pascal、FoxPro、C、Javaなど)は、実行前にコンパイルが必要です。コードに特定のタスクを実行させるには、適切なコンパイラが必要です。

Shell脚本编程初体验

一方、php、javascript、visualbasicのような他のプログラミング言語はコンパイラを必要としない。そのため、それらはインタープリタを必要とするが、私たちはコードをコンパイルせずにプログラムを実行できる。

shellスクリプトもインタープリタのようなものだが、通常は外部のコンパイル済みプログラムを呼び出すために使われる。そして、出力結果や終了コードを取得し、状況に応じて処理する。

Linux世界で最も人気のあるシェルスクリプト言語の1つがbashです。そして私が思うに(これは私個人の見解ですが)、その理由は、デフォルトのbashシェルでは、ユーザーが履歴コマンド(以前に実行したもの)を簡単にナビゲートできるからです。対照的に、kshでは履歴を確認してコマンドを修正するために、.profileを調整したり、「魔法の」キーの組み合わせを覚えたりする必要があります。

さて、この紹介で十分だと思います。どの環境が自分に最適かは、あなた自身が判断してください。これからは、bashとそのスクリプトについてのみ説明します。以下の例では、CentOS 6.6とbash-4.1.2を使用します。同じバージョンか、それ以上のバージョンがあることを確認してください。

Shellスクリプトのストリーム

シェルスクリプト言語は、数人の人々とおしゃべりするようなものです。すべてのコマンドを、あなたを助けてくれる人々として想像してください。ただし、正しい方法で彼らに頼む必要があります。例えば、文書を書きたいとします。まず、紙が必要です。次に、誰かに内容を話して、書いてもらいます。最後に、それをどこかに保存したいと思います。あるいは、家を建てたい場合、土地を空にする適切な人を雇う必要があります。彼らが「仕事は終わった」と言ったら、今度はレンガを積んでくれる別の技術者を頼ることができます。最後に、それらの技術者たちも「仕事は終わった」とあなたに伝えたら、塗装工を呼んで家をペイントしてもらえます。もし壁ができる前にペンキを塗らせたらどうなるでしょうか?彼らは文句を言い始めるでしょう。ほぼすべてのこれらの人間のようなコマンドは話すことができます。問題なく仕事を完了したら、「標準出力」に知らせます。もし頼まれたことを実行できない場合は、「標準エラー」に知らせます。こうして、最終のにすべてのコマンドは「標準入力」を通じてあなたの指示を聞きます。

簡単な例——Linuxターミナルを開いてテキストを入力するとき、あなたは「標準入力」を通じてbashと話している。では、bashシェルに尋ねてみよう。who am i(私は誰?)でしょう。

root@localhost ~]# who am i                                <--- 你通过标准输入对 bash shell 说
root     pts/0        2015-04-22 20:17 (192.168.1.123)     <--- bash shell通过标准输出回答你

さて、bashが理解できないいくつかの問題について話しましょう:

[root@localhost ~]# blablabla           <--- 哈,你又在和标准输入说话了
-bash: blablabla: command not found     <--- bash通过标准错误在发牢骚了

「:」の前の最初の単語は、通常あなたに文句を言うコマンドです。実際には、これらのストリームのそれぞれには独自のインデックス番号(LCTT注釈:ファイルハンドル番号)があります:

  • 标准输入(stdin) – 0
  • 标准输出(stdout) – 1
  • 标准错误(stderr) – 2

実際にどの出力コマンドが何を言ったかを知りたい場合は、その発言をファイルにリダイレクトする必要があります(コマンドの後に大なり記号">"とストリームインデックスを使用):

[root@localhost ~]# blablabla 1> output.txt
-bash: blablabla: command not found

この例では、ストリーム1(stdout)をoutput.txtという名前のファイルにリダイレクトしてみます。そのファイルの内容を確認してみましょう。catコマンドでそれを行うことができます:

[root@localhost ~]# cat output.txt
[root@localhost ~]#

空のようです。では、ストリーム2(stderr):

[root@localhost ~]# blablabla 2> error.txt
[root@localhost ~]#

さて、文句が消えました。そのファイルを確認してみましょう:

[root@localhost ~]# cat error.txt
-bash: blablabla: command not found
[root@localhost ~]#

案の定です!すべての文句がerrors.txtファイルに記録されていることがわかります。

時には、コマンドは同時に生成するstdoutとstderr。それらを異なるファイルにリダイレクトするには、次のステートメントを使用できます:

command 1>out.txt 2>err.txt

ステートメントを少し短くするには、"1"を省略できます。デフォルトではstdoutリダイレクトされる:

command >out.txt 2>err.txt

では、「悪いこと」をしてみましょう。rmコマンドでfile1とfolder1を削除してみましょう:

[root@localhost ~]# rm -vf folder1 file1 > out.txt 2>err.txt

では、以下の出力ファイルを確認しよう:

[root@localhost ~]# cat out.txt
removed `file1'
[root@localhost ~]# cat err.txt
rm: cannot remove `folder1': Is a directory
[root@localhost ~]#

見てわかるように、異なるストリームは異なるファイルに分離されました。時には、これもあまり便利ではありません。エラーが発生したときに、特定の操作の前後で連続して起こったことを確認したい場合があるからです。これを実現するには、2つのストリームを同じファイルにリダイレクトできます:

command >>out_err.txt 2>>out_err.txt

注意:">"の代わりに">>"を使用していることに注意してください。これにより、ファイルを上書きするのではなく、追記できます。

あるストリームを別のストリームにリダイレクトすることもできます:

command >out_err.txt 2>&1

説明しよう。すべてのコマンドの標準出力はout_err.txtにリダイレクトされ、エラー出力はストリーム1(上で説明した)にリダイレクトされ、そのストリームは同じファイルにリダイレクトされる。この例を見てみよう:

[root@localhost ~]# rm -fv folder2 file2 >out_err.txt 2>&1
[root@localhost ~]# cat out_err.txt
rm: cannot remove `folder2': Is a directory
removed `file2'
[root@localhost ~]#

これらの組み合わせた出力を見ると、次のように説明できます:まず、rmコマンドはfolder2の削除を試みますが、それは成功しません。なぜならLinuxでは-r键来允许rmフォルダを削除するコマンドが必要だからです。一方、2番目のfile2は削除されます。rm提供-v(詳細)キーを指定することで、rmコマンドに削除された各ファイルまたはフォルダを報告させます。

これらが、リダイレクトについて知っておくべきほぼすべてです。「ほぼ」と言ったのは、もう一つ重要なリダイレクトツールである「パイプ」があるからです。|(パイプ)記号を使用することで、通常はstdout流。

例えば、次のようなテキストファイルがあるとします:

[root@localhost ~]# cat text_file.txt
This line does not contain H e l l o  word
This lilne contains Hello
This also containd Hello
This one no due to HELLO all capital
Hello bash world!

そして、その中から"Hello"を含む行を見つける必要があります。Linuxにはgrepその作業を実行できるコマンド:

[root@localhost ~]# grep Hello text_file.txt
This lilne contains Hello
This also containd Hello
Hello bash world!
[root@localhost ~]#

ファイルがあり、その中を検索したいとき、これはとても便利だ。しかし、別のコマンドの出力の中から何かを探す必要がある場合はどうすればいいだろう? そう、もちろん、出力をファイルにリダイレクトしてから、そのファイルの中を検索すればよい:

[root@localhost ~]# fdisk -l>fdisk.out
[root@localhost ~]# grep "Disk /dev" fdisk.out
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

もし二重引用符で囲まれたスペースを含む内容をgrepしようとしたらどうでしょう!

注意:fdiskコマンドは、Linuxオペレーティングシステムのディスクドライブに関する情報を表示します。

見てわかるように、この方法は非常に不便です。なぜなら、すぐに一時ファイルスペースを散らかしてしまうからです。このタスクを実行するには、パイプを使用できます。パイプを使用すると、あるコマンドのstdout別のコマンドへのstdin流:

[root@localhost ~]# fdisk -l | grep "Disk /dev"
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

ご覧のとおり、一時ファイルなしで同じ結果を得られた。私たちは、fdisk stdoutリダイレクトされたgrep stdin。

注意:パイプリダイレクトは常に左から右へ行われます。

他にもいくつかのリダイレクトがありますが、それらは後で説明します。

シェルでカスタムメッセージを表示する

ご存じのとおり、通常、シェルとの対話やシェル内での対話は、会話形式で行われます。それでは、実際に私たちと話すスクリプトを作成してみましょう。これにより、いくつかの簡単なコマンドを学び、スクリプトの概念をより深く理解できるでしょう。

ある会社のヘルプデスク管理者だとしよう。コール情報(電話番号、ユーザー名、問題の簡単な説明)を登録するシェルスクリプトを作成したい。この情報は、後で統計を取るために、プレーンテキストファイルdata.txtに保存するつもりだ。スクリプト自体は対話形式で動作し、ヘルプデスクのスタッフの仕事を少し楽にしてくれるだろう。では、まず質問を表示する必要がある。情報を表示するには、echoコマンドとprintfコマンドを使うことができる。どちらも情報を表示するためのものだが、printfの方がより強力である。なぜなら、出力をうまく整形でき、右揃え、左揃え、またはメッセージのための専用スペースを確保することができるからだ。簡単な例から始めよう。ファイルを作成するには、お好みのテキストエディタ(kate、nano、viなど)を使い、note.shという名前のファイルを作成して、次のコマンドを書き込んでください:

echo "Phone number ?"

如何运行/执行脚本?

ファイルを保存した後、bashコマンドを使って実行できます。そのファイルを引数として渡します:

[root@localhost ~]# bash note.sh
Phone number ?

実際、このようにスクリプトを実行するのは非常に不便です。もしbashコマンドをプレフィックスとして使用せずに実行できれば、より快適です。スクリプトを実行可能にするには、次のコマンドを使用できます:chmod命令:

[root@localhost ~]# ls -la note.sh
-rw-r--r--. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]# chmod +x note.sh
[root@localhost ~]# ls -la note.sh
-rwxr-xr-x. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]#

Shell脚本编程初体验

注意:lsコマンドは現在のフォルダ内のファイルを表示します。-laキーを追加すると、より多くのファイル情報が表示されます。

私たちが見たように、chmodコマンド実行前、スクリプトには読み取り(r)と書き込み(w)の権限しかありません。実行すると、chmod +xその後、実行(x)権限を取得します。(権限の詳細については、次の記事で説明します。)さて、今は次のように実行するだけです:

[root@localhost ~]# ./note.sh
Phone number ?

スクリプト名の前に、./ という組み合わせを付けました。.(ドット)はUNIXの世界では現在の場所(現在のフォルダー)を意味し、/(スラッシュ)はフォルダーの区切り文字です。(Windowsシステムでは、同じ機能を表すのにバックスラッシュ / を使います。)つまり、この組み合わせ全体の意味は「現在のフォルダーからnote.shスクリプトを実行する」ということです。完全なパスでこのスクリプトを実行すれば、もっと明確になると思います:

[root@localhost ~]# /root/note.sh
Phone number ?
[root@localhost ~]#

それも動作します。

すべてのLinuxユーザーが同じデフォルトシェルを持っていれば、それで万事OKです。単にスクリプトを実行するだけなら、デフォルトのユーザーシェルがスクリプトの内容を解析してコマンドを実行するために使われます。シェルによって構文や組み込みコマンドなどが少しずつ異なるため、私たちのスクリプトが確実に使うようにするには、bash、私たちは追加すべきです#!/bin/bashファイルの先頭行に追加します。これにより、デフォルトのユーザーシェルが/bin/bashを呼び出し、そのとき初めてスクリプト内のコマンドが実行されます:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"

今になってようやく、私たちは100%確信しているbashがスクリプトの内容を解析するために使用されます。続けましょう。

读取输入

情報を表示した後、スクリプトはユーザーの回答を待ちます。次のreadコマンドはユーザーの回答を受け取るために使われる:

#!/bin/bash
echo "Phone number ?"
read phone

実行後、スクリプトはユーザーが[ENTER]キーを押して入力を終了するまで、ユーザーの入力を待ちます:

[root@localhost ~]# ./note.sh
Phone number ?
12345                               <--- 这儿是我输入的内容
[root@localhost ~]#

入力したすべての内容は変数phoneに格納されます。変数の値を表示するには、同様にecho命令:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"
read phone
echo "You have entered $phone as a phone number"
[root@localhost ~]# ./note.sh
Phone number ?
123456
You have entered 123456 as a phone number
[root@localhost ~]#

にbashシェルでは、通常私たちは使用します$(ドル)記号を使用して、これが変数であることを示します。ただし、変数への読み込みやその他ごく少数の場面を除いて、$は使用しません(後で説明します)。

さて、残りの質問を追加する準備ができました:

#!/bin/bash
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
script is not working.
[root@localhost ~]#

ストリームリダイレクトを使用する

完璧です!残っているのは、すべてをdata.txtファイルにリダイレクトすることだけです。フィールド区切り文字として、/(スラッシュ)記号を使用します。

注意:最適だと思う任意の区切り文字を選択できますが、ファイルの内容にこれらの記号が含まれないようにしてください。含まれると、テキスト行に余分なフィールドが生成される原因になります。

">"の代わりに">>"を使用することを忘れないでください。出力内容をファイルの末尾に追記したいからです!

[root@localhost ~]# tail -2 note.sh
read issue
echo "$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
987
Name?
Jimmy
Issue?
Keybord issue.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
[root@localhost ~]#

注意 : tailコマンドはファイルの最後の部分を表示するn行。

完了。もう一度実行してみましょう:

[root@localhost ~]# ./note.sh
Phone number?
556
Name?
Janine
Issue?
Mouse was broken.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
556/Janine/Mouse was broken.
[root@localhost ~]#

ファイルは増え続けています。各行の先頭に日付を追加しましょう。これは後でこれらの統計データを扱うときに役立ちます。これを実現するには、dateコマンドを使用し、何らかの形式を指定します。デフォルトの形式が好きではないからです:

[root@localhost ~]# date
Thu Apr 23 21:33:14 EEST 2015                     <---- date命令的默认输出
[root@localhost ~]# date "+%Y.%m.%d %H:%M:%S"
2015.04.23 21:33:18                               <---- 格式化后的输出

コマンドの出力を変数に読み込む方法はいくつかありますが、この単純なケースでは、`(これはバッククォートです。シングルクォートではなく、チルダ~と同じキーにあります)を使用します:

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
echo "$now/$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
Script hanging.
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
[root@localhost ~]#

うーん……私たちのスクリプトは少し見苦しいですね。美化しましょう。手動で読み取りたい場合はreadコマンドを使用すると、readコマンドでもいくつかの情報を表示できることがわかります。この機能を実現するには、-pキーと情報を使用する必要があります:

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
read -p "Phone number: " phone
read -p "Name: " name
read -p "Issue: " issue
echo "$now/$phone/$name/$issue">>data.txt

コンソールから直接、各コマンドに関する多くの興味深い情報を見つけることができます。次のように入力するだけです:man read, man echo, man date, man ……

同意しますか? ずっと見やすくなったでしょう!

[root@localhost ~]# ./note.sh
Phone number: 321
Name: Susane
Issue: Mouse was stolen
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
[root@localhost ~]#

カーソルがメッセージの後ろにある(新しい行ではなく)というのは、ちょっと面白いですね。(LCTT注釈:echoコマンドで出力表示する場合は、-nパラメータを使用して改行を防ぐことができます。)

循环

そろそろスクリプトを改良する時です。ユーザーが一日中電話を受けるとしたら、そのたびに実行するのは面倒ではないでしょうか? この一連の動作を無限ループにしましょう:

[root@localhost ~]# cat note.sh
#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
done

私はすでに交換しましたread phoneとnow=date行の位置。これは、電話番号を入力した後に時刻を取得したいからです。これをループの先頭に置くと、ループの1回の実行後、変数 now はデータがファイルに保存された直後に時刻を取得することになります。これは良くありません。次の呼び出しが20分後、あるいはそれ以降かもしれないからです。

[root@localhost ~]# ./note.sh
Phone number: 123
Name: Jim
Issue: Script still not works.
Phone number: 777
Name: Daniel
Issue: I broke my monitor
Phone number: ^C
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
[root@localhost ~]#

注意:無限ループから抜け出すには、[Ctrl]+[C]キーを押すことができます。シェルは^を表示してCTRLキーを表します。。

パイプリダイレクトを使用する

私たちの「フランケンシュタイン」にさらに機能を追加しましょう。スクリプトが各コールの後に統計データを表示するようにしたいのです。例えば、各番号が私に何回コールしてきたかを確認したいとします。そのためには、ファイルdata.txtをcatする必要があります:

[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
2015.04.23 22:02:14/123/Jimmy/New script also not working!!!
[root@localhost ~]#

さて、すべての出力をリダイレクトできます。cut命令,让cut各行をデリミタ「/」で区切って、2番目のフィールドを出力します:

[root@localhost ~]# cat data.txt | cut -d"/" -f2
123
321
123
777
123
[root@localhost ~]#

次に、この出力を別のコマンドにリダイレクトできます。sort:

[root@localhost ~]# cat data.txt | cut -d"/" -f2|sort
123
123
123
321
777
[root@localhost ~]#

そして一意な行だけを残します。一意なエントリを数えるには、単に追加するだけです。-c键到uniq命令:

[root@localhost ~]# cat data.txt | cut -d"/" -f2 | sort | uniq -c
    3 123
    1 321
    1 777
[root@localhost ~]#

これをループの最後に追加するだけです:

#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
        echo "===== We got calls from ====="
        cat data.txt | cut -d"/" -f2 | sort | uniq -c
        echo "--------------------------------"
done

运行:

[root@localhost ~]# ./note.sh
Phone number: 454
Name: Malini
Issue: Windows license expired.
===== We got calls from =====
    3 123
    1 321
    1 454
    1 777
--------------------------------
Phone number: ^C

Shell脚本编程初体验

現在のシナリオは、いくつかのよく知られた手順を通っています:

  • 显示消息
  • ユーザー入力の取得
  • 値をファイルに保存する
  • 保存されたデータの処理

しかし、ユーザーに少し責任があるなら、データを入力する必要があるときもあれば、統計を取るときもあるでしょう。あるいは、保存されたデータから何かを検索したいかもしれません。そのような場合には、switches/casesを使用し、出力をうまくフォーマットする方法を知る必要があります。これはシェルで表を「描く」ときに役立ちます。

来源:http://linux.cn/article-5591-1.html