Bashでプログラミングする方法:構文とツール
シェルはオペレーティングシステムのコマンドインタープリタです。Bashは私が最も好むシェルですが、あらゆるLinuxシェルは、ユーザーやシステム管理者が入力したコマンドを、OSが利用できる形式に解釈します。結果がシェルプログラムに返されると、それはSTDOUT(標準出力)に送られ、デフォルトではターミナルに表示されます。私が知る限り、すべてのシェルはプログラミング言語でもあります。
タブ補完、コマンドラインの履歴呼び出しと編集、エイリアスなどのショートカットといった機能はすべて、強力なシェルとしての価値に貢献しています。デフォルトのコマンドライン編集モードはEmacsですが、私が最も気に入っているBashの機能の一つは、これをViモードに変更できることです。これにより、すでに筋肉の記憶に組み込まれている編集コマンドを使用できます。
しかし、Bashを単なるシェルだと考えていると、その真の力の大半を見逃すことになります。私の3部構成のLinux自習コース(この連載記事のベースとなっています)を調査している際、20年以上Linuxを扱ってきた中で知らなかったBashの機能をいくつか発見しました。これらの新たな知識の一部は、プログラミング言語としての利用に関するものです。Bashは強力なプログラミング言語であり、コマンドラインおよびシェルスクリプトでの使用に完璧に設計されています。
この3部構成のシリーズでは、Bashをコマンドラインインターフェース(CLI)プログラミング言語として使用する方法を探ります。この第1回では、Bashを使ったシンプルなコマンドラインプログラミング、変数、制御演算子について見ていきます。続く記事では、Bashファイルの種類、実行フロー制御ロジックを提供する文字列・数値・その他の論理演算子、さまざまな種類のシェル展開、繰り返し処理を可能にするfor、while、untilループについて解説します。また、これらのツールの利用を簡素化・サポートするコマンドにも触れます。
シェルとは
シェルはオペレーティングシステムのコマンドインタープリタです。Bashは私が最も好むシェルですが、あらゆるLinuxシェルは、ユーザーやシスアドミンが入力したコマンドを、OSが利用できる形式に解釈します。結果がシェルプログラムに返されると、それはターミナルに表示されます。私が知る限り、すべてのシェルはプログラミング言語でもあります。
Bashは「Bourne Again Shell」の略称です。Bashシェルは、1977年にスティーブン・ボーンが書いた古いBourneシェルをベースにしています。他にも多くのシェルが利用可能ですが、私が最も頻繁に遭遇するのは以下の4つです:
- csh: C言語の構文を好むプログラマー向けのCシェル
- ksh: デビッド・コーンが書いたKornシェル。Unixユーザーに人気
- tcsh: 使いやすさを向上させたcshのバージョン
- zsh: 他の人気シェルの多くの機能を組み合わせたZシェル
すべてのシェルには、コアユーティリティが提供するコマンドを補完または置き換える組み込みコマンドがあります。シェルのmanページを開き、「BUILT-INS」セクションを見れば、そのシェルが提供するコマンドを確認できます。
各シェルには独自の個性と構文があります。人によって合う・合わないがあります。私はCシェル、Kornシェル、Zシェルを使ったことがありますが、やはりBashシェルが最も気に入っています。自分に最も合うものを使えばよいのですが、それにはいくつか試してみる必要があるかもしれません。幸い、シェルの変更は非常に簡単です。
これらすべてのシェルは、コマンドインタープリタであると同時にプログラミング言語でもあります。ここでは、Bashに不可欠なプログラミング構造とツールを簡単に紹介します。
プログラミング言語としてのBash
ほとんどのシスアドミンは、通常かなりシンプルで直截的なコマンドを発行するためにBashを使用してきました。しかしBashは、単一コマンドの入力にとどまりません。多くのシスアドミンは、一連のタスクを実行するシンプルなコマンドラインプログラムを作成しています。これらは時間と労力を節約する一般的なツールです。
CLIプログラムを書く際の私の目的は、時間と労力を節約すること、つまり「怠惰なシスアドミン」になることです。CLIプログラムは、複数のコマンドを特定の順序で列挙し、それらを順次実行することで、あるコマンドの進行を見守り、終了したら次のコマンドをタイプするといった作業を不要にします。各コマンドの進行を常に監視することなく、別の作業に移ることができます。
「プログラム」とは何か?
フリーオンライン計算機辞典(FOLDOC)では、プログラムを「物理デバイス上で実行されるのではなく、コンピュータによって実行される命令」と定義しています。プリンストン大学のWordNetでは「コンピュータが解釈し実行できる一連の命令……」と定義しています。Wikipediaにもコンピュータプログラムについての優れた項目があります。
したがって、プログラムは、特定の関連タスクを実行する1つ以上の命令で構成されます。コンピュータプログラムの命令は、プログラムステートメントとも呼ばれます。シスアドミンにとって、プログラムは通常、シェルコマンドのシーケンスです。Linuxで利用可能なすべてのシェル(少なくとも私が知る限り)は、少なくとも基本的なプログラミング機能を備えており、ほとんどのLinuxディストリビューションでデフォルトシェルとなっているBashも例外ではありません。
このシリーズではBashを使用します(非常に普及しているため)が、別のシェルを使う場合でも、一般的なプログラミングの概念は同じです。ただし、構造や構文は多少異なる場合があります。一部のシェルは他のシェルがサポートしていない機能をサポートしていることがありますが、すべて何らかのプログラミング機能を提供しています。シェルプログラムはファイルに保存して繰り返し使用できますし、必要に応じてコマンドライン上で即席で作成することもできます。
シンプルなCLIプログラム
最もシンプルなコマンドラインプログラムは、Enterキーが押される前にコマンドラインに入力される、1つまたは2つの連続したプログラムステートメントです。これらは関連している場合も、していない場合もあります。プログラムの2番目のステートメント(存在する場合)は、最初のステートメントのアクションに依存する可能性がありますが、必ずしも依存する必要はありません。
また、1つの構文上の句読点について明確にしておく必要があります。コマンドラインで単一のコマンドを入力する場合、Enterキーを押すことで、暗黙のセミコロン(;)によってコマンドが終了します。CLIシェルプログラムとしてコマンドラインに1行で入力する場合、各ステートメントを終了し、次のステートメントと区切るために、セミコロンを明示的に使用する必要があります。CLIシェルプログラムの最後のステートメントでは、明示的または暗黙的なセミコロンのどちらを使用しても構いません。
基本的な構文
以下の例でこの構文を明確にします。このプログラムは、明示的な終了子を持つ単一のコマンドで構成されています:
[student@studentvm1 ~]$ echo "Hello world." ;
Hello world.
大したプログラムには見えないかもしれませんが、これは私が新しいプログラミング言語を学ぶたびに最初に遭遇するプログラムです。構文は言語によって多少異なりますが、結果は同じです。
この定番だが些細なプログラムを少し拡張してみましょう。私は他の実験も行っているため、あなたの結果は私のものと異なるでしょう。あなたは、GUIデスクトップ経由でアカウントに初めてログインした際にアカウントのホームディレクトリに作成されるデフォルトのディレクトリとファイルしか持っていないかもしれません。
[student@studentvm1 ~]$ echo "My home directory." ; ls ;
My home directory.
chapter25 TestFile1.Linux dmesg2.txt Downloads newfile.txt softlink1 testdir6
chapter26 TestFile1.mac dmesg3.txt file005 Pictures Templates testdir
TestFile1 Desktop dmesg.txt link3 Public testdir Videos
TestFile1.dos dmesg1.txt Documents Music random.txt testdir1
これで少し意味が通じます。結果は関連していますが、個々のプログラムステートメントは互いに独立しています。セミコロンの前後にスペースを入れるとコードが読みやすくなるので、私はそのようにしています。今度は、末尾に明示的なセミコロンを付けずに同じCLIプログラムを試してみましょう:
[student@studentvm1 ~]$ echo "My home directory." ; ls
出力に違いはありません。
変数について
すべてのプログラミング言語と同様に、Bashシェルも変数を扱えます。変数は、何らかの値を含むメモリ上の特定の場所を参照するシンボリックな名前です。変数の値は変更可能、つまり「変数(variable)」です。
Bashは、C言語や関連言語のように変数を型付けしません(整数、浮動小数点、文字列型として定義しない)。Bashでは、すべての変数は文字列です。整数である文字列は整数演算に使用できますが、これはBashが実行できる唯一の数学演算です。より複雑な数学が必要な場合は、CLIプログラムやスクリプトでbcコマンドを使用できます。
変数には値が代入され、CLIプログラムやスクリプト内でその値を参照するために使用されます。変数の値を設定するには、$記号を付けずに変数名を使用します。代入VAR=10は、変数VARの値を10に設定します。変数の値を表示するには、echo $VARというステートメントを使用します。まずはテキスト(非数値)変数から始めましょう。
Bashの変数は、unsetされるまでシェル環境の一部となります。
まだ値が代入されていない変数の初期値を確認してみましょう。nullであるはずです。その後、変数に値を代入し、それを表示して値を確認します。これらすべてを1つのCLIプログラムで実行できます:
[student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ;
Hello World
[student@studentvm1 ~]$
注: 変数代入の構文は非常に厳格です。代入文のイコール(=)の両側にスペースを入れてはいけません。
空行は、MyVarの初期値がnullであることを示しています。変数の値を変更することと設定することは同じ方法で行います。この例では、元の値と新しい値の両方が表示されています。
前述の通り、Bashは整数演算が可能です。これは、配列内の要素の位置を参照する計算や、簡単な数学問題を解くのに便利です。しかし、科学計算や、財務計算のように小数点を必要とする用途には適していません。それらの計算には、より適したツールがあります。
簡単な計算例を示します:
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))"
Result = 63
浮動小数点数になる数学演算を実行するとどうなるでしょうか?
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))"
Result = 0
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var2/Var1))"
Result = 1
[student@studentvm1 ~]$
結果は最も近い整数になります。計算がechoステートメントの一部として実行されたことに注目してください。Bashの演算子優先順位のルールにより、計算は囲んでいるechoコマンドよりも先に実行されます。詳細については、Bashのmanページで「precedence」を検索してください。
制御演算子
シェル制御演算子は、興味深いコマンドラインプログラムを簡単に作成するための構文上の演算子の一つです。最もシンプルなCLIプログラムは、単に複数のコマンドをコマンドライン上でシーケンスとして連ねるだけです:
command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;
エラーが発生しなければ、これらのコマンドはすべて問題なく実行されます。しかし、エラーが発生したらどうなるでしょうか?組み込みの&&と||というBash制御演算子を使えば、エラーを予測して対処できます。これら2つの制御演算子はフロー制御を提供し、コード実行の順序を変更できるようにします。セミコロンも改行文字も、Bash制御演算子と見なされます。
&&演算子は単純に、「command1が成功したらcommand2を実行せよ。command1が何らかの理由で失敗したら、command2をスキップせよ」という意味です。その構文は次のようになります:
command1 && command2
新しいディレクトリを作成し、成功したらそれをカレントディレクトリ(PWD)にするコマンドを見てみましょう。ホームディレクトリ(~)がPWDであることを確認してください。まず、アクセス権限のない/rootディレクトリで試してみます:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ && cd $Dir
mkdir: cannot create directory '/root/testdir/': Permission denied
[student@studentvm1 ~]$
エラーはmkdirコマンドから出力されました。ディレクトリの作成が失敗したため、ファイルが作成できなかったというエラーは出ません。&&制御演算子が非ゼロのリターンコードを検知したため、touchコマンドはスキップされました。&&制御演算子を使用することで、ディレクトリ作成のエラーがあったためにtouchコマンドが実行されないようになっています。このタイプのコマンドラインプログラムフロー制御は、エラーが連鎖して事態を悪化させるのを防止できます。さて、少し複雑にしてみましょう。
||制御演算子を使用すると、最初のプログラムステートメントがゼロより大きいコードを返したときに実行される別のプログラムステートメントを追加できます。基本構文は次のようになります:
command1 || command2
これは、「command1が失敗したらcommand2を実行せよ」と読みます。つまり、command1が成功したらcommand2はスキップされます。新しいディレクトリの作成を試してみましょう:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$
これはまさに期待通りの動作です。新しいディレクトリを作成できなかったため、最初のコマンドが失敗し、その結果2番目のコマンドが実行されました。
この2つの演算子を組み合わせると、両方の利点が得られます。&&と||制御演算子を使用したフロー制御を含む制御演算子の構文は、一般的に次の形式を取ります:
preceding commands ; command1 && command2 || command3 ; following commands
これは次のように述べられます:「command1がリターンコード0で終了したらcommand2を実行し、そうでなければcommand3を実行せよ。」試してみましょう:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$
今度は、/rootディレクトリの代わりにホームディレクトリを使って最後のコマンドをもう一度試してみます。このディレクトリを作成する権限はあります:
[student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
[student@studentvm1 testdir]$
command1 && command2のような制御演算子の構文が機能するのは、すべてのコマンドがシェルにリターンコード(RC)を送り、それが正常に完了したか、実行中に何らかの障害が発生したかを示すためです。慣習により、RCがゼロ(0)は成功を、正の数は何らかの失敗を示します。シスアドミンが使用するツールの中には、失敗を単に1で返すものもありますが、多くは他のコードを使って発生した失敗の種類を示します。
Bashシェル変数$?には、最後に実行されたコマンドのRCが格納されます。このRCは、スクリプト、コマンドリスト内の次のコマンド、さらにはシスアドミンが直接確認することができます。簡単なコマンドを実行し、すぐにRCを確認してみましょう。RCは、それを確認する直前に実行された最後のコマンドのものになります。
[student@studentvm1 testdir]$ ll ; echo "RC = $?"
total 1264
drwxrwxr-x 2 student student 4096 Mar 2 08:21 chapter25
drwxrwxr-x 2 student student 4096 Mar 21 15:27 chapter26
-rwxr-xr-x 1 student student 92 Mar 20 15:53 TestFile1
<snip>
drwxrwxr-x. 2 student student 663552 Feb 21 14:12 testdir
drwxr-xr-x. 2 student student 4096 Dec 22 13:15 Videos
RC = 0
[student@studentvm1 testdir]$
この場合、RCはゼロであり、コマンドが正常に完了したことを意味します。今度は、非rootユーザーがアクセス権限のないrootのホームディレクトリで同じコマンドを試してみます:
[student@studentvm1 testdir]$ ll /root ; echo "RC = $?"
ls: cannot open directory '/root': Permission denied
RC = 2
[student@studentvm1 testdir]$
この場合、RCは2です。これは、ユーザーがアクセス権限のないディレクトリにアクセスしようとしたため、許可が拒否されたことを意味します。制御演算子はこれらのRCを利用して、プログラム実行の順序を変更できるようにしています。
まとめ
この記事では、Bashをプログラミング言語として捉え、その基本構文と基本的なツールを探りました。STDOUTへのデータ出力、変数の使用、制御演算子の使い方を示しました。このシリーズの次回は、命令実行のフローを制御する数多くのBash論理演算子について見ていきます。
-
Scoopを使ってWindowsのソフトウェアを簡単にインストール・更新する方法
Scoopは、Windows向けプログラムをコマンドラインからシンプルにインストールできるツールです。前回のガイドでは、Scoopのインストール方法とコマンドラインの基本的な使い方をご紹介しました。本記事では、Scoopの主な機能を概観しながら、Windowsでのソフトウェア管理がどれほど快適になるのかを見ていきます。通常のソフトウェア導入手順は、おそらく次のような流れではないでしょうか。ダウンロードサイトにアクセスし、インストーラーをダウンロードして、画面の指示に従ってクリックを進めていく。その間、紛り込む広告に惑わされないよう注意しなければなりません。しかも、プログラムを更新する段階になる
-
Adobe Readerやその他のツールでPDFファイルを結合する方法【Windows・Mac・スマホ対応】
PDFファイルは、プログラムのバージョン、オペレーティングシステム、そしてファイルを開くプラットフォームに影響されないドキュメント形式です。MS Wordなどのエディタファイルと異なり、PDFはデバイスが変わっても書式や表示が崩れることがなく、あらゆるPCやモバイル端末で同じ見た目を保ち、意図した通りに印刷できることから、現在のような人気を獲得しました。本記事では、Adobe Readerをはじめとする各種ツールを使用して、PDFファイルを結合する方法をプラットフォーム別に詳しく解説します。 ここでは、Windows、macOS、Android、iOSの各デバイスでPDFファイルを結合する複数