「Transwiki:Bash Shell Scripting」の版間の差分

出典: フリー教科書『ウィキブックス(Wikibooks)』
削除された内容 追加された内容
Fu7mu4 (トーク | 投稿記録)
Fu7mu4 (トーク | 投稿記録)
729 行 729 行
===環境変数===
===環境変数===


私達が今までみてきたように、プログラムが呼ばれたとき、そのプログラムはコマンドライン上で明確にリストアップされた引数のリストを受けとります。今まで、私達が説明してこなかったものに、プログラムが受けとる名前と値のペアのリストからなる環境変数があります。この環境変数はプログラミング言語毎にアクセス方法が異なります。プログラミング言語Cのプログラムは<tt>getenv("<var>variable_name</var>")</tt>を使用します。(<tt>main</tt>の第三引数として受け取ります)、プログラミング言語perlのプログラムは<tt>$ENV{'<var>variable_name</var>'}</tt>を使います。またプログラミング言語Javaの場合は<tt>System.getenv().get("<var>variable_name</var>")</tt>を使います。
We have already seen that, when a program is called, it receives a list of arguments that are explicitly listed on the command line. What we haven't mentioned is that it also receives a list of name-value pairs called "environment variables". Different programming languages offer different ways for a program to access an environment variable; C programs can use <tt>getenv("<var>variable_name</var>")</tt> (and/or accept them as a third argument to <tt>main</tt>), Perl programs can use <tt>$ENV{'<var>variable_name</var>'}</tt>, Java programs can use <tt>System.getenv().get("<var>variable_name</var>")</tt>, and so forth.


Bashの場合、環境変数は単純に通常のBash変数となります。例えば、次のスクリプトは、<tt>HOME</tt>環境変数の値を出力します。
In Bash, environment variables are simply made into regular Bash variables. So, for example, the following script prints out the value of the <tt>HOME</tt> environment variable:


<source lang="bash">#!/bin/bash
<source lang="bash">#!/bin/bash
echo "$HOME"</source>
echo "$HOME"</source>


ただし逆は成り立ちません。通常のBash変数は自動的に環境変数になりません。次を例を確認してください。
The reverse, however, is not true: regular Bash variables are not automatically made into environment variables. So, for example, this script:


<source lang="bash">#!/bin/bash
<source lang="bash">#!/bin/bash
742 行 742 行
bash -c 'echo $foo'</source>
bash -c 'echo $foo'</source>


will not print <tt>bar</tt>, because the variable <tt>foo</tt> is not passed into the <tt>bash</tt> command as an environment variable. (<tt>bash -c <var>script</var> <var>arguments&hellip;</var></tt> runs the one-line Bash script <tt><var>script</var></tt>.)
このスクリプトは<tt>bar</tt>を出力しません。というのも<tt>foo</tt>変数は<tt>bash</tt> コマンドに環境変数として渡されていないからです。(<tt>bash -c <var>script</var> <var>arguments&hellip;</var></tt> は、一行のBashスクリプト<tt><var>script</var></tt>です。)


To turn a regular Bash variable into an environment variable, we have to "export" it into the environment. The following script ''does'' print <tt>bar</tt>:
To turn a regular Bash variable into an environment variable, we have to "export" it into the environment. The following script ''does'' print <tt>bar</tt>:

2019年7月7日 (日) 08:21時点における版

現在、この本は Bashの初等レベルの知識のみを提供しています。より深く学ぶには、External Programs, External linksUsing man, info and help に移動してください。

イントロダクション

Bashとは?

BashUnix 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 --helpman 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

ここで diffcp はよく使用するユーティリティプログラムで、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.txtbar.txtbaz.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.txtbar.txtbaz.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.

Caution:

ファイル名がハイフン-で始まるファイルがある場合、ファイル名展開は予想外の結果を引き起しかねません。例えばあるディレクトリに、名前が-nのファイルと 名前がtmp.txtのファイルがあったとします。そこでcat *コマンドをファイル展開すると、cat -n tmp.txtになり、これをcatプログラムは-n をファイル名ではなくオプションだと解釈します。こうのようにする代りに、cat ./*コマンドまたは cat -- *コマンドを入力しましょう。これは cat ./-n ./tmp.txt または cat -- -n tmp.txtとなりこの問題を回避できます。

それでは実際のファイル名にアステリスクが含まれている場合、(例えば *.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"はディレクトリとその中に含むものを含めて削除します。(-rrecursiveつまり再帰を短くしたもので、このコマンドはディレクトリツリーを 再帰的に 削除します。) これらのオプションは実際にただの引数です。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
    構文は、最初にcommand1を実行し、それが実行に成功した場合、(いいかえれば、終了値が0だった場合) command2を実行します。

別の書き方をすると、上のものは次のものと同じです。:

#!/bin/bash

[[ -e source.txt ]] && cp source.txt destination.txt

ただしより直接的です。(そして自由度が高く、短いです)

一般的に、Bashは成功の終了値(0)を"真(true)"の意味とし、失敗の終了値(非ゼロ)を"偽(false)"の意味として扱います。例えば、ビルトインコマンドのtrue は常に "成功(succeeds)" し(0を返し)、ビルトインコマンドの false 常に "失敗(fails)"し (1を返し)ます。

Caution:

多くのプログラミング言語では、0を"失敗(false)" とし、非ゼロを "成功(true)"として扱います。一方Bashでは、(あとで説明する)演算表現の中でも真になります。しかしコマンドレベルでは真は逆になっています。終了値が0は"成功(successful)" または "真(true)" を意味し、終了値が非ゼロは"失敗(failure)" または "偽(false)"を意味します。


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.txtdestination.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.txtdestination.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_modeecho または : を設定することでコンパクトにできます。ここで、コロン:は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などに加えて $@$*$#も)を使用するとそれは、そのシェル関数を含むスクリプトの引数ではなく、そのシェル関数の呼び出し時の引数を参照します。スクリプト自体の引数が必要な場合、"$@"などを使って明示的にシェル関数の引数として渡す必要があります。(またシェル関数内でのshiftsetはそのシェル関数内の位置パラメータに影響します。スクリプト側の位置パラメータには影響しません。)

シェル関数呼び出しは、スクリプトやほとんどすべてのコマンドと同じように終了値を返します。終了値を明示的に指定するには、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:

もしあなたのスクリプトが与えられたコマンドを実行する前に現在ディレクトリを変更する必要がある場合、可能であればサブシェルはよいアイディアです。サブシェルを使用しない場合、スクリプトを読み間常に現在ディレクトリを追跡しなければなりません。(代替方法としては、ビルトインコマンドの pushdpopdがあり、類似した効果が得られます。)

サブシェル内の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です。)

To turn a regular Bash variable into an environment variable, we have to "export" it into the environment. The following script does print bar:

#!/bin/bash
export foo=bar
bash -c 'echo $foo'

Note that export doesn't just create an environment variable; it actually marks the Bash variable as an exported variable, and later assignments to the Bash variable will affect the environment variable as well. That effect is illustrated by this script:

#!/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'

The export command can also be used to remove a variable from an environment, by including the -n option; for example, export -n foo undoes the effect of export foo. And multiple variables can be exported or unexported in a single command, such as export foo bar or export -n foo bar.

It's important to note that environment variables are only ever passed into a command; they are never received back from a command. In this respect, they are similar to regular Bash variables and subshells. So, for example, this command:

#!/bin/bash
export foo=bar
bash -c 'foo=baz' # has no effect
echo "$foo" # print 'bar'

prints bar; the change to $foo inside the one-line script doesn't affect the process that invoked it. (However, it would affect any scripts that were called in turn by that script.)

If a given environment variable is desired for just one command, the syntax var=value command may be used, with the syntax of a variable assignment (or multiple variable assignments) preceding a command on the same line. (Note that, despite using the syntax of a variable assignment, this is very different from a normal Bash variable assignment, in that the variable is automatically exported into the environment, and in that it only exists for the one command. If you want avoid the confusion of similar syntax doing dissimilar things, you can use the common Unix utility env for the same effect. That utility also makes it possible to remove an environment variable for one command — or even to remove all environment variables for one command.) If $var already exists, and it's desired to include its actual value in the environment for just one command, that can be written as var="$var" command.

An aside: sometimes it's useful to put variable definitions — or function definitions — in one Bash script (say, header.sh) that can be called by another Bash script (say, main.sh). We can see that simply invoking that other Bash script, as ./header.sh or as bash ./header.sh, will not work: the variable definitions in header.sh would not be seen by main.sh, not even if we "exported" those definitions. (This is a common point of confusion: 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'.

範囲・スコープ

We have now seen some of the vagaries of variable scope in Bash. To summarize what we've seen so far:

  • Regular Bash variables are scoped to the shell that contains them, including any subshells in that shell.
    • They are not visible to any child processes (that is, to external programs).
    • If they are created inside a subshell, they are not visible to the parent shell.
    • If they are modified inside a subshell, those modifications are not visible to the parent shell.
    • This is also true of functions, which in many ways are similar to regular Bash variables.
  • Function-calls are not inherently run in subshells.
    • A variable modification within a function is generally visible to the code that calls the function.
  • Bash variables that are exported into the environment are scoped to the shell that contains them, including any subshells or child processes in that shell.
    • The export built-in command can be used to export a variable into the environment. (There are other ways as well, but this is the most common way.)
    • They differ from non-exported variables only in that they are visible to child processes. In particular, they are still not visible to parent shells or parent processes.
  • External Bash scripts, like other external programs, are run in child processes. The . or source built-in command can be used to run such a script internally, in which case it's not inherently run in a subshell.

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 or local 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.

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".

Tip:

A variable that is set inside a function (either via assignment, or via a for-loop or other built-in command) should be marked as "local" using the built-in command local, so as to avoid accidentally affecting code outside the function, unless it is specifically desired that the caller see the new value.

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).

シェル演算

Arithmetic expressions in Bash are closely modeled on those in C, so they are very similar to those in other C-derived languages, such as C++, Java, Perl, JavaScript, C#, and PHP. One major difference is that Bash only supports integer arithmetic (whole numbers), not floating-point arithmetic (decimals and fractions); something like 3 + 4 means what you'd expect (7), but something like 3.4 + 4.5 is a syntax error. Something like 13 / 5 is fine, but performs integer division, so evaluates to 2 rather than to 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 ) ))

Arithmetic for-loops

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 +=).

整数リテラル

An integer constant is expressed as an integer literal. We have already seen many of these; 34, for example, is an integer literal denoting the number 34. All of our examples have been decimal (base ten) integer literals, which is the default; but in fact, literals may be expressed in any base in the range 2–64, using the notation base#value (with the base itself being expressed in base-ten). For example, this:

echo $(( 12 ))        # use the default of base ten (decimal)
echo $(( 10#12 ))     # explicitly specify base ten (decimal)
echo $(( 2#1100 ))    # base two (binary)
echo $(( 8#14 ))      # base eight (octal)
echo $(( 16#C ))      # base sixteen (hexadecimal)
echo $(( 8 + 2#100 )) # eight in base ten (decimal), plus four in base two (binary)

will print 12 six times. (Note that this notation only affects how an integer literal is interpreted. The result of the arithmetic expansion is still expressed in base ten, regardless.)

For bases 11 through 36, the English letters A through Z are used for digit-values 10 through 35. This is not case-sensitive. For bases 37 through 64, however, it is specifically the lowercase English letters that are used for digit-values 10 through 35, with the uppercase letters being used for digit-values 36 through 61, the at-sign @ being used for digit-value 62, and the underscore _ being used for digit-value 63. For example, 64#@A3 denotes 256259 (62 × 642 + 36 × 64 + 3).

There are also two special notations: prefixing a literal with 0 indicates base-eight (octal), and prefixing it with 0x or 0X indicates base-sixteen (hexadecimal). For example, 030 is equivalent to 8#30, and 0x6F is equivalent to 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).

非整数の演算

As mentioned above, Bash shell arithmetic only supports integer arithmetic. However, external programs can often be used to obtain similar functionality for non-integer values. In particular, the common Unix utility bc is often used for this. The following command:

echo "$(echo '3.4 + 2.2' | bc)"

prints 5.6. Needless to say, since bc is not so tightly integrated with Bash as shell arithmetic is, it is not as convenient; for example, something like this:

# print the powers of two, from 1 to 512:
for (( i = 1 ; i < 1000 ; i *= 2 )) ; do
  echo $i
done

would, to support non-integers, become something like this:

# 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, as a shell, is actually a 'glue' language. It helps programs to cooperate with each other, and benefits from it. Always Search The Internet for what you want -- there are lots of command line utilities available.

whiptailの使用

Whiptail is a program that allows shell scripts to display dialog boxes to the user for informational purposes, or to get input from the user in a friendly way. Whiptail is included by default on Debian and various other GNU/Linux distributions.

From the GNU/Linux Dictionary: whiptail is a "dialog" replacement using newt instead of ncurses.
From its README: whiptail is designed to be drop-in compatible with dialog(1), but has fewer features: some dialog boxes are not implemented, such as tailbox, timebox, calendarbox, etc.

See Bash Shell Scripting/Whiptail.

AWKの使用

See AWK and man awk (man gawk).

sedの使用

See sed and man sed.

grepの使用

See grep and man grep.

Using man, info and help

These three programs are where you can find help or reference from. man displays roff manual pages, info displays texinfo documentations, while help displays builtin helps.

Appending --long-help, --help or --usage to a command-line program may also gives you the usage information. Possible synonyms include -H and -h.

Just try these out:

man --help
man man

info --help
man info
info info

help help

Pressing h in man and info's interfaces can also give you some direction.

入力/出力

ビルトインのread

From help 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 is great for both user inputs and reading standard inputs/piping.

An example of user input:

# 'readline'  prompt          default    variable name
read -e -p "Do this:" -i "destroy the ship" command

echo "$command"

Or even simpler:

pause() { read -n 1 -p "Press any key to continue..."; }

Hello-world level example of stdout operation:

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
}

シェルのリダイレクション

In shells, redirection is used for file I/O. The most common usage of is to redirect standard streams (stdin, stdout and stderr) to accept input from another program via piping, to save program output as a file, and to suppress program output by redirecting a stream to /dev/null.


パイプ

Index of symbols

Symbol Explanation
!
  • Logically negates the exit status of a pipeline. For example, if grep YES votes.txt returns 0, then ! grep YES votes.txt returns 1, but is otherwise equivalent.
  • Also supported by the [ ... ] builtin, and inside conditional expressions. For example, if [[ -e file.txt ]] is true, then [[ ! -e file.txt ]] is false.
  • Also supported in arithmetic expressions. For example, if $i is nonzero, then $(( ! i )) is 0.
  • See also #! below.
"…"
  • Quotes an argument (or part of an argument) so that it is not split by whitespace into multiple arguments, but without preventing parameter expansion and command substitution internally.
  • See also $"…" below.
#
  • Introduces a comment (which continues to the end of the line). For example, the command foo bar baz # bip is equivalent to the command foo bar baz, because the comment # bip is removed.
  • Inside an arithmetic expression, an integer literal of the form b#n is interpreted in base b. For example, 2#110110 is binary 110110, i.e. fifty-four.
  • See also #! below.
  • See also $# below.
#!
  • (Typically "shebang" when read aloud.) Used at the very beginning of an executable script to specify the interpreter that should be used to run it. For example, if the first line of script.pl is #!/usr/bin/perl, and script.pl has executable permissions, then ./script.pl is roughly equivalent to /usr/bin/perl ./script.pl.
  • The first line of a Bash script is generally either #!/bin/bash or #!/bin/sh. (The former is generally considered preferable.)
$
  • Introduces various types of expansions, notably parameter expansion (as in $var or ${var}), command substitution (as in $(command)), and arithmetic expansion (as in $((expression))).
$"…"
  • A variant of "…" (see above) that supports locale-specific translation. (Unless you're writing scripts for use in multiple languages, e.g. both English and French, you don't need to worry about this.)
$#
  • The number of positional parameters (arguments to a script or function). For example, if a script is invoked as script.sh a b c, then $# will be 3. Builtins that modify positional parameters, such as shift and set, affect $# as well.
% 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.
`…`
  • Triggers command substitution; equivalent to $(…), but is somewhat more error-prone.
{ Open curly brace. Used for specific variable expansion. E.g. (where var = "hello ") echo "${var}world" will print "hello world", echo "$varworld" will generate an error, expecting a variable called 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.
} Closing curly brace.
~ Tilde. Typically used to refer to the home directory. Logged in as "mrwhite", cd ~ (or just cd) would go to /home/mrwhite. Logged in as another user, the same effect could be achieved with 'cd ~mrwhite'.

See also

Related Wikibooks

External links

テンプレート:Alphabetical

  1. ^ Bash - GNU Project - Free Software Foundation”. テンプレート:ISO date/en閲覧。
  2. ^ http://www.sal.ksu.edu/faculty/tim/unix_sg/bash/math.html