アプリ開発者のための
UNIX/Linux入門

Yusuke Shinyama, Apr. 2024

概要: UNIXは現在のほとんどのサーバおよび macOSの基盤となるオペレーティングシステムであり、 UNIXのスキルは Webアプリケーション開発やシステム管理、ネットワーク管理などに欠かせないものとなっている。 この記事では、おもにアプリ開発者の役に立ちそうな UNIX/Linux に関する基礎知識を紹介する。 最終的な目標はシェルスクリプトを使った自動処理が書けることである。 記事は3部に分かれており、 1. UNIXの基本的な概念 (ファイル、プロセス、標準入出力)、 2. シェルおよび各種コマンドの使い方、 および 3. 環境変数およびシェルスクリプトの基礎 となっている。

注意: まだ作成中の資料です。内容は変わる可能性があります。

目次

  1. UNIXとは
  2. ディスクとファイル
  3. プロセス
  4. 第1部のまとめ
  5. シェル
  6. ファイル操作コマンド

第1部 - UNIXの基本的な概念

1. UNIXとは

UNIX v7 Windows Linux FreeBSD NetBSD OpenBSD Solaris HP-UX macOS 模倣 模倣

1.1. オペレーティングシステム (OS) とは?

オペレーティングシステム (OS) アプリ アプリ アプリ

1.2. Linux「ディストリビューション」とは?

Linuxの世界では、実際には "RedHat", "Ubuntu", "Debian" といった 複数の異なる "Linux" が存在している。これらは厳密には Linux ではなく、 異なる Linux の ディストリビューション と呼ばれている

Windows や macOS の場合は、OSの核となる部分 (カーネル) および付属ツールなどをまとめて「OS」と呼んでいるが、Linux の場合は意味が異なる。 本来 "Linux" といえばカーネルのみをさしており 「Linuxだけ」で OSとしては使うことはない。 Linux カーネルにさまざまな部品 (ファイルシステム、パッケージ管理など) を追加して OS として 使えるようにしたものが「ディストリビューション」である。

Linuxの各ディストリビューションのおもな違いはファイルシステム構造の差異 (設定ファイルの違いなど) および、システム管理の方法の差異である。 とくに、パッケージ管理方式は各ディストリビューションによって大きく違っている。 しかしこの記事では UNIXのシステム管理の方法までは扱わないので、 内容の大部分はどのディストリビューションにもあてはまる。

2. ディスクとファイル

こんにち、ファイルという単位を使ってディスクにデータを保存することは常識となっている。

UNIXにおいては、ファイルはただの「決まった長さをもつバイト列 (01の羅列)」 でしかなく、中身はなんでもよい。したがって、UNIXではあらゆるデータはファイルとして保存される:

ファイル1 ファイル2 010010111010001... 11110111011100111111...

(さらにUNIXでは「すべてはファイルである」という考え方をおしすすめているので、 入出力装置である画面 (端末) やメモリすらもファイルとして扱われている。)

それぞれのファイルはファイル名によって区別する。 UNIXでは、各ファイルには名前のほかに、以下のようなメタデータが付与されている:

2.1. ディレクトリとは

多数のファイルは フォルダ (UNIXではディレクトリ directory と呼ぶ) を使って整理する。

UNIXでは、すべてのファイル・フォルダはひとつの巨大な木構造 (tree) をなしている、と考える。

ディスク A B C B E C K H K X /

2.2. パス名とは

同じ名前のファイル・フォルダが複数存在している場合、 ファイルの正確な位置は、パス名 (path) で表すことができる。 パス名は、ディスク上におけるファイルの「住所」である。

演習 1. パス名の練習
  1. ディレクトリ X のパス名を書け。
  2. ファイル H のパス名を書け。
  3. ファイル C のパス名を 2つ書け。

UNIXにおける「お約束」パス名

UNIX では、いくつかのパス名は 「お約束」として決められている。

演習 2. やってみよう

2.3. カレント・ディレクトリ (カレント・フォルダ) とは

A B C B E C K H K X カレント・ディレクトリ

2.4. 絶対パス名と相対パス名

実は「パス名」と呼ばれているものには 2つの種類がある。 上で説明した「パス名」は「絶対パス名」のことであった。

A B C B E C K H K X カレント・ディレクトリ 目的のディレクトリ

つまり、上のディレクトリ X の位置は、次の2通りで表せる:

2.5. 相対パス名の表し方

演習 3. 相対パスの練習

カレント・ディレクトリが E のとき…

A B C B E C K H K X カレント・ディレクトリ
  1. ディレクトリ A への相対パス名は?
  2. ファイル H への相対パス名は?
  3. ファイル K への相対パス名は? (2つある)
演習 4. やってみよう

3. プロセス

ファイルシステム上で「実行可能 (executable)」フラグ (x) がついている ファイルはプログラムとして実行可能である。 通常、ここには機械語で書かれた命令列が記録されている:

-rwxr-xr-x  1 root  wheel  154352 Mar 21 15:13 /bin/ls

実行されたプログラムは、OS上で プロセス (process) として走り続ける。 UNIXはマルチタスクOSなので、通常は複数のプロセスが並列に実行される。

カーネル プロセス プロセス プロセス

(実際の CPU は一度にひとつの プログラムしか実行できないため、各プロセスは高速に切り替えられ 少しずつ (10ms程度) 実行される (時分割処理)。 これらプロセス切り替え処理は、OS の カーネル (kernel) という部分がおこなう。)

Slack VSCode Chrome
PC上のプロセス(アプリ)
Node nginx Spring (Java)
Linuxサーバ上のプロセス

ただしPC上のプロセス(アプリ)が入出力装置としてもっぱらGUIを使うのに対し、 サーバ上のプロセスは入出力装置としてネットワークを使う。 この違いを除けば、PCでもサーバでも UNIXプロセスが動いているという点は同じである。

UNIXプロセスの特徴

(現代のLinuxでは init のかわりに systemd、 macOSでは launchd が使われている。)

演習 5. やってみよう

プロセスとコンテナ

最近では、サーバ上では生身のプロセスを直接動かすのではなく、 Dockerなどの「コンテナ」を実行することが多い。

カーネル コンテナ コンテナ プロセス プロセス プロセス

3.1. シグナル

UNIX のプロセスには、シグナル (signal) を送って制御することができる。

プロセス シグナル

シグナルにはいくつかの種類がある:

演習 6. やってみよう

3.2. 標準入力・標準出力・標準エラー出力

現代のUNIXはさまざまな入出力装置をサポートしているが、 プロセスが入出力装置に直接アクセスすることはほとんどない。 ほぼすべて OS (カーネル) を介している。

OS プロセス ディスク ネットワーク 端末

UNIX の各プロセスは、つねに 「標準入力 (stdin)」 「標準出力 (stdout)」 「標準エラー出力 (stderr)」 という 3つの入出力装置にアクセス可能である。 これらはプロセスが画面に文字を表示したり、 キーボードから文字を入力するために使用する。 これらをまとめて 標準入出力 (Standard I/O)」と呼ぶ。

端末とは?

端末 (terminal, TTY) とは、コンピュータと接続して文字情報をやりとりする機器である。 もともとはタイプライタのような物理的な機械だったが、現在ではGUIにより 仮想的にエミュレートされるアプリになっている。

IBM 2741 Communications Terminal   Terminal icon2
物理的な端末と、現代の端末エミュレータ

標準出力 (および標準エラー出力) は、プロセスからの出力を表示するのに使われる:

$ ./hello
hello, world.

いっぽう標準入力は、ユーザが文字を入力するのに使われる:

$ ./greet
your name?
euske
greetings, euske

UNIXにおける標準入出力の最大の特徴は、これが切り替え可能だということである。

通常の状態では、プロセスの 標準入力・標準出力・標準エラー出力は、 どれも端末に接続されている。

プロセス 標準入力 標準出力 標準エラー出力 端末 端末 端末

(後述する) シェルの機能を使うと、 標準出力を端末ではなくファイルに切り替える (リダイレクトする) ことができる:

$ ./hello > output.txt
(テキストファイル output.txt が生成される)
hello 標準入力 標準出力 標準エラー出力 端末 output.txt 端末
注意: UNIX は無愛想な OS なので、 > の出力先として うっかり存在するファイル名を指定してしまうと、 そのファイルは何の警告もなく上書きされ、 空のファイルされる。

また、標準入力を端末ではなくファイルにリダイレクトすることも 可能である:

(テキストファイル input.txt を作成する)
$ ./greet < input.txt
your name?
greetings, test
greet 標準入力 標準出力 標準エラー出力 input.txt 端末 端末

さらに、UNIX には「パイプ (pipe)」という機能がある。 これを使うと 「あるプロセスの標準出力を、別のプロセスの標準入力に」 リダイレクトすることができる:

$ ./hello | ./greet
your name?
greetings, hello, world.
hello 標準入力 標準出力 標準エラー出力 端末 パイプ 端末 greet 標準入力 標準出力 標準エラー出力 端末 端末

パイプによる複数プロセスの接続は UNIX (シェル) の特徴的な機能のひとつである。 これをうまく使うと、複雑な処理をいくつかのコマンドの組み合わせによって 実現することができる。

(ls コマンドの出力を検索し、さらにそれをソートして最初の10行を表示する)
$ ls -l | grep euske | sort | head -n10
注意: <, >, | による 標準入力・標準出力の切り替えは、 プロセスを起動する瞬間にしか指定できない。 いちどプロセスが起動してしまうと、あとから 切り替えることはできないので注意。
演習 7. やってみよう

サーバにおける標準入出力

標準入出力は UNIXの入出力装置のうちもっとも基本的なもので、 どんなプロセスでも使用可能である。 PCの場合、これはデフォルトでは端末エミュレータアプリだが、 サーバには通常端末が存在しないので、サーバ上のプロセスの標準出力は、 ふつうはログファイルか、ネットワークを介した syslog などのログ収集プロセス、 あるいは (AWSのようなクラウド環境の場合) CloudWatch などのログ収集サービスに 接続されている。

OS プロセス ディスク ネットワーク 端末 標準出力
PC上のプロセス
OS プロセス ディスク ネットワーク (syslog等) 端末 標準出力
サーバ上のプロセス

第1部のまとめ

(以下の穴埋め問題の中には透明なテキストが書かれており、コピー・ペーストすれば正解が見れるようになっている)

第2部 - シェルの使い方

5. シェルとは

UNIX を使ううえで中心的な役割を果たしているのが シェル (shell) と呼ばれるプログラムである。 シェルは複数のプロセス全体を統括する殻 (shell) となる、 以下のような機能をもつ:

UNIX 上で「コマンド (command)」と呼ばれるものは、 実はほとんどシェルによって起動されるプロセスである。 通常の PC では、アプリ (これもプロセス) は一度起動したら 長時間走らせておくのが普通だが、たいていのコマンドは 数ミリ秒〜数秒しか生存しない。 UNIX は「湯水のようにプロセスを消費する」OSである。

とはいえ、UNIX的にみれば、シェルも他のコマンドと同じく、ひとつのプロセスにすぎない。 現在では sh, csh, bash, zsh など いろいろなシェルが開発されている。 ほとんどの場合、シェルは以下の動作を繰り返しているだけである:

  1. 端末 (標準入力) からコマンド文字列が入力されるのを待つ。
  2. 文字列を解析し、スペースで区切られた引数のリストを作成する (引数展開)。
  3. 必要に応じて標準入力・標準出力・標準エラー出力を切り替えて、子プロセスを起動する。
  4. 子プロセスが終了するのを待ち、終了状態を受けとる。
  5. 1. に戻る。

5.1. コマンド引数の展開

先に述べたように、UNIX コマンドのほとんどは実はプロセスであり (例外も存在する)、 シェルの主な機能はコマンド文字列を解析し、プロセスを起動することである。 たとえば

$ ls -l /etc
という行は、以下の行と同じである:
$ /bin/ls -l /etc

この行が入力されたとき、シェルは以下のことをおこなう:

  1. /bin/ls というプログラムを子プロセスとして起動する。
  2. このとき、各引数を文字列としてプロセスに渡す:
  3. 子プロセスが終了するまで待つ。

ここで注意したいのは、引数である 「-l」や「/etc」をどのように利用するかは 各コマンド次第ということである。 すべてのコマンドにおいて 「-l」がオプションであると決まっているわけではないし 「/etc」がパス名として解釈されるとも限らない。 この意味で、UNIX の使い方を学ぶことは API の使い方を学ぶのに似ている。 個々の関数・メソッドの引数がどのような意味をもつのか学習し、 それらを組み合わせて必要な処理を実現する。

注意: もし実際に 「-l」という名前のディレクトリがあったとすると、 そのままでは ls のパス名として指定できない。 だが ls ./-l というトリックを使えば指定できる。)

たとえば echo というコマンドは、シェルから与えらえた引数をただそのまま 表示するだけのコマンドである:

$ echo abc 1234
abc 1234
$ echo -l /etc
-l /etc

一般にシェルの引数は「 」(スペース) で 区切られるが、"〜" で囲むことにより スペースが入っている文字列を「ひとつの引数」として認識させることができる:

$ ls -l /etc
…
$ ls "-l /etc"
ls: invalid option -- ' '
$ "ls -l" /etc
ls -l: command not found
演習 8. シェルの引数展開

以下のコマンドラインの引数を 0番目からすべて挙げよ:

  1. ls /etc /bin
  2. ls "/etc /bin"
  3. ls "/etc" "/bin"
  4. ls "" "/bin"
  5. "ls /etc /bin"

5.2. ファイル名の展開

シェルでは、ディレクトリ上に複数のファイルがあるとき、 それらのファイル名を複数の引数として展開する機能がある (ファイル名展開, filename expansion)。

たとえば、カレント・ディレクトリ内に a, bb, ccc というファイルがあるとき…

$ ls
a   bb   ccc
以下の * (ワイルドカード) を指定すると
$ cat *
以下の引数を与えたのと同じである:
$ cat a bb ccc

ワイルドカードには、パターンを指定することもできる。 たとえば /etc 内に多くの「.conf」で終わるファイルがあるとき…

$ ls -l | grep .conf
-rw-r--r--    1 root     root          5613 Jun 18  2020 ca-certificates.conf
drwxr-xr-x    2 root     root           520 Jul  5  2020 conf.d
-rw-r--r--    1 root     root          1102 Jul  6  2020 dhcpcd.conf
…
以下のパターンは
$ cat /etc/*.conf
以下のような引数に展開される:
$ cat /etc/ca-certificates.conf /etc/dhcpcd.conf

繰り返すが、ここで展開されたパス名をどのように扱うかは 各コマンド次第である。 シェルがファイル名展開したからといって、 各コマンドがこれらをファイル名として扱う保証はない。

ここで、echo コマンドは シェルが各引数をどのように展開したかを表示するのに使える:

$ echo /etc/*.conf
/etc/ca-certificates.conf /etc/dhcpcd.conf …
演習 9. ファイル名展開

/etc 以下に次のようなファイルがあるとする:

カレント・ディレクトリも /etc であるとして、 以下のコマンドラインの出力を答えよ:
  1. echo *
  2. echo *.conf
  3. echo *.conf /etc/*
  4. echo /etc/issue*
  5. echo "*"

5.3. コマンド出力の展開

さらにシェルは、あるコマンドが (標準出力に) 出力した文字列を まるごと引数として展開することができる。

たとえば、ls コマンドが以下のような出力をしたとする:

$ ls
a   bb   ccc

このコマンドの出力は a, bb, ccc という 3つの引数に展開される:

$ echo $(ls)
a bb ccc

ここでコマンドの出力からは、余分な空白が除かれていることに注意。 これは通常のコマンドライン引数で

$ echo a   bb   ccc
a bb ccc
とタイプしたのと同じことである。 標準出力全体をひとつの文字列として扱うには、 コマンド出力の展開部分を "〜" で囲めばよい:
$ echo "$(ls)"
a
bb
ccc

6. ファイル操作コマンド

シェル上でファイル操作を行うためのおもなコマンドは以下のとおり:

ls
指定したディレクトリに含まれるファイル一覧を表示する。
$ ls(カレント・ディレクトリのファイル一覧を表示)
$ ls /etc(ディレクトリ /etc のファイル一覧を表示)
cd (Change Directory)
カレント・ディレクトリを変更する。
$ cd /etc
(カレント・ディレクトリを /etc に変更)
$ cd
(カレント・ディレクトリを自分のホームディレクトリに変更)
pwd (Print Working Directory)
カレント・ディレクトリのパス名を表示する。
$ pwd
(現在のパス名を表示)
catless
ファイルの内容を表示する。 もともと cat は 複数のファイルを連結 (conCATenate) するためのコマンドだった。
$ less /etc/services
(/etc/services の内容を表示)
$ cat /etc/hosts /etc/services
(/etc/hosts の内容と /etc/services の内容を連結して表示)
mkdir (MaKe DIRectory)
空のディレクトリを新規作成する。
$ mkdir foo
(カレント・ディレクト下に foo を作成)
cp (CoPy)
指定したファイルを宛先パス名に複製する。
$ cp a.txt b.txt
(a.txt を新しい b.txt という名前で複製する)
$ cp a.txt dest/
(a.txt を dest/ ディレクトリ内に同じ名前で複製する)
$ cp a.txt b.txt c.txt dest/
(a.txt, b.txt, c.txt の各ファイルを dest/ 内に同じ名前で複製する)
$ cp *.txt dest/
(〜.txt で終わるすべてのファイルを dest/ 内に同じ名前で複製する)

cp で複製できるのは、通常はファイルのみである。 あるディレクトリ内のファイルを (ディレクトリごと) 複製したい場合には -r オプションを使う:

$ cp -r foo dest/
(foo ディレクトリ内とその中のファイルをすべて dest/ 内に複製する)
注意: UNIX は無愛想な OS なので、 コピー先としてうっかり存在するファイル名を指定してしまうと、 そのファイルは何の警告もなく上書きされる。 これを防ぐためには -i オプションを使う:
$ cp -i a.txt b.txt
(a.txt を新しい b.txt という名前で複製するが、
 すでに同名のファイルがある場合は確認する)
mv (MoVe)
指定したファイルを宛先パス名に変更あるいは移動する。
$ mv a.txt b.txt
(a.txt を新しい b.txt という名前に変更する)
$ mv a.txt dest/
(a.txt を dest/ ディレクトリ内に移動する)
$ mv a.txt b.txt c.txt dest/
(a.txt, b.txt, c.txt の各ファイルを dest/ 内に移動する)
$ mv *.txt dest/
(〜.txt で終わるすべてのファイルを dest/ 内に移動する)
注意: UNIX は無愛想な OS なので、 移動先としてうっかり存在するファイル名を指定してしまうと、 そのファイルは何の警告もなく上書きされる。 これを防ぐためには -i オプションを使う:
$ mv -i a.txt b.txt
(a.txt を新しい b.txt という名前に変更するが、
 すでに同名のファイルがある場合は確認する)
rm (ReMove)
指定したファイルを削除する。
$ rm a.txt
(ファイル a.txt を削除する)
$ rm *.txt
(〜.txt で終わるすべてのファイルを削除する)

rm で削除できるのは、通常はファイルのみである。 あるディレクトリ内のファイルを (ディレクトリごと) 削除したい場合には -r オプションを使う:

$ rm -r dest/
(dest/ ディレクトリ内とその中のファイルをすべて削除する)
注意: UNIX は無愛想な OS なので、 rm コマンドは削除が成功しても何も表示しない。 削除するファイルをひとつずつ確認するためには -i オプションを使う:
$ rm -i *.txt
(〜.txt で終わる各ファイルを yes/no で確認しながら削除する)
du (DiskUse)
指定したディレクトリのファイル使用量を表示する。
$ du ~
(ホームディレクトリの使用量を各ディレクトリごとに表示する)
$ du -s .
(カレントディレクトリの総使用量のみを表示する)

パス名の指定方法に注意

cp, mv, rm などのコマンドでは 「-」で始まる引数はオプションとみなされる。 もし実際に "-i" というファイルを複製したい場合、 cp -i a.txt などとやってもうまくいかない:

$ cp -i a.txt
cp: missing destination file operand after 'a'
(エラーが出て実行できない)

このような場合には、2つの方法がある:

  1. 引数が「-」で始まらなけれさえすればよいので、 -i がカレント・ディレクトリにあることを利用して、 以下のようにする:
    $ cp ./-i a.txt
    (カレント・ディレクトリの -i というファイルがコピーされる)
    
  2. 多くの UNIX 標準コマンドは、引数に "--" が表れると、 それ以後の引数をオプションとして解釈することをやめ、 ファイル名として解釈するように実装されている。 このことを利用して:
    $ cp -- -i a.txt
    (-i はオプションでなく実際のファイル名として解釈される)
    

Yusuke Shinyama