Transwiki:Bash Shell Scripting
現在、この本は Bashの初等レベルの知識のみを提供しています。より深く学ぶには、External Programs, External links や Using man, info and help に移動してください。
イントロダクション
[編集]Bashとは?
[編集]BashはUnix shell: OSと相互作用するためのコマンドラインインターフェース。広く使用されており、多くのGNU/LinuxディストリビューションやmacOSでのデフォルトシェルに採用されており、また多くのシステムに移植されています。Bashは1980年代に 自由ソフトウェア財団 で働くプログラマーのBrian Fox氏 によって作成されました。またこれは Bourn シェルの代替となる自由ソフトウェアを意図しています。(事実、その名前はBourne Again SHellからとられています。) また Bash には整数の計算やジョブの制御などの機能が導入されています。[1].
シェルスクリプティングとは?
[編集]ユーザーがコマンドを入力したときに、コマンドをただちに実行してそのフィードバックを返すインタラクティブモードに加えて、その他とシェルと同様に、Bashはコマンドのスクリプトを実行する機能があります。これは、Bash シェルスクリプト (または Bash スクリプト、 シェルスクリプト、スクリプトと呼ぶこともあります)。スクリプトには単純なコマンドのリストであることがありますし、コマンドひとつのこともありますし、シェル関数、ループ、条件節その他手続き型プログラミングに含まれるもの全てを含むこともできます。実際、Bashシェルスクリプトは、Bashプログラミング言語で書かれたコンピュータープログラムです。
シェルスクリプティングはそのようなスクリプトの作成やメンテナンスの技術です。
シェルスクリプトはインタラクティブなコマンドラインから呼びだされることもあれば、システムの一部から呼びだされることがあります。あるスクリプトはシステムの起動時に呼びだされ、またあるスクリプトは月曜から金曜の午前2:30に実行され、またあるスクリプトは、ユーザーがシステムにログインしたときに実行されます。
シェルスクリプトは通常多くのシステム管理者のタスクに使用されています。例えば、ディスクのバックアップの実行、システムログの評価などです。またシェルスクリプトは複雑なプログラムのインストールにも使用します。もし、スクリプトが外部の(Bashではない)プログラクを実行する必要があれば、そのスクリプトは2行のスクリプトになります。チューリング完全な手続き型プログラミング言語の全ての能力や機能が必要であれば、スクリプトはすべて行うことができます。
この本の読み方
[編集](1) 読みながらBASHの練習をする(理想的)
あなたがこれを読んでいる間ににBashのコマンドラインを開いているのは、この本を読むのにとてもよいと思えます。あなたはいくつかの例を試せます。しかしより重要なことはこの本の例をコピーして試すことではありません。あなたがある方法でなにかが動作すると考えた場合、あなたはその例外をテストすることです。明確に指定されている場合を除いて、この本の例は、特別な権限を要求しませんし、Bash自身とBashのあるシステムに通常備わっている通常のユーティリティ以外を求めません。(もしあなたが Windows 10 を使用しているなら、Windows Subsystem for Linuxをインストールすることをお勧めします。もしより古いマシンを使用しており、GNU/LinuxやUNIXシステムへのSSHアクセスを持っていない場合、Cygwinのようなシステムが簡単に準備できるかもしれません。Cygwin は Bash 以外のユーティリティをも含まれています。Bash port for Windows にはただBashだけが含まれています。もし Cygwin を持っていたなら、そのユーティリティを含むディレクトリ— 例えばC:\Cygwin\bin — がWindows pathに含まれていることを確認してください。)
(2) よくわかっていないコマンドのドキュメントやマニャアルをじっくり読む
あなたに経験がある場合、さまざまなコマンドのドキュメントやマニュアルの読解がヒントになるでしょう。Bash組込みコマンドの場合、あなたはビルトインの help コマンドが有用でしょう。例えば、help echo
はビルトインのechoコマンドの情報をprint (表示)します。外部プログラムの場合、あなたのシステムにマニュアルページがインストールされているでしょうから、例えば、man (manual) コマンドでマニュアルが読めるでしょう。例えば cp (copy) コマンドのマニュアルを読む場合、man cp
と入力してください。加えて、多くのプログラムでは、実行時の引数に --help を指定すると、ヘルプ情報を表示します。例えば、cp --help
はman cp
のような情報を表示します。(また多くのプログラムは --help アプローチはman アプローチほど多くの情報を提供しません。)
(3) 盲目的なコマンド入力の危険性
警告: この本はWiki の一部であり、誰もがこの内容を編集できます。全くないとは言えませんが、可能性として、例として悪意のあるコードが加えられることや既存の例に悪意のあるコードが挿入される可能性がありえます。あなたの直感を信頼してください。例が疑わしい場合、例が何を行うのか説明するテキストがない場合、 周囲のテキストがその実行内容にふさわしくない場合、実行しないでください。たとえ、その例が悪意がないことが明確だとしても、あなたのシステムでは特別でない権限やパーミッションで実行してください。そうすれば、重要ななにかの削除などといった事故が予防できます。Bashは非常にパワフルなシステム管理者のツールであり、これは同時にあなたのシステムを事故で破損する可能性をもっていることを意味しています。
もしあなたがBashの完全な初心者、またはBashのコマンドラインを実行したことはあるがプログラミングの初心者である場合、この本を最初から順に経験していくことが最も効果的だと考えます。トピックやセクションを完全に理解していると考えた場合、あなたは実際には内容を把握していないにもかかわらず読み飛ばすのかもしれません、しかし結局あなたは今あなたが知らないことを知りたくないのかもしれません。
もしあなたがBashの経験やプログラミングの経験がすでにある場合、この本の前半は流し読みしてもよいでしょう。そこにはあなたがあまり詳しくないBashのシンタックスの例があります。後半には、あなたがより詳しく学ぶべき内容があります。
もしあなたがBash シェルスクリプティングを深く経験しているなら、この本はあなたに適切ではないかもしれません。あなたには、自由ソフトウェア財団のウェブサイトにあるBash Reference Manualの方が適切でしょう。もしくは man bash をすべて熟読しましょう。(しかしこれは wiki上にあるので、あなたの経験の糧を他の方と共有することもできます。) この本は包括的なリファレンスマニュアルではありません。これは広く使われている技術、価値の高い技術を注視しており、一般的な環境を仮定しています。コマンドに影響するような例外的な機能は含めていません。
用語の注意
[編集]Bashコマンドの言語はチューリング完全なプログラミング言語です。つまり、与えられた計算が理論的に実行できるなら、理論上Bashでそれが実行できることになります。さらに現実的な観点から言うと、Bashにとってとても洗練された方法で行うことができ、人はそのように利用します。それにも関わらず、Bashはプログラミング言語というよりシェルスクリプト言語と呼ばれ、プログラムは、BashプログラミングよりBashスクリプト言語で書かれたと言われます。この本では理解しやすくするため、スクリプトは Bashスクリプトに用い、プログラムはBashスクリプトから起動されるような外部プログラムに用います。
また私達は、ユーティリティという語を用います。この用語は主観的なもので、さまざまに使用されていますしかし典型的には、人との相互作用が必要がなく他のプログラムと協調して動作する、外部のプログラムに用います。全てのユーティリティはプログラムですが相互に入れ替え可能な点もあります。ユーティリティの対義語はアプリケーションです。アプリケーションも主観的な用語でさまざまに使用されていますが、比較的に複雑なGUIを持ち、その他のプログラムと協調して動作することが少ないものです。アプリケーションはその性質からBashスクリプトから呼び出すことはあまりありません。そのためアプリケーションの語はあまり使用しません。多くのプログラムはユーティリティか'アプリケーションであり、いくつかのプログラムはユーティリティとアプリケーションの要素を持っています。
Bashスクリプトにはさまざまな要素がありますが、いくつかはコマンドと呼び、他方はステートメントと呼びます。ここで両者の区別を説明しますが過度に注意する必要はありません。コマンドは呼び出される外部プログラム、echoなどのBashのビルトイン、シェルの関数(後述)などを意味しています。 ステートメントは通常if … then … else … fiのような、Bashプログラミングの構文や変数への代入(後述)に使用します。混乱するかもしれませんが、コマンドはBashのビルトインを指すことに使用しますが、ビルトインの使用は、結果としてコマンドではなくステートメントになります。例えば ifステートメントは、ifコマンドを使うステートメントです。この区別は細かいですが心配しないでください。文脈によってそれは用語がなにかを明確にするために十分なのです。
あなたのシェルの注意点
[編集]シェルのバージョン
[編集]このテキストが提供する情報は、広く使用されているバージョンのBashの情報です。2014年の7月よりこれは Bash バージョンが 4になりました。それより古いバージョンのbashでの実行はいくつか失敗するかもしれません。
macOSが標準として供えている bashは バージョン3.2ですので注意してづあさい。いくつかのユーザーにとって注意の必要な変更としては、-e オプションのサポートがあります。これはバックスラッシュの相互運用を有効にするものでカラー表示に重要です。これには、(printfを使用するなどの)回避方法がありますが快適ではありません。
Bashのソースを http://www.gnu.org/software/bash/ から取得してコンパイルするなどの方法も検討してください。
- OS X 10.8以降の Bash 4.18 がダウンロードできます。これは Arthur200000によってビルドされています。 Baidu Download Link. ダウンロードの前にKNOWN_ISSUESを御読みください。'下载', がダウンロードを意味しています。
bashrc
[編集]この~/.bashrc ファイルはインタラクティブシェルの動作を決定します。適切に調整してBashライフをよりよくしましょう。
多くの GNU/Linux ディストリビューションは ~/.bashrc ファイルがあり、あなたのお気にいりのテキストエディタで中身を確認できます。
多くの場合では、システム全体の bashrc ファイルがあり、通常は /etc/bashrcにあります。変更するにはrootでなければなりません。 もしあなたがよくわかっていない場合、こちらのbashrcの例を確認してみましょう:
最初のBashスクリプトの例
[編集]単純な "hello world" スクリプトから始めましょう。
echo 'Hello, world!'
これをBash プロンプトに直接入力することも、これをファイルに保存して(例えばhello_world.shファイル) Bashプロンプトでbash hello_world.sh入力して実行することもできます。(後者はより洗練された方法で行うこともできます。) どちらの場合も Hello, world!と表示します。:
$ echo 'Hello, world!'
Hello, world!
ここで、$ は Bashプロンプトのシンボルを示しています。$の後の、残りの部分があなたの入力するコマンドを示しています。その次の行はそのコマンドの出力を示しています。
より複雑なスクリプトの例です。:
if [[ -e readme.txt ]] ; then
echo 'The file "readme.txt" exists.'
else
echo 'The file "readme.txt" does not exist.'
fi
このスクリプトは現在ディレクトリにreadme.txtという名前のファイルが存在するかしないかをテストし、そのテストいよってif ステートメントでどのコマンドを実行するかを制御しています。これは直接プロンプトに入力するのは難しいかもしれません。しかし便利ではないかもしれませんがどのようなスクリプトでも可能です。
両方のスクリプトは全て"Bash内部"ですので、実行すべき外部のプログラムを必要としません。(コマンドのecho, if … then … else … fi さらに [[ -e … ]] はすべてビルトインコマンドであり、Bashに自身によって実装されています。) しかしシェルスクリプトで、多くのBashでは外部プログラムを実行します。次のスクリプトを見てください。:
if [[ -e config.txt ]] ; then
echo 'The file "config.txt" already exists. Comparing with default . . .'
diff -u config-default.txt config.txt > config-diff.txt
echo 'A diff has been written to "config-diff.txt".'
else
echo 'The file "config.txt" does not exist. Copying default . . .'
cp config-default.txt config.txt
echo '. . . done.'
fi
ここで diff と cp はよく使用するユーティリティプログラムで、Bashの一部ではありませんが Bashを持っている多くのシステムには通常含まれています。このスクリプトは config-default.txtという名前のデフォルトの設定ファイルが存在していることを仮定しており、config.txtという名前の設定ファイルの存在をチェックします。もしconfig.txtが存在していれば、このスクリプトは、外部プログラムのdiffを使って diffを生成します。(この場合二つのファイルの違いを報告しています。)ユーザーはデフォルトではない設定を確認できるでしょう。もしconfig.txtが存在していない場合、このスクリプトは外部プログラムの cp (copy)を使って、このデフォルトの設定ファイルをconfig.txtにコピーします。
あなたが見たように、外部プログラムもビルトインコマンドと同じ構文で実行できます。両方ともコマンドです。
このスクリプトの上のバージョンは冗長です。これは出力が多いです。一般的なスクリプトはecho コマンドを含めていません、というのもユーザーはこのレベルの情報が必要ないからです。この場合、 #表記を使って、Bashに無視されるコメントにします。そのためこれはユーザーからは見えません。このようなコメントはこのスクリプトを読む人のためのものです。
if [[ -e config.txt ]] ; then
# if config.txt exists:
diff -u config-default.txt config.txt > config-diff.txt # see what's changed
else
# if config.txt does not exist:
cp config-default.txt config.txt # take the default
fi
上の例はデモンストレーション目的です。このような単純なスクリプトにコメントは不要です。
シンプルなコマンド
[編集]シンプルなコマンド は半角スペースやタブによって区切られた単語 の列からできています。最初の単語はコマンド名として扱います。残りの単語は引数としてコマンドに渡されます。私達はこれまでシンプルなコマンドを見てきました。ここでもいくつか見ましょう。:
cd ..
- これは cd コマンドを使用します。("change directory"; ファイルシステムを移動するビルトインコマンド) でひとつ "上の" ディレクトリに移動します。
- .. は "親ディレクトリ" を意味しています。例えば /foo/bar/../baz.txt は /foo/baz.txt と同じです。
rm foo.txt bar.txt baz.txt
- rm プログラムがインストール済みと仮定しています。このコマンドは ("remove") で現在ディレクトリのfoo.txtとbar.txtと baz.txt を削除します。
- Bash は 名前がrmで実行可能ファイルである、rm プログラムを設定済にディレクトリのリストから探します。(つまりそのファイルのパーミッションに依存します)
/foo/bar/baz bip.txt
- このコマンドは /foo/bar/bazにある実行ファイルにたった一つの引数 bip.txt を渡して実行します。
- /foo/bar/baz は実行可能でなければなりません。(つまりそのファイルのパーミッションに依存します)
- 警告: スラッシュ/の後ろとファイル名の間に半角スペースがないことを確認してください。 いいかえると "root" ディレクトリの "foo" ディレクトリとなっていることを確認して、それからコマンドを実行してください。: あなたが sudoアクセスしていた場合 "rm -r / foo" によってシステム全体が破壊されます。注意してください。sudoについては後述しますのでわからない場合は心配しないでください。
- もし、/foo/bar/baz がバイナリファイルではなくテキストファイルで、そのファイルの先頭が#!で始まっている場合、その行の残りの部分をはインタプリタ してそれを使用してこのファイルを実行します。例えば、/foo/bar/bazファイルの最初の行が #!/bin/bashだったとします。その場合、上記のコマンドは /bin/bash /foo/bar/baz bip.txtと同じです。
/foo/bar/bazの例は特別なことを示しました。通常のプログラムと同様に実行するBashスクリプトをどのように作成するかを示したからです。そのスクリプトの先頭行に #!/bin/bash を加えるだけです。(ここではBashがあなたのシステムに配置されている場所を仮定しています。場所が正しくない場合、修正してください。)それからそのスクリプトファイルのパーミッションが読み込みと実行を許可していることを確認します。この本の残りの、完全なシェルスクリプトの例は全て、#!/bin/bashの行で始まっています。
クォート
[編集]私たしは rm foo.txt bar.txt baz.txt
コマンドが3つのファイルを削除することを確認しました。foo.txtとbar.txtとbaz.txtです。これはBashがホワイトスペース(半角スペースやタブ)によって4単語に分割し、そのうち3単語をrmプログラムに引数として渡すからです。しかし、半角スペースを含むファイル名のファイルを削除する必要はどうすればいいでしょうか?
Caution: Unix や GNU/Linux ディストリビューションでは、ファイル名に半角スペース、タブ、改行文字、制御文字を含めることができます。 |
Bash はクォート機構を持っており、このような場合に有用です。通常はシングルクォート ' またはダブルクォート "を使います。以下のコマンドは、this file.txtのようなファイルの削除にしようできます。:
rm 'this file.txt'
rm "this file.txt"
クォートマークの間では半角スペースで単語分割されません。通常、上でみたようにクォートマークで単語を囲みます。半角スペース自身は囲まれる必要があります。; this' 'file.txtまたはthis" "file.txt は 'this file.txt'と同じです。
もうひとつの通常使用されるクォートの機構にバックスラッシュ \ があります。しかし、すこし違う動作です。このクォートは (または "エスケープ") は一文字だけです。次のコマンドは、そのため、上のコマンドと同じです。
rm this\ file.txt
これらの場合で、これらのクォート文字自身はプログラムに渡されません。(これをクォート除去といいます。) 結果として rm はどのように呼び出されたのかを知る方法はありません。例えばrm foo.txt
として呼び出されたのかrm 'foo.txt'
として呼び出されたのかはわかりません。
ファイル名展開とチルダ展開
[編集]Bash はいくつかの特別な表記をサポートしています、これは展開と呼ばれ、プログラムに引数を渡すためによく使用されます。
ファイル名展開 は、*.txtのような パターン でこれはそのパターンにマッチするファイル名をもつ全てのファイルに置き換えられます。例えば、もし現在ディレクトリにfoo.txt, bar.txt, this file.txt それに something.else のファイルがある場合、このコマンド
echo *.txt
次のコマンドと同じです。
echo 'bar.txt' 'foo.txt' 'this file.txt'
ここでアステリスク * は "0文字以上の任意の文字" です。その他にも特別なパターン文字があります。(疑問符? は"任意の一文字"を表します。) その他にもパターンマッチルールがあります。しかしその中でも*は非常によく使われています。
ファイル名展開は現在ディレクトリに限定される必要はありません。例えば、/usr/binデイレクトリ中のファイルでt*.shにマッチする全てのファイルを表示したい場合、次のように書けます。:
echo /usr/bin/t*.sh
これは次のように展開されるでしょう。
echo /usr/bin/test.sh /usr/bin/time.sh
もし指定したパターンにマッチするファイルがない場合、展開は行われません。例えばつぎのコマンド
echo asfasefasef*avzxv
はおそらく次のように出力するでしょう。asfasefasef*avzxv.
それでは実際のファイル名にアステリスクが含まれている場合、(例えば *.txt) これをどのように参照すればいいでしょう? (はい、ファイル名にアステリスクを含めることができます。) 上でみたようにクォートスタイルのどちらでも対応できます。:
cat '*.txt'
cat "*.txt"
この両者はどちらも*.txtを表示します。名前の末尾が.txtが含まれている全てのファイルではありません。
もうひとつ似ているが異なる展開に チルダ展開 があります。チルダ展開は多くの機能がありますがもっとも重要なのは、単体のチルダ ~ またはチルダとスラッシュで始まる単語です ~/ (tilde-slash)、このチルダは現在のユーザーのホームディレクトリに展開されます。例えば:
echo ~/*.txt
このコマンドは現在のユーザーのホームディレクトリにある*.txtにマッチする全てのファイルを表示します。
ブレース展開
[編集]ファイル名展開に似たものに、ブレース展開 があります。これは、複数の似た引数をコンパクトに表す方法です。次の4つのコマンドは同じです。:
ls file1.txt file2.txt file3.txt file4.txt file5.txt
ls file{1,2,3,4,5}.txt
ls file{1..5..1}.txt
ls file{1..5}.txt
最初のコマンドは明示的にそれぞれを示しています。その他の3コマンドはブレース展開を使用して、引数を短く表現しています。2番目のコマンドでは可能性のある選択肢が 1 から 5 まで明示的にコンマ区切りで示されています。3番目のコマンドは数字の列として示しています。("1 から 5 まで増分 1で); 4番目のコマンドは、三番目のコマンドですが、..1 は暗黙的に指定しています。
同様に降順にファイル名を指定します。:
ls file5.txt file4.txt file3.txt file2.txt file1.txt
ls file{5,4,3,2,1}.txt
ls file{5..1..-1}.txt
ls file{5..1}.txt
ここでのデフォルトの増分が-1になっているのは、数字列の開始点が終了点より大きいためです。
Bashでは最初の単語は実行するコマンドですので、コマンドをこのように書くことができます。:
{ls,file{1..5}.txt}
しかし、明かに可読性が低くなっています。 (これはファイル名展開の場合も同様にできますが推奨しません。)
ブレース展開は、ファイル名展開と同様にクォート機構によって無効にできます。; '{', "{" や \{ は実際の文字通りのブレースになります。
出力のリダイレクト
[編集]Bash はコマンドの標準出力(ファイルディスクリプタ1)をコンソースからファイルに向けられる。例えば、通常のユーティリティプログラムcat はそのファイルの内容を標準出力に書き出します。もしその標準出力をファイルにリダイレクトした場合ファイルの内容を他のファイルにコピーすることになります。
もしコマンドの出力で目的のファイルを上書きする場合、次のように表記します。
cat input.txt > output.txt
もしそのファイルの内容はそのままにして、コマンドの出力を追記する場合、次のように表記します。
cat input.txt >> output.txt
しかし、プログラムがコンソールに書き出すのは標準出力だけではありません。多くのプログラムは、標準エラー出力(ファイルディスクリプタ2)を使って、 エラーメッセージ、ログ,サイドチャンネルのメッセージを書き出します。もし、標準出力に標準エラー出力を混ぜたい場合、次のように表記します。
cat input.txt &>> output.txt
cat input.txt >> output.txt 2>&1
もし、標準エラー出力を標準出力とは別のファイルに追加したい場合、次のように表記します。
cat input.txt >> output.txt 2>> error.txt
標準エラー出力だけをリダイレクトし、標準出力をそのままにします。
cat input.txt 2>> error.txt
これらの上の例で、リダイレクトの対象へ追記ではなく上書きにしたい場合、>> を >に置き換えます。
出力をリダイレクトする、さらに複雑な応用例をあとで示します。
入力のリダイレクト
[編集]Bashがプログラムの出力をファイルへ向けなおしたように、bashはプログラムの入力をファイルから読み込められます。例えば、通常のUNIXユーティリティcat はその入力を出力にコピーします。
cat < input.txt
例えば上のコマンドは、input.txtの内容をコンソールに出力します。
既に見たように、このトリックはこの場合には必要ありません。というのもcat は指定したファイルから出力にコピーできるからです。上のコマンドは単純に次のコマンドと同じです。
cat input.txt
これは例外というよりはルールです。コンソースから入力をとる、通常のUnixユーティリティは、代りにファイルから入力をとるビルトイン機能があります。実際catを含む多くのプログラムは複数のファイルを入力にとれます。これは上のものより柔軟になります。次のコマンドはinput1.txt を出力し、つづいて input2.txtを出力します。
cat input1.txt input2.txt
入力のリダイレクトの例はまた後述します。
パイプラインのイントロダクション
[編集]この章の範囲外ではありますが、ここでパイプを簡単に紹介します。パイプ はコマンドの並びを パイプ文字| で区切ったものです。それぞれのコマンドは同時に実行され、それぞれのコマンドの出力が次のコマンドの入力に使用します。
このパイプを考えてみましょう。
cat input.txt | grep foo | grep -v bar
cat ユーティリティはすでに見たはずです。cat input.txt コマンドは単純にinput.txtファイルの内容をその標準出力に書き出します。このgrep プログラムは通常のUnixユーティリティでパターンに応じてフィルタリングします。("greps" は Unix 用語です) 例えば、grep foo コマンドは入力のうちfooを含む行だけをその標準出力に送ります。grep -v barコマンドは -v オプションを使ってパターンを逆にしています。このコマンドは入力のうちbarを含まない行を標準出力に送ります。それぞれのコマンドの標準入力が前のコマンドの標準出力であるため、このパイプは、input.txtの行のうちfooを含み、かつbarを含まない行を表示します。
変数
[編集]Bashスクリプトでは、値を保持できるパラメータが数種類あります。もっともよくあるのが変数という名前つきのパラメータです。もしあなたが他の手続き型のプログラミング言語(CやBASICやFortranまたはPascal)を知っていれば、変数に馴染みがあるでしょう。次のシンプルなスクリプトはlocation 変数をつかって、worldを保持して、Hello, world!メッセージを表示します。
location=world # store "world" in the variable "location"
echo "Hello, ${location}!" # print "Hello, world!"
あなたが見たように、2行目のコマンドが実行される前にこの${location}はworld置き換えられています。この置換を変数展開といいます。そしてこれはあなたが思い以上に自由度が高いのです。例えば、実行するコマンドでさえ保持できます。
cmd_to_run=echo # store "echo" in the variable "cmd_to_run"
"${cmd_to_run}" 'Hello, world!' # print "Hello, world!"
上の両方の例で、私達は変数展開を実施するために${variable_name}という表記を使用しました。上の例でのより短い表記方法は$variable_nameの中括弧なしの表記ですが、これは同じ効果を生みますが、いくつかの場合では中括弧が必要です。(例えば、${foo}barは$foobarと書けません。というのも後者は${foobar}と解釈されてしまうからです)。しかし通常は省略されますし、現実のスクリプトでも通常は省略されます。
もちろんいうまでもないことですが上の例は現実的な例とはいえません。 これはどのように変数を使うかを示しただけで、それをなぜ、いつ使うのかは示していません。もしあなたが他の命令型言語をよく知っているなら、変数をなぜ、いつ使うのかは明白でしょう。もしそうでないなら、この本を読み、より現実的な例を見て理解しましょう。
また変数展開を行う文字列にシングルクォート 'ではなく、ダブルクォート"を使用したことを忘れないでください。シングルクォートは変数展開を抑制します。echo '${location}'のようなコマンドは、実際の文字列${location}を出力するからです。その名前がlocationの変数の値ではありません。
一般的に変数展開の文字列をダブルクォーテーションでくくるのはよいアイディアです。というのも変数展開の結果はファイル名展開や単語分割されるからです。(つまり半角スペースが単語の区切りとみなされるのです)。例えば、このスクリプトは、
foo='a b*' # store "a b*" in the variable "foo"
echo $foo
ba.txt bd.shのように奇妙に表示されてしまいます。これは私達が求めていたものと異なります。現実世界のスクリプトではダブルクォーテションが明確に必要な場合だけ使用するスタイルが多いです。しかしこの例ではこのような混乱するようなバグが発生してしまいました。
Tip: 変数展開をダブルクォーテションでくくるのは一般的によいアイディアです。つまり"$var" の方が $varよりよいといえます。 |
いくつかの変数には特別な意味があります。例えば、PATH変数には、外部プログラムを実行するときにそれを探す対象のディレクトリのリストが含まれています。もし、/usr/bin:/binが設定されていた場合、cp src.txt dst.txt
のようなコマンドの場合、/usr/bin/cp または /bin/cpのどちらかを探すことになります。また、HOME変数は、現在のユーザーのホームディレクトリで事前に初期化されています。そしてこれはチルダ展開に影響します。例えば、あるスクリプトがHOME=/fooに設定した場合、echo ~/bar
は/foo/barと表示されます。(しかしこれはユーザーのホームディレクトリを実際に変更するわけではありません)
位置パラメータ
[編集]上のコマンドのほとんどは、ビルトインコマンドも外部のプログラムでも、一つ以上の引数を提供していました。これはコマンドを実行する上で必要なものを示しています。例えば、Unix ユーティリティのmkdir ("make directory")プログラムを起動して新しいディレクトリを作成するとき次のようなコマンドを実行します。
mkdir tmp
ここで tmpは新しく作成するディレクトリの名前です。
ここで見たように、Bashスクリプトは自身が実行できるプログラムです。そのため、もちろん引数を取ることができます。これらの引数はプログラムから位置パラメータとして利用できます。この本の最初の頃に、パラメータの一種として変数を見ました。位置パラメータはそれとよく似ています。しかし位置パラメータは名前というよりはむしろ数字で特定します。例えば$1 (または ${1})はスクリプトの最初の引数に展開されます。そのため、ファイル名と一行のテキストの2つの引数をとり、指定したテキストが含まれる指定した名前のファイルを作成する、単純なスクリプト mkfile.sh を作成するとします。それは次のように書けるでしょう。
#!/bin/bash
echo "$2" > "$1"
(このファイルの最初の行の#!/bin/bash
に注目しましょう。この行は Basic commands にもありました。このコードによって、実行したとき、あなたのコンピューターが非標準的な設定であったり他のプログラムを実行していたとしても、Bash シェルで解釈されることを保証できます。)
そして、(chmod +x mkfile.shを実行して、このファイルを実行可能にした後で)これを次のように実行します。
./mkfile.sh file-to-create.txt 'line to put in file'
また私達は、$@を使って一度に全ての引数を参照できます。これは 全て の位置パラメータに順番に展開されます。"$@"のようにダブルクォーテーションでつつんだとき、それぞれの引数は単語に分割されます。(注意: 代替の$*はより標準的かもしれません。しかし"$*"はひとつの単語に展開されます。もともとの引数を半角スペースで結合した単語になります。そのため、$@ や $*より"$@"は好ましいとされています。$@ や $*はその値に半角スペースがある場合単語が複数に分割されるかもしれません。また"$*"はすべての引数をひとつの単語にしてしまいます)これはよくビルトインコマンドのshiftと同時に使用されます。shiftは最初の位置パラメータを削除し以降のパラメータをひとつ前にシフトします。つまり$2 が $1になり、$3 が $2になるのです。例えば、mkfile.shを次のように修正した場合、
#!/bin/bash
file="$1" # save the first argument as "$file"
shift # drop the first argument from "$@"
echo "$@" > "$file" # write the remaining arguments to "$file"
これをこのように実行できます。:
./mkfile.sh file-to-create.txt line to put in file
ファイル名以外の引数がすべて、ファイルに書き出されます。
位置パラメータの数は$#で利用できます。もし$# が 3なら、全位置パラメータは$1と$2と$3です。
$9より後ろの、例えば10番目の位置パラメータには、中括弧が必要です。$10ではなく、${10} と書かなければなりません。($10は ${1}0と解釈されるかもしれません。) ユーザーがそれを追いかけるのが大変なため通常は多くのパラメータを使うのはよくないアイディアです。10以上の引数が必要なスクリプトは、再度設計をやりなおしましょう。
もしあなたにBashやUnixのユーティリティを使用した経験がありましたら、多くのコマンドがさまざまなオプションをとることに気付いているでしょう。これは-から始まるものですが、通常の引数です。例えば、rm "$filename"
は個々のファイルを削除しますが、rm -r "$dirname"
はディレクトリとその中に含むものを含めて削除します。(-rはrecursiveつまり再帰を短くしたもので、このコマンドはディレクトリツリーを 再帰的に 削除します。) これらのオプションは実際にただの引数です。rm "$filename"
には引数がひとつ("$filename")ですが、一方で、rm -r "$dirname"
には引数が二つ(-r と "$dirname")あります。ここにはただの引数だけがあり何も特別なものはありません。しかしオプションのための表記は、標準として幅広く使用されています。Bashのビルトインコマンドはさまざまなオプションを受けつけます。また、あとでBashスクリプトがオプションをサポートするさまざなまテクニックを学びましょう。
終了値・返り値
[編集]プロセスが完了したとき、これは小さい負でない整数値をOSに返します。この値は終了値 または 返り値 と呼ばれます。利便性のため、処理の実行が成功として完了したとき、0を返します。一方処理でエラーが発生したとき正の整数を返します。(この方法は複数の個別のエラーを表現でき、またその値によってエラーを区別できます。) Bashスクリプトはビルトインコマンドの exitを使用してこのアプローチを実行できます。このコマンドは:
exit 4
そのスクリプトを終了させ、エラー状態として4を返します。エラー状態を指定しない場合、(exit が引数なしで実行した場合、またはこのスクリプトがexitを呼び出さずに終端に到達した場合)そのexitの直前のコマンドの返り値をスクリプトは返します。
Bashの演算子 && ("and") と || ("or") を使ってエラー状態を処理できます。もし二つのコマンドが&&でつなげられていた場合、左のコマンドが実行されますが、そのコマンドが成功した場合に限って右のコマンドが実行します。逆にそれらのコマンドが||でつなげられていた場合、右のコマンドが実行されるのは左のコマンドが失敗したときだけです。
例えば、file.txtファイルを削除し、それと同じファイルを空ファイルで作成したいとします。Unixのユーティリティコマンドrm ("remove")でファイルを削除できます。そしてUnixのユーティリティのtouchで作成できます。そのためこのように書けます。
rm file.txt
touch file.txt
しかし、本当は、もしrmが失敗した場合、touchを実行したくありません。わたしたちはファイルを削除に失敗した場合、再作成はしたくなりません。そのためこのように書けます。
rm file.txt && touch file.txt
これと前と同じですが、rm が成功でない限りtouch を実行しません。
3番目のブール値のような演算子! ("not")はコマンドの終了値を逆にします。例えば、このコマンド
! rm file.txt
はrm file.txt
と同じですが、これはrmが失敗したときに成功を返し、rmが成功したときに失敗になります。 (これはコマンドの成功か失敗かを終了値として使用するときにのみ有用です。このあとで、not演算子が有用に使用する拡張した使用例を示します。)
このコマンドの終了値は(簡単に)$?で利用できます。複数の終了状態を区別する場合にも利用できます。例えば、指定パターンがファイルの行にマッチした行を探すgrepコマンドはマッチした場合0を返し、マッチしなければ1を返し、純然たるエラーが発生すれば2を返します。
条件式とifステートメント
[編集]よくありますが、ある条件を満した場合にのみコマンドを実行したい場合があります。例えば、cp source.txt destination.txt
("source.txt ファイルをdestination.txtにコピー") するコマンドを実行したいですが、それはただ source.txt が存在する場合だけとします。それはこのように書けます:
#!/bin/bash
if [[ -e source.txt ]] ; then
cp source.txt destination.txt
fi
これには2個のビルトインコマンドを使用しています。
- この[[ 条件 ]] 構文は条件 が真(true)の場合、0の終了値を返しますが、条件 が偽(false)の場合、非ゼロを返します。前の例では条件 が -e source.txtで、それはsource.txtという名前のファイルが存在している場合真(true)になります。
- この
- if command1 ; then
command2
fi
- if command1 ; then
別の書き方をすると、上のものは次のものと同じです。:
#!/bin/bash
[[ -e source.txt ]] && cp source.txt destination.txt
ただしより直接的です。(そして自由度が高く、短いです)
一般的に、Bashは成功の終了値(0)を"真(true)"の意味とし、失敗の終了値(非ゼロ)を"偽(false)"の意味として扱います。例えば、ビルトインコマンドのtrue は常に "成功(succeeds)" し(0を返し)、ビルトインコマンドの false 常に "失敗(fails)"し (1を返し)ます。
Caution: [[ と ]] の前後には半角スペースが必要です。というのも Bash はそれぞれを単語として認識しているからです。そのため if[[ や [[-e は正しく動作しません。 |
ifステートメント
[編集]上で見たように、ifステートメントはより自由度が高いです。実際には、if のテストコマンドが成功した場合、複数の コマンドが指定してできますし、加えて else 句をつかって、if のテストコマンドが失敗した場合に実行するコマンドを複数指定できます。:
#!/bin/bash
if [[ -e source.txt ]] ; then
echo 'source.txt exists; copying to destination.txt.'
cp source.txt destination.txt
else
echo 'source.txt does not exist; exiting.'
exit 1 # terminate the script with a nonzero exit status (failure)
fi
コマンドには他のif ステートメントを含めることができます。つまりあるif を他のステートメントの中にネストすることができます。この例では、if ステートメントを他のifステートメントのelse句にネストさせています。
#!/bin/bash
if [[ -e source1.txt ]] ; then
echo 'source1.txt exists; copying to destination.txt.'
cp source1.txt destination.txt
else
if [[ -e source2.txt ]] ; then
echo 'source1.txt does not exist, but source2.txt does.'
echo 'Copying source2.txt to destination.txt.'
cp source2.txt destination.txt
else
echo 'Neither source1.txt nor source2.txt exists; exiting.'
exit 1 # terminate the script with a nonzero exit status (failure)
fi
fi
この特定のパターン— ちょうどひとつのifステートメントだけを含む else 句は、フォールバックテストを表しているのですが、これは、Bashの標準的な省略形が用意されています。elif ("else-if") 句といいます。上の例をこれで書き直してます。
#!/bin/bash
if [[ -e source1.txt ]] ; then
echo 'source1.txt exists; copying to destination.txt.'
cp source1.txt destination.txt
elif [[ -e source2.txt ]] ; then
echo 'source1.txt does not exist, but source2.txt does.'
echo 'Copying source2.txt to destination.txt.'
cp source2.txt destination.txt
else
echo 'Neither source1.txt nor source2.txt exists; exiting.'
exit 1 # terminate the script with a nonzero exit status (failure)
fi
ひとつのif ステートメントには任意の数の elif 句を持てます。これは任意の数のフォールバック条件を示しています。
最後に、ifの条件が偽の場合にはあるコマンドを実行したいが、条件が正の場合にはコマンドを実行したくないことがあります。そのような場合、コマンドの前に置く、実行結果の真偽をひっくりかえすビルトインコマンドの!を使うといいでしょう。例えば、destination.txt がすでに存在しなかった場合にのみ、source.txt を destination.txt にコピーする例をあげます。
#!/bin/bash
if ! [[ -e destination.txt ]] ; then
cp source.txt destination.txt
fi
今までの例はすべてtest式でした、実際にはifであらゆるコマンドが実行でき、そのコマンドの終了値が0のときに thenのに含まれるコマンドが実行されます。
# First build a function that simply returns the code given
returns() { return $*; }
# Then use read to prompt user to try it out, read `help read' if you have forgotten this.
read -p "Exit code:" exit
if (returns $exit)
then echo "true, $?"
else echo "false, $?"
fi
if のふるまい、動作は論理演算の 'and' && や 'or' || と似通ったところがあります。
# Let's reuse the returns function.
returns() { return $*; }
read -p "Exit code:" exit
# if ( and ) else fi
returns $exit && echo "true, $?" || echo "false, $?"
# The REAL equivalent, false is like `returns 1'
# Of course you can use the returns $exit instead of false.
# (returns $exit ||(echo "false, $?"; false)) && echo "true, $?"
繰り返しになりますが、これらの論理演算の間違った使用はエラーの元です。これらの例がうまくいくのは素のechoは常に成功するからです。
条件式
[編集]上の、fileが存在した場合に真を返す、-e file条件の他にも、Bashの [[ … ]] 表記はいくつか便利な条件をサポートしています。ここではよく使用する条件をあげます。
- -d file
- file が存在し、それがディレクトリである場合に真
- -f file
- file が存在し、それが通常のファイルである場合に真
- string1 == string2
- 文字列string1 と 文字列string2 が等しい場合に真
- string ~= pattern
- 文字列 string がパターンpatternにマッチした場合に真 (pattern はファイル名展開と同じ形式が使えます。例えばクォートされていない * は "0文字以上の任意の文字列")
- string != pattern
- 文字列string がパターン patternにマッチしない場合に真
上の最後の3つのテストで、左の値は通常変数展開を行います。例えば、[[ "$var" = 'value' ]] が真を返すのは、var変数の値にvalueという文字列が含まれている場合です。
上の条件はほんのさわりです、ファイルを検査する条件はもっとあります。また文字列を検査する条件ももう少しあります。また整数の値を検査する条件もありますし、これらのグループに含まれない条件もあります。
よく使用される、等値テストの使用方法としては、スクリプトの最初の引数($1)が特別なオプションかどうかを調べるというものがあります。例えば、source1.txt または source2.txt を destination.txtにコピーしようとするifステートメントを考えてみましょう。上のバージョンは非常にverbose(冗長)" です。多くの出力を生成します。通常わたしたちはそんなに多くの出力を生成するスクリプトを作成したいと思いません。しかし私達はユーザーに出力できるように求められるかもしれません。例えば第一引数に--verbose渡された場合です。次のスクリプトは上のifステートメントと同じですしかし、第一引数が--verboseである場合だけ出力します。
#!/bin/bash
if [[ "$1" == --verbose ]] ; then
verbose_mode=TRUE
shift # remove the option from $@
else
verbose_mode=FALSE
fi
if [[ -e source1.txt ]] ; then
if [[ "$verbose_mode" == TRUE ]] ; then
echo 'source1.txt exists; copying to destination.txt.'
fi
cp source1.txt destination.txt
elif [[ -e source2.txt ]] ; then
if [[ "$verbose_mode" == TRUE ]] ; then
echo 'source1.txt does not exist, but source2.txt does.'
echo 'Copying source2.txt to destination.txt.'
fi
cp source2.txt destination.txt
else
if [[ "$verbose_mode" == TRUE ]] ; then
echo 'Neither source1.txt nor source2.txt exists; exiting.'
fi
exit 1 # terminate the script with a nonzero exit status (failure)
fi
後者は、このあとで学習するシェル関数を学んだとき、これをよりコンパクトに書く方法がわかるでしょう。(事実、私達がすでに知っているように、これをコンパクトに表現できます。$verbose_mode 変数に TRUE または FALSE を設定するのではなく、$echo_if_verbose_mode に echo または : を設定することでコンパクトにできます。ここで、コロン:はBashのビルトインコマンドで何もしないコマンドです。それからecho を "$echo_if_verbose_mode"に置き換えます。"$echo_if_verbose_mode" messageコマンドは、verbose-modeが有効な場合echo messageコマンドになり、messageが表示します。verbose-modeが無効な場合なにもしないコマンドになります。しかし、この方法は混乱させてしまうかもしれません。)
条件の組み合わせ
[編集]複数の条件を "and" または "or"で組合せ、また 逆転させる"not"を使って、今までに見た Bashの表記をより一般的な表記にしましょう。次の例を考えましょう。
#!/bin/bash
if [[ -e source.txt ]] && ! [[ -e destination.txt ]] ; then
# source.txt exists, destination.txt does not exist; perform the copy:
cp source.txt destination.txt
fi
このテストコマンド[[ -e source.txt ]] && ! [[ -e destination.txt ]]
は && と ! の演算子を使っており、この条件は conditionが真のときに、[[ condition ]] が "真(successful)"を返すことを利用しています。つまり[[ -e source.txt ]] && ! [[ -e destination.txt ]]
がは、source.txtが存在する場合に! [[ -e destination.txt ]]
を実行します。さらに言えば、!は[[ -e destination.txt ]]
の終了値を逆転させるので、! [[ -e destination.txt ]]
が成功するのは、destination.txtが存在しない場合です。そのため、[[ -e source.txt ]] && ! [[ -e destination.txt ]]
が真を返すのは、source.txtが存在し、destination.txtが存在しない場合だけです。
[[ … ]]
構文は実際に、これらの演算子をビルトインで内部的にサポートしているので上の例は次のように書けます。
#!/bin/bash
if [[ -e source.txt && ! -e destination.txt ]] ; then
# source.txt exists, destination.txt does not exist; perform the copy:
cp source.txt destination.txt
fi
しかし、一般的な表記はよりクリアです。それはあらゆるテストコマンドを使用できるため、[[ … ]]
構文だけではないからです。
可読性の注意
[編集]上の例のif ステートメントは、人間や読みやすく理解しやすくなるようにフォーマットしています。これはこの本の例だけでなく現実世界のスクリプトでも重要です。特に上の例は次の規約に従っています。
- ifステートメント内のコマンドは一定量でインデントされています。(半角スペース2個で)。このインデントはBashとは関係ありません。-- Bashは行頭のホワイトスペースを無視します。-- しかしこれは人間のプログラマーにはとても重要です。インデントがない場合、if ステートメントの開始と終了がわからなくなり、またif ステートメントがあるかどうかもわからなくなります。一定のインデントは特に、ifステートメント内にネストしたifステートメント(や、あとで学ぶ他の制御構文)に重要です。
- thenの前のセミコロン;を使用します。これはコマンドを分割する特別な演算子です。これは改行とほぼ同じですが、異なる点もあります。(例えば、コメントは#から行末までで、# から;までではありません。)私達は、thenを次の行の行頭に書くこともでき、これは完全に合法です。単純なスクリプトでは十分でしょう。これは通常の構文が表われないように統一しましょう。しかし現実世界のスクリプトの場合プログラマーは通常if または elifの行の行末に; thenを置くのでここではこの規約に従っています。
- then または elseのあとに改行文字を入れます。この改行文字はオプションです。-- この改行はセミコロンに置き換えられません -- しかし改行文字によって if ステートメントの読み易さ、リーダビリティに貢献しています。
- 通常のコマンドは改行で区切り、セミコロンは使用しません。これは一般的な規約でifステートメントに限られるものではありません。各行にコマンドを配置することで、他のひとがそのスクリプトがなにをしているのかをざっくりと把握することができるようになります。
これらの正格な規約は特に重要ではありません。しかし規約は一貫して従うことがよくコードを規約に従って読み易くしましょう。あなたのコードを熟練プログラマが見たとき、または書き上げたあなたが数ヶ月後にそれを見返したとき、一貫性のなさやフォーマットの違反があるとスクリプトが何をしているかがわかりにくくさせる原因です。
反復・ループ
[編集]いくどか、私達はすこしだけ変化させながら一連のコマンドを繰り返して実行したいときがあります。例えば、*.txtにあてはまる全てのファイルをとり、*.txt.bakに名前を変える場合("backup")を想定しましょう。私達はファイル名展開を使用して、*.txtの名前のあるファイルリストを取得できます。しかし、そのリストをどのように使えばよいのでしょう。明らかにコマンドは含まれていません。例えば、'foo.txt' 'bar.txt' 'baz.txt'とすると、これから3つを移動させる必要があります。これに必要なのはforループです。
for file in *.txt ; do
mv "$file" "$file.bak"
done
上の例は、変数fileをとり、*.txtの展開のそれぞれの単語が割り当てられます。そのときどきで、ループの本体を実行します。いいかえればこれは次のものと同じです。
file='foo.txt'
mv "$file" "$file.bak"
file='bar.txt'
mv "$file" "$file.bak"
file='baz.txt'
mv "$file" "$file.bak"
ファイル名展開について特別なことはありません。他の引数のリストについても同じアプローチで繰り返しができます。たとえば、1から20までの整数の繰り返しを示します。(ここではブレース展開を使用しています。)
for i in {1..20} ; do
echo "$i"
done
また位置パラメータ "$@" のループを示します。
for arg in "$@" ; do
echo "$arg"
done
実際、よく現れる特定の場合のためにBashは同等の省略形を用意しています。for arg ; do
ここではin "$@"
が省略されている(しかし明示した形式のほうがより分かりやすいです。)
その他の種類のループにはwhileループがあります。これはifステートメントに似ていますが、テストコマンドが成功する限りそのLOOPを繰り返すところが違います。例えば、wait.txtが削除されるまで待つ必要があったとします。一つの方法として数秒間 休み(sleep) してその後に起動し、そのファイルがあるか確認します。この方法を繰り返しループさせることができます。
while [[ -e wait.txt ]] ; do
sleep 3 # "sleep" for three seconds
done
これとは逆に、untilループも使用できます。これは、与えられたコマンドが成功するまでループを繰り返します。上の逆になります。
until [[ -e proceed.txt ]] ; do
sleep 3 # "sleep" for three seconds
done
もちろん、これはwhile と !を組み合わせると同じになります。しかしいくつかの場合では、より読みやすくなります。
- ifと同様にwhileは真(true) または 偽(false)を判断します。いくつか試してください。
シェル関数
[編集]シェル関数は特別な種類の変数で、スクリプト内に書かれたスクリプトと言えます。シェル関数により、一連のコマンドを一つの名前のコマンドにまとめられます。これは、ある一連のコマンドをスクリプト内の色々な場所から(複数回)実行する場合に特に便利です。シェル関数は1つのコマンドだけでも構成できます。これは、元にするコマンドが特別に複雑な場合や、元のコマンドの意味や意図が読者にすぐにはわからりにくい場合に便利です。 (つまり、シェル関数は2つの目的があります。入力を節約できます。また名前の付いたコマンドを作成して、直感的にコードを読みやすくすることができます。)次のスクリプトを見てください
#!/bin/bash
# Usage: get_password VARNAME
# Asks the user for a password; saves it as $VARNAME.
# Returns a non-zero exit status if standard input is not a terminal, or if the
# "read" command returns a non-zero exit status.
get_password() {
if [[ -t 0 ]] ; then
read -r -p 'Password:' -s "$1" && echo
else
return 1
fi
}
get_password PASSWORD && echo "$PASSWORD"
上のスクリプトはget_passwordという名前のシェル関数を作成しています。これは、ユーザーにパスワードの入力を求め、その結果を指定した変数に保管します。get_password PASSWORDを実行するとパスワードは$PASSWORDに保存されます。get_passwordの呼び出しが成功した場合(これはその終了値によって決まる)、取得したパスワードを標準出力に出力します(これは現実的な仕様ではありません。ここではget_passwordの動作を説明することだけが目的です)。
この例のget_password関数はシェル関数でなければ実現できないわけではありませんが、シェル関数を使用した結果、読みやすくなりました。このシェル関数はビルトインコマンドのread(ユーザーの入力行を読み取り、それを1つ以上の変数に保存します)を呼び出します。readには、ほとんどのBashプログラマーにはよく知られていないオプションがあります。
- -rオプションはバックスラッシュ文字の特別な意味を無効にします。
- -pオプションは指定されたプロンプト(この場合はPassword:)を行頭に表示して入力を促します。
- -sオプションはこのようなパスワードを表示しないようにします(エコーバック無効)。
-sオプションを指定すると、ユーザーの改行も表示されません。そのため、echo コマンドで改行を出力しています。さらに、この関数は条件式-t 0を使用して、スクリプトの入力が確実に端末からのものになるようにしています。ファイルからの入力は受け付けませんし、パスワードが必要かどうかわかっていない他のプログラムからの入力も受け付けません。(これには議論の余地があります。スクリプトの機能によっては、一般的に、ソースに関係なく標準入力からパスワードを受け入れる方が良い場合があります。ただし、ソースがスクリプトを念頭に置いて設計されているという前提のもとでです。 このような複雑な一連のコマンドにget_passwordという名前を付けると、プログラマーはその部分が何をするのかを理解しやすくなります。
シェル関数内で位置パラメータ($1や$2などに加えて $@と$*と$#も)を使用するとそれは、そのシェル関数を含むスクリプトの引数ではなく、そのシェル関数の呼び出し時の引数を参照します。スクリプト自体の引数が必要な場合、"$@"などを使って明示的にシェル関数の引数として渡す必要があります。(またシェル関数内でのshiftやsetはそのシェル関数内の位置パラメータに影響します。スクリプト側の位置パラメータには影響しません。)
シェル関数呼び出しは、スクリプトやほとんどすべてのコマンドと同じように終了値を返します。終了値を明示的に指定するには、returnコマンドを使用します。returnコマンドにより、シェル関数の呼び出しを終了し、指定した終了値を返します。 (この目的で exitコマンドを使用できません。シェル関数の外部から呼び出された場合と同様にスクリプト全体を終了させます)終了値が指定されていない場合、つまりreturnコマンドに引数が指定されていないかreturnコマンドを実行せずに関数の終わりに到達すると、シェル関数は最後に実行されたコマンドの終了ステータスを返します。
加えて、シェル関数の宣言からfunctionまたは( ) を省略できますが、どちらかは残しておかなければなりません。多くのプログラマはfunction get_password ( )
と書く代わりに、get_password()
と書くことが多いようです。同様に、{ … }の表記は必須ではありませんし、これはシェル関数に限ることでもありません。{ … }は、一連のコマンドをグルーピング化してひとつの複合コマンドにまとめるものです。シェル関数の本体は複合コマンドでなければなりませんが複合コマンドは、{ … } 表記のブロックまたはifステートメントです。本体が単一の複合コマンドしかなくて理論的には省略できる場合であっても、通常は{ … }ブロックです。
サブシェル、環境変数、スコープ
[編集]Caution: この章には時間をかけてください。これらの概念は、一度理解できたあとでは比較的簡単に理解できますが、多くの点で他のプログラミング言語の類似の概念とは異なります。 Bashの経験がある人も含め、多くのプログラマーやシステム管理者は、これを最初に学んだ際には直感に反すると感じると思います。 |
サブシェル
[編集]Bashでは、一つまたは複数のコマンドを丸括弧でつつむことで、それらのコマンドを "サブシェル"で実行できます。(サブシェルを暗黙的に作成する方法があり、後に学べられます。) サブシェルはそれをつつむ コンテキストの実行環境 のコピーを受けとります。この実行環境には、変数やそのほかのものを含んでいます。しかしサブシェルが変更または作成した実行環境はサブシェルが完了したあとに元へ戻しません。例をあげます。
#!/bin/bash
foo=bar
echo "$foo" # prints 'bar'
# subshell:
(
echo "$foo" # prints 'bar' - the subshell inherits its parents' variables
baz=bip
echo "$baz" # prints 'bip' - the subshell can create its own variables
foo=foo
echo "$foo" # prints 'foo' - the subshell can modify inherited variables
)
echo "$baz" # prints nothing (just a newline) - the subshell's new variables are lost
echo "$foo" # prints 'bar' - the subshell's changes to old variables are lost
出力内容:
bar bar bip foo bar
Tip: 変数の値を一時的に変更する関数が必要な場合、-- 呼び出し中は修正した値を使用し呼び出し後に元の値に戻したい場合 -- その関数呼び出しを丸括弧でくくることができます。これをサブシェルといいます。サブシェルは修正を 隔離 しそのまわりの実行環境へ影響を波及させません。(可能な場合、この方法で記述した関数は問題が起きにくいです。また local キーワードも助けてくれます。) |
これは関数の定義も同じ点があります。通常の変数と同じでサブシェルの中で定義されたシェル関数はそのサブシェルの外からは見えません。
サブシェルはまた実行環境の他の要素の変更も区別します。特に cd ("change directory")コマンドが影響するのはサブシェルの中だけです。スクリプトの例をあげます。
#!/bin/bash
cd /
pwd # prints '/'
# subshell:
(
pwd # prints '/' - the subshell inherits the working directory
cd home
pwd # prints '/home' - the subshell can change the working directory
) # end of subshell
pwd # prints '/' - the subshell's changes to the working directory are lost
出力内容:
/ / /home /
Tip: もしあなたのスクリプトが与えられたコマンドを実行する前に現在ディレクトリを変更する必要がある場合、可能であればサブシェルはよいアイディアです。サブシェルを使用しない場合、スクリプトを読み間常に現在ディレクトリを追跡しなければなりません。(代替方法としては、ビルトインコマンドの pushd とpopdがあり、類似した効果が得られます。) |
サブシェル内のexitステートメントはそのサブシェルだけを終了させます。また例を挙げます。:
#!/bin/bash
( exit 0 ) && echo 'subshell succeeded'
( exit 1 ) || echo 'subshell failed'
表示内容:
subshell succeeded subshell failed
スクリプト全体の場合、最後に実行したコマンドの終了値をこのexitが返す終了値のデフォルトにするのと同様に、明示的にexitで値を指定しなかったサブシェルの場合、exitが返すデフォルトは最後に実行したコマンドの終了値になります。
環境変数
[編集]私達が今までみてきたように、プログラムが呼ばれたとき、そのプログラムはコマンドライン上で明確にリストアップされた引数のリストを受けとります。今まで、私達が説明してこなかったものに、プログラムが受けとる名前と値のペアのリストからなる環境変数があります。この環境変数はプログラミング言語毎にアクセス方法が異なります。プログラミング言語Cのプログラムはgetenv("variable_name")を使用します。(mainの第三引数として受け取ります)、プログラミング言語perlのプログラムは$ENV{'variable_name'}を使います。またプログラミング言語Javaの場合はSystem.getenv().get("variable_name")を使います。
Bashの場合、環境変数は単純に通常のBash変数となります。例えば、次のスクリプトは、HOME環境変数の値を出力します。
#!/bin/bash
echo "$HOME"
ただし逆は成り立ちません。通常のBash変数は自動的に環境変数になりません。次を例を確認してください。
#!/bin/bash
foo=bar
bash -c 'echo $foo'
このスクリプトはbarを出力しません。というのもfoo変数はbash コマンドに環境変数として渡されていないからです。(bash -c script arguments… は、一行のBashスクリプトと同じで、スクリプトscriptを実行し、引数 argumentsをとれます。)
通常のBash変数を環境変数にするには、"export" を使わなければなりません。次のスクリプトはbarが出力できます。
#!/bin/bash
export foo=bar
bash -c 'echo $foo'
exportは環境変数を作成しないことに注意してください。これは、実際 Bashの変数にエクスポートした変数と印をつけるだけだからです。そして、そのあとで行われるBash変数への代入は環境変数にも同様に影響します。この影響をスクリプトでみてみましょう。
#!/bin/bash
foo=bar
bash -c 'echo $foo' # prints nothing
export foo
bash -c 'echo $foo' # prints 'bar'
foo=baz
bash -c 'echo $foo' # prints 'baz'
exportコマンドは環境変数から変数を取り除くことにも使用します。その際には、 -nオプションを使います。例えば、export -n foo
コマンドでexport foo
の影響を取り消します。またひとつのコマンドで複数の変数をエクスポートまたはその取り消しができます。export foo bar
または export -n foo bar
でできます。
環境変数はコマンドに 渡される だけであることに注意しましょう。環境変数はコマンドから返されません。この点で、環境変数は通常のBash変数とサブシェルに似ています。
#!/bin/bash
export foo=bar
bash -c 'foo=baz' # has no effect
echo "$foo" # print 'bar'
上のスクリプトは barと出力します。 一行のスクリプトの内部で $foo 変数を変更していますが、その変更は呼び出し側のプロセスには影響しません。(しかしそのスクリプトによって呼び出されたスクリプトには影響するでしょう。)
特定の環境変数を1つのコマンドだけに影響させたい場合、同じ行のコマンドの前に変数割り当て(複数の変数割り当ても可能)の構文を使用して、var=value command
といった構文を使用できます。(変数割り当ての構文を使用しているにもかかわらず、通常のBash変数割り当てとは大きく異なります。これらは変数は自動的に環境にエクスポートされますが、1つのコマンドに対してのみ影響します。
変数割り当てと似た文法でこれとは全く異なった効果があります。これを混乱を避けたいのなら、同じ効果を得るために一般的なUnixユーティリティ env を使いましょう。このユーティリティは、1つのコマンドの環境変数を 削除できます。または1回のコマンドで 全ての環境変数を削除できます。)もし $var がすでに存在し、かつ実際の値を1つのコマンドだけの実行環境にだけ影響させたい場合、var="$var" command
として実行できます。
変数の定義や関数の定義をひとつの Bash スクリプトにまとめておき(例えば header.sh)、このファイルを他の Bash スクリプト(例えば main.sh) から読み込むことを便利だと伝えてきた。しかし、単純に他の Bash スクリプトを起動した場合(例えば./header.sh や bash ./header.shではうまく動作しない。header.sh にある変数の定義は"エクスポート"されていないので、main.shから見えないからです。(これは通常混乱しやすい点です。: export exports variables into the environment so that other processes can see them, but they're still only seen by child processes, not by parents.) However, we can use the Bash built-in command . ("dot") or source, which runs an external file almost as though it were a shell function. If header.sh looks like this:
foo=bar
function baz ()
{
echo "$@"
}
then this script:
#!/bin/bash
. header.sh
baz "$foo"
will print 'bar'.
範囲・スコープ
[編集]Bashの変数のスコープに不規則な点をいくつか見てきました。要約すると以下のようになります。
- 通常のBash変数の範囲は、それを含むシェル、またはそれに含まれるサブシェルに限定されています。
- 通常のBash変数は子プロセスからは見えません。(外部プログラムからも)
- サブシェルで作成された通常のBash変数は親のシェルからは見えません。
- 通常のBash変数がサブシェル内で修正された場合、その変更は親のシェルからは見えません。
- この挙動は関数でも同じで、通常のBash変数にもあてはまる。
- 関数呼び出しは、サブシェルで継承された実行ではありません。
- 関数内での変数の修正は通常、関数を呼び出したコードから 見えます。
- 環境にエクスポートされた Bash 変数のスコープは、それを包むシェル、そのシェルのサブシェル、そのシェルの子プロセスです。
- ビルトインのexport コマンドは環境に変数をエクスポートするために使用できます。(これ以外にも方法はあるがこれが一番通常の方法です。)
- エクスポートされていない変数はその子プロセスで見えないことです。特に親シェルや親プロセスでは見えません。
- その他のプログラムのような、外部のBashスクリプトは、子プロセスとして実行します。ビルトインコマンドの . や source はそのスクリプトを内部で実行します。この場合、サブシェルのように継承しません。
To this we now add:
- Bash variables that are localized to a function-call are scoped to the function that contains them, including any functions called by that function.
- The local built-in command can be used to localize one or more variables to a function-call, using the syntax
local var1 var2
orlocal var1=val1 var2=val2
. (There are other ways as well — for example, the declare built-in command has the same effect — but this is probably the most common way.) - They differ from non-localized variables only in that they disappear when their function-call ends. In particular, they still are visible to subshells and child function-calls. Furthermore, like non-localized variables, they can be exported into the environment so as to be seen by child processes as well.
- The local built-in command can be used to localize one or more variables to a function-call, using the syntax
In effect, using local to localize a variable to a function-call is like putting the function-call in a subshell, except that it only affects the one variable; other variables can still be left non-"local".
It's important to note that, although local variables in Bash are very useful, they are not quite as local as local variables in most other programming languages, in that they're seen by child function-calls. For example, this script:
#!/bin/bash
foo=bar
function f1 ()
{
echo "$foo"
}
function f2 ()
{
local foo=baz
f1 # prints 'baz'
}
f2
will actually print baz rather than bar. This is because the original value of $foo is hidden until f2 returns. (In programming language theory, a variable like $foo is said to be "dynamically scoped" rather than "lexically scoped".)
One difference between local and a subshell is that whereas a subshell initially takes its variables from its parent shell, a statement like local foo immediately hides the previous value of $foo; that is, $foo becomes locally unset. If it is desired to initialize the local $foo to the value of the existing $foo, we must explicitly specify that, by using a statement like local foo="$foo".
When a function exits, variables regain the values they had before their local declarations (or they simply become unset, if they had previously been unset). Interestingly, this means that a script such as this one:
#!/bin/bash
function f ()
{
foo=baz
local foo=bip
}
foo=bar
f
echo "$foo"
will actually print baz: the foo=baz statement in the function takes effect before the variable is localized, so the value baz is what is restored when the function returns.
And since local is simply an executable command, a function can decide at execution-time whether to localize a given variable, so this script:
#!/bin/bash
function f ()
{
if [[ "$1" == 'yes' ]] ; then
local foo
fi
foo=baz
}
foo=bar
f yes # modifies a localized $foo, so has no effect
echo "$foo" # prints 'bar'
f # modifies the non-localized $foo, setting it to 'baz'
echo "$foo" # prints 'baz'
will actually print
bar baz
パイプとコマンド置換
[編集]As we have seen, a command's return value, taken strictly, is just a small non-negative integer intended to indicate success or failure. Its real output is what it writes to the standard output stream. By default, text written to the standard output stream is printed to the terminal, but there are a few ways that it can be "captured" and used as the command's true return value.
パイプ
[編集]When a sequence of commands are linked together in a pipeline, the output of each command is passed as input to the next. This is a very powerful technique, since it lets us combine a number of small utility programs to create something complex.
コマンド置換
[編集]Command substitution is a bit like variable expansion, but it runs a command and captures its output, rather than simply retrieving the value of a variable. For example, consider our get_password example above:
#!/bin/bash
function get_password ( )
# Usage: get_password VARNAME
# Asks the user for a password; saves it as $VARNAME.
# Returns a non-zero exit status if standard input is not a terminal, or if the
# "read" command returns a non-zero exit status.
{
if [[ -t 0 ]] ; then
read -r -p 'Password:' -s "$1" && echo
else
return 1
fi
}
get_password PASSWORD && echo "$PASSWORD"
There is really no reason that the caller should have to save the password in a variable. If get_password simply printed the password to its standard output, then the caller could use command substitution, and use it directly:
#!/bin/bash
function get_password ( )
# Usage: get_password
# Asks the user for a password; prints it for capture by calling code.
# Returns a non-zero exit status if standard input is not a terminal, or if
# standard output *is* a terminal, or if the "read" command returns a non-zero
# exit status.
{
if [[ -t 0 ]] && ! [[ -t 1 ]] ; then
local PASSWORD
read -r -p 'Password:' -s PASSWORD && echo >&2
echo "$PASSWORD"
else
return 1
fi
}
echo "$(get_password)"
To evaluate "$(get_password)", Bash runs the command get_password in a subshell, capturing its standard output, and replaces $(get_password) with the captured output.
In addition to the notation $(…), an older notation `…` (using backquotes) is also supported, and still quite commonly found. The two notations have the same effect, but the syntax of `…` is more restrictive, and in complex cases it can be trickier to get right.
Command substitution allows nesting; something like a "$(b "$(c)")" is allowed. (It runs the command c, using its output as an argument to b, and using the output of that as an argument to a.)
A command substitution can actually contain a sequence of commands, rather than just one command. The output of all of these commands is captured. As we've seen previously, semicolons can be used instead of newlines to separate commands; that is particularly commonly done in command substitution. A command substitution can even contain variable assignments and function definitions (though, since the substituted commands run within a subshell, variable assignments and function definitions inside the command will not be seen outside it; they are only useful if they are used inside the substituted commands).
シェル演算
[編集]Bashでの演算の表記方法は、Cのそれをモデルにしています。そのため、他のC言語から派生または参考にした、C++, Java, Perl, JavaScript, C#, PHPなどの言語ともよく似ています。ただ大きく異なるのは、Bashは整数演算しかサポートしていないことです。浮動点演算(整数や小数)はサポートされていません。3 + 4の結果は7を期待できますが、3.4 + 4.5 はシンタックスエラーになります。13 / 5 はシンタックスエラーになりませんが、その結果は整数除算になるので2になります。2.6にはなりません。
演算式
[編集]Perhaps the most common way to use arithmetic expressions is in arithmetic expansion, where the result of an arithmetic expression is used as an argument to a command. Arithmetic expansion is denoted $(( … )). For example, this command:
echo $(( 3 + 4 * (5 - 1) ))
prints 19.
expr (古い方法)
[編集]Another way to use arithmetic expressions is using the Unix program "expr", which was popular before Bash supported math.[2] Similar to Arithmetic expansion, this command:
echo `expr 3 + 4 \* \( 5 - 1 \)`
prints 19. Note that using "expr" requires an escape character "\" before the multiplication operator "*" and parentheses. Further note the spaces between each operator symbol, including the parentheses.
数値演算子
[編集]In addition to the familiar notations + (addition) and - (subtraction), arithmetic expressions also support * (multiplication), / (integer division, described above), % (modulo division, the "remainder" operation; for example, 11 divided by 5 is 2 remainder 1, so 11 % 5 is 1), and ** ("exponentiation", i.e. involution; for example, 24 = 16, so 2 ** 4 is 16).
The operators + and -, in addition to their "binary" (two-operand) senses of "addition" and "subtraction", have "unary" (one-operand) senses of "positive" and "negative". Unary + has basically no effect; unary - inverts the sign of its operand. For example, -(3*4) evaluates to -12, and -(-(3*4)) evaluates to 12.
変数への参照
[編集]Inside an arithmetic expression, shell variables can be referred to directly, without using variable expansion (that is, without the dollar sign $). For example, this:
i=2+3
echo $(( 7 * i ))
prints 35. (Note that i is evaluated first, producing 5, and then it's multiplied by 7. If we had written $i rather than i, mere string substitution would have been performed; 7 * 2+3 equals 14 + 3, that is, 17 — probably not what we want.)
The previous example shown using "expr":
i=`expr 2 + 3`
echo `expr 7 \* $i`
prints 35.
変数への代入
[編集]Shell variables can also be assigned to within an arithmetic expression. The notation for this is similar to that of regular variable assignment, but is much more flexible. For example, the previous example could be rewritten like this:
echo $(( 7 * (i = 2 + 3) ))
except that this sets $i to 5 rather than to 2+3. Note that, although arithmetic expansion looks a bit like command substitution, it is not executed in a subshell; this command actually sets $i to 5, and later commands can use the new value. (The parentheses inside the arithmetic expression are just the normal mathematical use of parentheses to control the order of operations.)
In addition to the simple assignment operator =, Bash also supports compound operators such as +=, -=, *=, /=, and %=, which perform an operation followed by an assignment. For example, (( i *= 2 + 3 )) is equivalent to (( i = i * (2 + 3) )). In each case, the expression as a whole evaluates to the new value of the variable; for example, if $i is 4, then (( j = i *= 3 )) sets both $i and $j to 12.
Lastly, Bash supports increment and decrement operators. The increment operator ++ increases a variable's value by 1; if it precedes the variable-name (as the "pre-increment" operator), then the expression evaluates to the variable's new value, and if it follows the variable-name (as the "post-increment" operator), then the expression evaluates to the variable's old value. For example, if $i is 4, then (( j = ++i )) sets both $i and $j to 5, while (( j = i++ )) sets $i to 5 and $j to 4. The decrement operator -- is exactly the same, except that it decreases the variable's value by 1. Pre-decrement and post-decrement are completely analogous to pre-increment and post-increment.
Arithmetic expressions as their own commands
[編集]A command can consist entirely of an arithmetic expression, using either of the following syntaxes:
(( i = 2 + 3 ))
let 'i = 2 + 3'
Either of these commands will set $i to 5. Both styles of command return an exit status of zero ("successful" or "true") if the expression evaluates to a non-zero value, and an exit status of one ("failure" or "false") if the expression evaluates to zero. For example, this:
(( 0 )) || echo zero
(( 1 )) && echo non-zero
will print this:
zero non-zero
The reason for this counterintuitive behavior is that in C, zero means "false" and non-zero values (especially one) mean "true". Bash maintains that legacy inside arithmetic expressions, then translates it into the usual Bash convention at the end.
コンマ演算子
[編集]Arithmetic expressions can contain multiple sub-expressions separated by commas ,. The result of the last sub-expression becomes the overall value of the full expression. For example, this:
echo $(( i = 2 , j = 2 + i , i * j ))
sets $i to 2, sets $j to 4, and prints 8.
The let built-in actually supports multiple expressions directly without needing a comma; therefore, the following three commands are equivalent:
(( i = 2 , j = 2 + i , i * j ))
let 'i = 2 , j = 2 + i , i * j'
let 'i = 2' 'j = 2 + i' 'i * j'
比較、ブール値と条件演算子
[編集]Arithmetic expressions support the integer comparison operators <, >, <= (meaning ≤), >= (meaning ≥), == (meaning =), and != (meaning ≠). Each evaluates to 1 for "true" or 0 for "false".
They also support the Boolean operators && ("and"), which evaluates to 0 if either of its operands is zero, and to 1 otherwise; || ("or"), which evaluates to 1 if either of its operands is nonzero, and to 0 otherwise; and ! ("not"), which evaluates to 1 if its operand is zero, and to 0 otherwise. Aside from their use of zero to mean "false" and nonzero values to mean "true", these are just like the operators &&, ||, and ! that we've seen outside arithmetic expressions. Like those operators, these are "short-cutting" operators that do not evaluate their second argument if their first argument is enough to determine a result. For example, (( ( i = 0 ) && ( j = 2 ) )) will not evaluate the ( j = 2 ) part, and therefore will not set $j to 2, because the left operand of && is zero ("false").
And they support the conditional operator b ? e1 : e2. This operator evaluates e1, and returns its result, if b is nonzero; otherwise, it evaluates e2 and returns its result.
These operators can be combined in complex ways:
(( i = ( ( a > b && c < d + e || f == g + h ) ? j : k ) ))
算術的 for ループ
[編集]Above, we saw one style of for-loop, that looked like this:
# print all integers 1 through 20:
for i in {1..20} ; do
echo $i
done
Bash also supports another style, modeled on the for-loops of C and related languages, using shell arithmetic:
# print all integers 1 through 20:
for (( i = 1 ; i <= 20 ; ++i )) ; do
echo $i
done
This for-loop uses three separate arithmetic expressions, separated by semicolons ; (and not commas , — these are completely separate expressions, not just sub-expressions). The first is an initialization expression, run before the loop begins. The second is a test expression; it is evaluated before every potential loop iteration (including the first), and if it evaluates to zero ("false"), then the loop exits. The third is a counting expression; it is evaluated at the end of each loop iteration. In other words, this for-loop is exactly equivalent to this while-loop:
# print all integers 1 through 20:
(( i = 1 ))
while (( i <= 20 )) ; do
echo $i
(( ++i ))
done
but, once you get used to the syntax, it makes it more clear what is going on.
ビット演算
[編集]In addition to regular arithmetic and Boolean operators, Bash also offers "bitwise" operators, meaning operators that operate on integers qua bit-strings rather than qua integers. If you are not already familiar with this concept, you can safely ignore these.
Just as in C, the bitwise operators are & (bitwise "and"), | (bitwise "or"), ^ (bitwise "exclusive or"), ~ (bitwise "not"), << (bitwise left-shift), and >> (bitwise right-shift), as well as &= and |= and ^= (which include assignment, just like +=).
整数リテラル
[編集]整数定数は「整数リテラル」で表現します。今までみてきたように、例えば34は数字の34を表わす整数リテラルです。私たちがみてきた例は全て10進数(基数10)の整数リテラルでした。10進数がデフォルトです。しかしbashでは基数に2–64 の整数リテラルを表現できます。この場合基数#値という表記方法を使用します。基数自体は10進数で表現してください。例を挙げます。
echo $(( 12 )) # 基数が10の10進数、デフォルト(decimal) echo $(( 10#12 )) # 明示的に基数を表示した10進数 (decimal) echo $(( 2#1100 )) # 基数2の2進数 (binary) echo $(( 8#14 )) # 基数8の8進数 (octal) echo $(( 16#C )) # 基数16の16進数 (hexadecimal) echo $(( 8 + 2#100 )) # 基数10の10進数であらわした 8 (decimal)に 基数2の2進数であらわした 4の和
これらはそれぞれ 12 を表示します。(この表記方法は整数リテラルをどのように解釈するかだけに影響します。算術式表現の結果は10進数のままです。)
基数が 11 から 36 の範囲では英語文字の A から Z を使用します。これらの文字はそれぞれ 10 から 35に相当しますが、大文字小文字は同一視します。基数が37 から 64では違います。a から z を10 から 35に、AからZを36から61に相当させます。アットマークは @ 62で アンダースコア、アンダーライン _ は 63 に相当します。例えば 64#@A3 は 256259 (62 × 642 + 36 × 64 + 3)
ここで特別な表記方法が二つあります。先頭が0のリテラルは基数8の8進数を表します。先頭が0x または 0Xのリテラルは基数16の16進数を表します。例えば 030 は to 8#30で0x6F は16#6Fです。
整数変数
[編集]A variable may be declared as an integer variable — that is, its "integer attribute" may be "set" — by using this syntax:
declare -i n
After running the above command, any subsequent assignments to n will automatically cause the right-hand side to be interpreted as an arithmetic expression. For example, this:
declare -i n
n='2 + 3 > 4'
is more or less equivalent to this:
n=$((2 + 3 > 4))
except that the first version's declare -i n
will continue to affect later assignments as well.
In the first version, note the use of quotes around the right-hand side of the assignment. Had we written n=2 + 3 > 4, it would have meant "run the command + with the argument 3, passing in the environment variable n set to 2, and redirecting standard output into the file 4"; which is to say, setting a variable's integer attribute doesn't affect the overall parsing of assignment statements, but merely controls the interpretation of the value that is finally assigned to the variable.
We can "unset" a variable's integer attribute, turning off this behavior, by using the opposite command:
declare +i n
The declare built-in command has a number of other uses as well: there are a few other attributes a variable can have, and declare has a few other features besides turning attributes on and off. In addition, a few of its properties bear note:
- As with local and export, the argument can be a variable assignment; for example,
declare -i n=2+3
sets $n's integer attribute and sets it to 5. - As with local and export, multiple variables (and/or assignments) can be specified at once; for example,
declare -i m n
sets both $m's integer attribute and $n's. - When used inside a function, declare implicitly localizes the variable (unless the variable is already local), which also has the effect of locally unsetting it (unless the assignment syntax is used).
非整数の演算
[編集]説明したように、Bashシェルの演算は整数演算のみをサポートしています。このような場合、整数演算以外の計算機能を持つ類似の外部プログラムが助けになります。特によく使用されるUnixユーティリティにはbcがあります。次のコマンドは3.4 + 2.2=5.6を計算します。
echo "$(echo '3.4 + 2.2' | bc)"
上の例は 5.6と表示します。言うまでもありませんが、bc はBashシェル演算に統合されていないので、便利ではありません。例えば次を見てください。
# print the powers of two, from 1 to 512:
for (( i = 1 ; i < 1000 ; i *= 2 )) ; do
echo $i
done
これを非整数演算で行うと次のようになります。
# print the powers of one-half, from 1 to 1/512:
i=1
while [ $( echo "$i > 0.001" | bc ) = 1 ] ; do
echo $i
i=$( echo "scale = scale($i) + 1 ; $i / 2" | bc )
done
Part of this is because we can no longer use an arithmetic for-loop; part of it is because referring to variables and assigning to variables is trickier now (since bc is not aware of the shell's variables, only its own, unrelated ones); and part of it is because bc communicates with the shell only via input and output.
外部プログラム
[編集]シェルの一種としてのBashは、実際に 'グルー言語'です。Bashは様々なプログラムが協調して動作することができ、より有用に使用できます。何が必要なのかをSearch The Internet検索しましょう。コマンドラインのユーティリティは数多くあります。
whiptailの使用
[編集]Whiptail はシェルスクリプトからdialog boxes を表示できるプログラムで、ユーザーフレンドリーな方法でユーザーに情報を提供したり、ユーザーから入力を取得したりできます。 Whiptail はDebian などのさまざまな他の GNU/Linux ディストリビューションに含まれています。
- GNU/Linux Dictionaryによると: whiptail は "dialog" を置き換えるもので、ncursesの代わりに newt を使用しています。
- そのREADMEによると: whiptail はdialogに互換性があるように設計されています。しかし、一部の機能(tailbox, timebox, calendarbox等のダイアログボックス)が不足しています。
Bash Shell Scripting/Whiptailを参照してください。
AWKのページまたはman awk (man gawk)を参照してください。
sedのページまたはman sedを参照してください。
grepのページまたはman grepを参照してください。
man、info、helpの使用
[編集]これらは、ヘルプまたはリファレンスを参照するときに使用するプログラムです。man は roff 形式のマニュアルページを表示します。info はtexinfo 形式の文書を表示します。一方、help はビルトインのヘルプを表示します。
コマンドラインプログラムに --long-help, --help または --usage を与えると、使用方法の情報を表示することがあります。同じ意味のシノニムには -H や -hもありえます。
これらを試しましょう:
man --help
man man
info --help
man info
info info
help help
man と info のコマンドライン引数に -h を指定したときにも同じ情報が表示されます。
入力/出力
[編集]ビルトインのread
[編集]readのヘルプより:
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
read はユーザーの入力や標準入力またはパイプからの読み込みのためのツールです。
ユーザー入力の例:
# 'readline' prompt default variable name
read -e -p "Do this:" -i "destroy the ship" command
echo "$command"
それをよりシンプルにした例:
pause() { read -n 1 -p "Press any key to continue..."; }
Hello-world レベルの標準出力の操作の例:
echo 'Hello, world!' | { read hello
echo $hello }
Just be creative. For example, in many ways read can replace whiptail. Here is an example, extracted from Arthur200000's shell script:
# USAGE
# yes_or_no "title" "text" height width ["yes text"] ["no text"]
# INPUTS
# $LINE = (y/n) - If we should use line-based input style(read)
# $_DEFAULT = (optional) - The default value for read
yes_or_no() {
if [ "$LINE" == "y" ]; then
echo -e "\e[1m$1\e[0m"
echo '$2' | fold -w $4 -s
while read -e -n 1 -i "$_DEFAULT" -p "Y for ${5:-Yes}, N for ${6:-No}[Y/N]" _yesno; do
case $_yesno in
[yY]|1)
return 0
;;
[nN]|0)
return 1
;;
*)
echo -e "\e[1;31mINVALID INPUT\x21\e[0m"
esac
else whiptail --title "${1:-Huh?}" --yesno "${2:-Are you sure?}" ${3:-10} ${4:-80}\
--yes-button "${5:-Yes}" --no-button "$6{:-No}"; return $?
fi
}
# USAGE
# user_input var_name ["title"] ["prompt"] [height] [width]
# INPUTS
# $LINE = (y/n) - If we should use line-based input style(read)
# $_DEFAULT = (optional) - The default value for read; defaults to nothing.
user_input(){
if [ "$LINE" == "y" ]; then
echo -e "\e[1m${2:-Please Enter:}\e[0m" | fold -w ${4:-80} -s
read -e -i "${_DEFAULT}" -p "${3:->}" $1
else
eval "$1"=$(whiptail --title "$2" --inputbox "$3" 3>&1 1>&2 2>&3)
fi
}
シェルのリダイレクション
[編集]シェルでは、リダイレクションにはファイルI/Oを使用しています。標準入力、標準出力、標準エラーなどの標準ストリームをリダイレクトするのは、パイプを通じて他のプログラムから入力を受け入れること、プログラムの出力をファイルに保存すること、/dev/nullにリダイレクトすることで、プログラムの出力を抑制することに使われます。
パイプ
[編集]シンボル一覧
[編集]シンボル | 説明 |
---|---|
! |
|
"…" |
|
# |
|
#! |
|
$ |
|
$"…" |
|
$# |
|
% | The modulus operator. Returns the remainder resulting from integer division. E.g. 5%2 = 1 |
& | Ampersand. Commonly used to start a command in the background. E.g. firefox & |
' | Single quote. Used to quote text literally. |
( | Open parenthesis. Used to denote the beginning of a subshell, among other things. |
) | Closing parenthesis. Used to denote the "EOF" of a subshell. |
* | Asterisk. Denotes multiplication. E.g. 5*2 = 10 |
+ | Plus. Denotes addition. E.g. 5+2 = 7 |
, | Comma. Used for separation. E.g. ls file{1,2,3} |
- | Hyphen. Denotes subtraction. E.g. 5-2 = 3 |
. | Full Stop. |
/ | Forward slash. Denotes integer division (e.g. 5/2=2) or part of a path (e.g. /home/user) |
: | Colon. |
; | Semicolon. Separates lines if no newline/EOL exists. E.g. echo hello; echo world |
< | Open angle bracket. Used for input redirection |
= | Equality sign. Used to assign variables and check equality |
> | Closing angle bracket. Used for output redirection. |
? | Question Mark. |
@ | At sign. Typically used as a variable containing all arguments passed to the environment as $@ |
[ | Open square bracket. Used as a more visually appealing alternative to test. E.g. if [ condition ] ; then etc |
\ | Backslash. Most commonly used for escaping. E.g. rm file\ with\ a\ bunch\ of\ spaces.txt |
] | Closing square bracket. Closes test enclosures |
^ | Caret. |
_ | Underscore. |
`…` |
|
{ | 開き中括弧。変数展開に使用する。例:変数var の値が "hello "のとき echo "${var}world" は "hello world"を出力する。 echo "$varworld" はバグの元になる。これは bashにvarworld 変数を期待させるからである。 |
| | Pipe. Used for redirecting input to output. Specifically, it takes the output of the command on the left hand side, runs the program on the right side, and then passes the contents of the first command's output to the second, as if it were being typed from a keyboard. 'ls -l | grep Desk' is equivalent to running "grep Desk", and then manually typing what ls -l would have output. Every press of the return key would then trigger grep until ^D is pressed to pass the EOF. |
} | 閉じ中括弧 |
~ | チルダ。ホームディレクトリを参照するために使用する。ログインしている、"mrwhite"のホームディレクトリ/home/mrwhiteに移動するには、 cd ~ (または cd)。ログインしていないユーザーの場合は、'cd ~mrwhite' のように名前をいれる。 |
See also
[編集]Related Wikibooks
[編集]- A Quick Introduction to Unix often involves the Bash shell.
- Guide to Unix often involves the Bash shell.
- The Android/Terminal IDE uses Bash as its default command shell.
- Mac OS X Tiger/A Quick Look Under the Hood uses Bash as its default command shell.
- Shell Programming
- Bourne Shell Scripting - Bash is almost Bourne-Shell compatible.