Lesson 5

標準入出力

Lesson 5 Chapter 1
標準入出力

Lesson3でbashについて学んだ際に、Linuxにおける様々なコマンドを紹介しました。 紹介したコマンド一つ一つはとてもシンプルな機能であると感じられたのではないでしょうか? 本章では標準入出力という概念について学習を行います。 この概念を理解すると、個別ではシンプルなLinuxのコマンドを連結させ、より複雑な処理を記述できるようになります。

標準入出力とは

標準入出力について理解するにあたり、Lesson3で色々なコマンドを実行した時のことを思い出してみましょう。 例えば以下のコマンドを実行して、カレントディレクトリを調べることが出来ました。

 $ pwd
/home/vboxuser

また、以下のように存在しないディレクトリに移動しようとすると、エラーとなってしまいます。

 $ cd /dontexists/
-bash: cd: /dontexists: No such file or directory

この時起きていたことは、以下の三つの事象に切り分けることが出来ます。

  • ユーザーがキーボードからコマンドとしてのデータを入力した。
  • コマンドが実行され、その結果を表すデータが画面に出力された。
  • 実行結果がエラーの場合も同様に、エラー内容を表すデータが画面に出力された。

つまり、コマンドの実行においては、データを入力する入口と、データを出力する出口が必要となるのです。 このように、データが入力されてから出力されるまでの一連の流れのことを「ストリーム」と呼びます。

ストリームのうち、データの入口としてデフォルトで定められたものを「標準入力」、 データの出口としてデフォルトで定められたものを「標準出力」、 エラー時におけるデータの出口としてデフォルトで定められたものを「標準エラー出力」と呼びます。

そして「標準入出力」とは、標準入力、標準出力、標準エラー出力の三つをまとめた表現となります。

ストリームと標準入出力

上の例では標準入力としてキーボードが、また標準出力と標準エラー出力としてディスプレイが設定されている図になっています。 しかし、標準入出力にはファイルやプリンターなど、様々なものを設定することが出来ます。 例えばエラーログを取りたい場合は、標準エラー出力としてファイルを設定することが出来ます。

名前 英字
表記
内容 デフォルトの入出力先 その他入出力先の例
標準入力 stdin プログラムの入力元 0 キーボード ファイル
標準出力 stdout プログラムの出力先 1 ディスプレイ プリンター
標準エラー出力 stderr エラーメッセージの出力先 2 ディスプレイ ログファイル

また、linux内部では「標準入出力」を表中の「値」のように、それぞれ「0」から「2」の数値として管理しています。 この数値を利用することで、標準出力と区別して、標準エラー出力をどこに出力するかを指定できるようになります。

Lesson 5 Chapter 2
リダイレクト

Chapter1では標準入出力の概念について説明をし、その中で標準入出力はデフォルトの設定から切り替えることが出来ると説明しました。 このように標準入出力先を変更することを「リダイレクト」と呼びます。 Chapter2では、リダイレクト機能について、実際にコマンドを実行しながら確認していきます。

標準入力のリダイレクト

標準入力のリダイレクトについてcatコマンドを用いて解説します。 シェルを立ち上げてから、catコマンドを引数を指定しないで実行してみてください。

$ cat

すると、何も起きずにキーボードからの入力待ちとなります。 これが標準入力待ち状態です。 ここで例えば2行目に「Hello」と入力し実行すると3行目に「Hello」と出力されます。

$ cat
Hello
Hello

catコマンドの終了

出力を確認出来たら、「ctrl + d」ボタンを押してcatコマンドを終了することができます。

これはcatコマンドが「標準入力の内容を入力として読み込んで、標準出力先にそのまま出力する」というコマンドである為です。 現在の標準入力のデフォルト設定がキーボードのため、キーボードからの入力を待つ挙動をしています。

続いて、標準入力をキーボードではなくファイルにしてみましょう。 標準入力のリダイレクトには「」という記号を使います。次の例では/etc/hostnameファイルを標準入力に指定してcatコマンドを実行しています。 これによりhostnameファイルの内容が標準入力内容としてcatコマンドに渡され、それを標準出力先に出力します。

$ cat localhost.localadmin

上でご紹介した$ cat というコマンドですが、 Lesson3で使ってきたように、「

$ cat /etc/hostname

本章ではリダイレクトに関する説明を主としているため、敢えて「

標準出力のリダイレクト

次は「ls」コマンドを用いて標準出力のリダイレクトについて解説します。 標準出力のリダイレクトには「>」と「>>」の二つの記号を使うことが出来ます。 両者の主な違いは、リダイレクト先にファイルを指定した場合、「>」を用いた場合は「上書き」され、「>>」を用いた場合は「末尾に追記」される点です。 場面に応じて、適宜使い分けるようにして下さい。

記号 内容
> 指定したリダイレクト先にデータを出力する。リダイレクト先にファイルを指定した場合、そのファイルの内容は上書きされる。
>> 指定したリダイレクト先にデータを出力する。リダイレクト先にファイルを指定した場合、そのファイルの末尾に追加で書き込まれる。

以下では「ls -l /」部分でルートディレクトリ直下のディレクトリやファイルを出力し、それを「>」によって「result.txt」というファイルにリダイレクトしています。 なお、リダイレクト先として指定したresult.txtは自動で作成されるため、前もって作成しておく必要はありません。

$ ls -l / > result.txt
$ cat result.txt
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

echoコマンドを用いたファイルへの書き込み

Lesson3では、「echo」コマンドを用いて空のファイルにテキストの書き込みを行いました。以下のようなコマンドを実行したのを覚えていますでしょうか?

 $ echo "hello" > test00.txt
$ echo "takeshi" >> test00.txt
$ cat test00.txt
hello
takeshi

echoは、通常標準入力として受け取ったデータを、デフォルトの標準出力であるディスプレイに出力します。 上記のコマンドでは、echoの標準出力を「test00.txt」というファイルにリダイレクトすることで、ファイルへの書き込みを実現していたのでした。

標準エラー出力のリダイレクト

最後に「ls」コマンドを用いて標準エラー出力のリダイレクトについて解説します。 標準エラー出力については、「2>」「2>>」の二つの記号を使います。 両者の主な違いは、リダイレクト先にファイルを指定した場合、「2>」を用いた場合は「上書き」され、「2>>」を用いた場合は「末尾に追記」される点です。 これは標準出力の場合と同じです。場面に応じて、適宜使い分けるようにして下さい。

記号 内容
2> 指定したリダイレクト先にデータを出力する。リダイレクト先にファイルを指定した場合、そのファイルの内容は上書きされる。
2>> 指定したリダイレクト先にデータを出力する。リダイレクト先にファイルを指定した場合、そのファイルの末尾に追加で書き込まれる。

以下ではlsコマンドで「/abc/」直下のディレクトリやファイルを表示しようとしていますが、「/abc/」というディレクトリが存在しないためエラーメッセージが表示されます。

 $ ls /abc/
ls: cannot access /abc/: No such file or directory

このエラーメッセージを「error.txt」というファイルに出力させます。

 $ ls /abc/ 2> error.txt
$ cat error.txt
ls: cannot access /abc/: No such file or directory

次に、もう一度存在しないディレクトリ名でlsコマンドを実行し、エラーメッセージをerror.txtの末尾に追記します。

 $ ls /abcde/ 2>> error.txt
$ cat error.txt
ls: cannot access /abc/: No such file or directory
ls: cannot access /abcde/: No such file or directory

意図通り、エラーメッセージがファイルに出力されていることを確認できました。

「2>」や「2>>」の意味

Chapter1「標準入出力」の表で「標準エラー出力(stderr)」は「2」の値で管理されている旨記載してありました。 単に「>」としてコマンドを実行すると、標準出力を意味してしまうため、「2>」として「2」を付けることで、標準エラー出力を指定しているのです。

また、標準出力と標準エラー出力をまとめて1つのファイルにリダイレクトしたい場合もあると思います。その際は、標準出力をリダイレクトした後に 「2>&1」を追記することで可能です。

 $ ls /abcdefg/ >> error.txt 2>&1
$ cat error.txt
ls: cannot access /abc/: No such file or directory
ls: cannot access /abcde/: No such file or directory
ls: cannot access /abcdefg/: No such file or directory

「2>&1」の意味

「&1」の「1」とは、Chapter1の表に記載したとおり「標準出力」の意味です。それに「&」を付けることで、「標準出力と同じ場所に」という意味になります。

従って「ls /abcdefg/ > error.txt 2>&1」とは、ls /abcdefg/の標準出力を、「error.txt」に出力してください。 また、ls /abcdefg/の標準エラー出力(「2>」)は標準出力と同じ「error.txt」(「&1」)に出力してください。 という意味になります。今回の例では、コマンド実行の結果として標準出力がないため、標準エラー出力のみが「error.txt」に出力されています。

Lesson 5 Chapter 3
パイプライン

Chapter1からChapter2をとおして、標準入出力の概念について説明し、Chapter2では実際のコマンドを用いて、標準入出力(エラー出力)のリダイレクトを行いました。 Linuxではあるコマンドの標準出力を、次に連携させたいコマンドの標準入力として与えることで、複数のコマンドを繋ぎ合わせることが出来ます。 そのような機能のことを「パイプライン」と呼びます。パイプラインは「|」という記号を使います。 基本書式は以下です。

<コマンド1> | <コマンド2> ...

これで1つ目のコマンドの標準出力が2つ目のコマンドの標準入力になります。
例えば、lsコマンドの結果をcatコマンドで確認するという処理を考えましょう。先ほどまでは、lsコマンドの実行結果をファイルに出力し、 その後にcatコマンドを実行することでlsコマンドの出力結果を見ていました。

$ ls / >> result.txt/
$ cat result.txt

上の方法では、result.txtというファイルをわざわざ作成しなくてはいけないため、少々手間です。この処理を一度で実行します。

$ ls / | cat 

そうすると/(ルートディレクトリ)配下にあるディレクトリの一覧をcatコマンドの標準入力に直接渡し、内容を確認することが出来ます。 以上がパイプラインを使った処理になります。この機能は今後Linuxを使う上で多用する機能になりますのでしっかりと覚えておいてください。

Lesson 5 Chapter 4
高度なテキスト処理

Chapter3ではパイプラインを使ってコマンドを連結する方法を学びました。 Chapter4では、テキストに対して処理を行う高度な関数に着目して学習していきます。 これらの関数はパイプラインの機能と相性がいいため、取得すればテキストを自由自在に操れるようになります。

awk

awkコマンドはテキストの抽出、加工等の編集操作を行うためのコマンドです。 書式は以下になります。

awk <オプション> '<コマンド>' <ファイル> ...

awkコマンドを実際に使用する前に準備として「sample00.txt」という名前のテキストファイルをvimで用意してください。内容は以下としました。

 A1 A2 A3 A4 A5
 B1 B2 B3 B4 B5
 C1 C2 C3 C4 C5
 D1 D2 D3 D4 D5
 E1 E2 E3 E4 E5

用意ができたらawkコマンドを使ってファイル内容を出力してみましょう。 以下では、コマンドに「print」を指定し、「sample00.txt」のファイル内容を出力します。

 $ awk '{ print }' sample00.txt
A1 A2 A3 A4 A5
B1 B2 B3 B4 B5
C1 C2 C3 C4 C5
D1 D2 D3 D4 D5
E1 E2 E3 E4 E5

$」という記載方法で、内容の一部を列指定して出力させることも出来ます。 指定した1列目、3列目、5列目の値が表示されています。

 $ awk '{ print $1,$3,$5 }' sample00.txt 
A1 A3 A5
B1 B3 B5
C1 C3 C5
D1 D3 D5
E1 E3 E5

また、行を指定したい場合は「NR==」を使います。 数行分指定する場合はセミコロン「;」で区切ればよいです。以下では1行目と3行目が出力されます。

 $ awk 'NR==1;NR==3' sample00.txt
A1 A2 A3 A4 A5
C1 C2 C3 C4 C5

awkコマンドの組み込み変数

上の「NR」とはawk関数の組み込み変数というものです。 これは元々awk関数によって準備された特別な変数で、それには特定の意味が含まれています。 「NR」は「行数」を表します。

その他主な組み込み変数は下表のとおりとなります。

組み込み変数 内容
NF フィールド(列)の数
NR レコード(行)の数
FS フィールドの区切り文字(デフォルトは空白)
RS レコードの区切り文字(デフォルトは改行)
OFS 出力するときのフィールドの区切り文字(デフォルトは空白)
ORS 出力するときのレコードの区切り文字(デフォルトは改行)

FSとOFSを例にして、これらの組み込み変数の使い方を説明します。

上の表で記載しているとおり、フィールドの区切り文字を表すFSのデフォルトは「空白」です。 sample00.txtについては、フィールドの間(例えばA1とA2の間)が「空白」となっていたたため、awkコマンドはsample00.txtを5つのフィールド(列)からなるファイルだと認識していました。

 A1 A2 A3 A4 A5
 B1 B2 B3 B4 B5
 C1 C2 C3 C4 C5
 D1 D2 D3 D4 D5
 E1 E2 E3 E4 E5

ですので以下のコマンドを実行すると、1、3、5フィールド目が綺麗に出力されていました。

 $ awk '{ print $1,$3,$5 }' sample00.txt 
A1 A3 A5
B1 B3 B5
C1 C3 C5
D1 D3 D5
E1 E3 E5

では次にsample01.txtというファイルを以下の内容で準備し、このファイルのA1、A3、A5のフィールドを抽出したい場合を想定してください。

 A1,A2,A3,A4,A5
 B1,B2,B3,B4,B5
 C1,C2,C3,C4,C5
 D1,D2,D3,D4,D5
 E1,E2,E3,E4,E5

ところがsample01.txtではフィールドの間が「,」で区切られているため、awkコマンドは、sample01.txtを1フィールドのファイルであると認識します。 従って単に以下を実行した場合、sample01.txtにとっての1フィールド目、すなわちファイルの全てが出力されます。

 $ awk '{ print $1,$3,$5 }' sample00.txt 
A1,A2,A3,A4,A5
B1,B2,B3,B4,B5
C1,C2,C3,C4,C5
D1,D2,D3,D4,D5
E1,E2,E3,E4,E5

このような時にFSとOFSを使います。以下のように、「FS=,」「OFS=' '」として、sample01.txtの「,」文字を区切り文字として認識し、空白に置き換えて出力するようにしています。

 $ awk '{ print $1,$3,$5 }' FS=, OFS=' ' sample00.txt 
A1 A3 A5
B1 B3 B5
C1 C3 C5
D1 D3 D5
E1 E3 E5

特定の文字列を含む行を取得する場合は「//」を使います。 するとE4が含まれる5行目が出力されます。

 $ awk '/E4/' sample00.txt
E1 E2 E3 E4 E5

awkコマンドは、パイプラインと合わせて使うことが出来ます。 以下では「/etc/」直下のcrontabというファイルの3、5行目を表示しています。

 $ cat /etc/crontab  | awk 'NR==3;NR==5'
MAILTO=root
# For details see man 4 crontabs

sed

sedコマンドは文字列の置換処理や削除処理を行うコマンドです。書式は以下になります。

sed <オプション> <スクリプト> <ファイル>

スクリプトとは、アドレス(指定箇所)とコマンドを組み合わせたもので、例えばファイルの「3から6行目を削除してください」といった情報を意味するものです。 この場合「3から6行目」がアドレス、「削除してください」がコマンドになります。 アドレスは指定しないこともできますが、その場合はファイル全行がコマンドの処理対象になります。

sedコマンドでは主にテキストを置換、削除、表示することが出来ます。これから解説しますが、 事前準備として、以下のように「fruits.txt」というファイルを作成しておいて下さい。

 $ echo "appleorangeappleorange" >> fruits.txt

ではsedコマンドを使ってみましょう。行の置換には、「s」コマンドを使用します。 以下のような書式になります。フラグは、指定しなくても実行できます。

sed s/置換前文字列/置換後文字列/フラグ

早速試してみましょう

$ sed s/appleorange/bananapine/ fruits.txt
bananapineappleorange

すると、先頭の「appleorange」という文字が「bananapine」に置換されますが、まだ後方に「appleorange」の文字が残っています。 これはsコマンドを実行した際に、行頭から指定文字を探し始め最初に見つかった文字列のみを置換する処理のためです。

一括ですべての検索文字を置換したい場合はgフラグをつけます。以下ではechoコマンドでfruits.txtの中身を「appleorangeappleorange」に書き直し、gフラグを付けてsedコマンドを実行しています。 全ての「appleorange」が、「bananapine」書き変わりました。

 $ echo "appleorangeappleorange" > fruits.txt
$ sed 's/appleorange/bananapine/g' fruits.txt
bananapinebananapine

続いて行の削除処理を確認しましょう。削除処理はdコマンドを使います。

 $ sed '2d' sample00.txt
 A1 A2 A3 A4 A5
 C1 C2 C3 C4 C5
 D1 D2 D3 D4 D5
 E1 E2 E3 E4 E5

ここではsample00.txtの「2行目」を削除した結果を出力するという処理を実行しています。 この時、sedコマンドは編集結果を表示しているだけで実際にファイルの中身を削除はしていません。

複数の行を指定する場合は、「,」を使います。 以下では「sample00.txt」について、2行目から4行目を削除した結果を出力しています。

 $ sed '2,4d' sample00.txt
 A1 A2 A3 A4 A5
 E1 E2 E3 E4 E5

最後にpコマンドを使った行の表示処理を確認します。以下は1行目を指定して出力します。

 $ sed '1p' sample00.txt
 A1 A2 A3 A4 A5
 A1 A2 A3 A4 A5
 B1 B2 B3 B4 B5
 C1 C2 C3 C4 C5
 D1 D2 D3 D4 D5
 E1 E2 E3 E4 E5

1行目をアドレス指定して表示処理を行いましたが、予想と異なり、かなり多くの行が出力される結果となってしまいました。 これはsedコマンドでは何もオプションをつけずに「p」コマンドを実行すると「元のファイルの内容」と「pコマンドで指定した行」を表示する挙動となるためです。 これを避けるためにnオプションを同時に使用します。

 $ sed -n '1p' sample00.txt
 A1 A2 A3 A4 A5

すると見事期待する結果が得られました。1行だけ表示させたい場合はnオプションも同時に使用してください。

sedコマンドも、パイプラインと合わせて使うことが出来ます。 既に作成した「fruits.txt」を以下のように編集してください。

apple/banana/grape/melon
banana/strawberry/pineapple
cherry/apple/melon/grapefruit 
APPLE/BANANA/GRAPE/MELON

fruits.txt内のappleの文字をpeaceに置き換えて表示したい場合は、以下のように実行します。

 $ cat ./fruits.txt | sed 's/apple/peach/' 
peach/banana/grape/melon
banana/strawberry/pinepeach/
cherry/peach/melon/grapefruit
APPLE/BANANA/GRAPE/MELON

grep

grepコマンドは文字列を検索するコマンドです。書式は以下になります。

grep <オプション> <検索文字列> <ファイル>

grepコマンドでよく使われているオプションは下図のとおりです。

オプション 内容
-i 大文字と小文字を区別しないで検索する。
-n ヒットした行番号を表示させる。
-v 検索文字列を含まない行を表示させる。

先ほど作成した「fruits.txt」ファイルの中からgrapeという文字列が存在する行を出力します。

 $ grep grape fruits.txt
apple/banana/grape/melon
cherry/apple/melon/grapefruit

上の例で、今度は-iオプションを付けます。-iオプションを付けると大文字小文字を区別しないで検索するため、GRAPEを含む行も検索でヒットしています。

 $ grep -i grape fruits.txt
apple/banana/grape/melon
cherry/apple/melon/grapefruit
APPLE/BANANA/grape/MELON

出力された行が何行目かを確認したい場合は、-nオプションを付けます。この例では、1行目と3行目が出力されていることが分かります。

 $ grep -n grape fruits.txt
1: apple/banana/grape/melon
2: cherry/apple/melon/grapefruit

今までとは逆に、検索した文字を含む行を除いて表示させたいときは、-vオプションを付けます。

 $ grep -v grape fruits.txt
banana/strawnerry/pineapple
APPLE/BANANA/GRAPE/MELON

grepについても、パイプラインと併用した使用例を紹介します。以下ではcatコマンドでfruits.txtの中身を出力し、その内容をgrepコマンドへ標準入力として与えています。

 $ cat fruits.txt | grep grape
apple/banana/grape/melon

以下ではルートディレクトリの中から、「in」の文字が含まれるディレクトリのみを表示します。

 $ ls -l / | grep in
lrwxrwxrwx. 1 root root 7 Jan 5 13:03 bin -> usr/bin
lrwxrwxrwx. 1 root toot 8 Jan 5 13:03 sbin -> usr/sbin

以下ではルートディレクトリの中から、「in」の文字が含まれる行のみを出力し、その内容をin.txtファイルに書き込んでいます。

 $ ls -l / | grep in > ./in.txt
$ cat ./in.txt
lrwxrwxrwx. 1 root root 7 Jan 5 13:03 bin -> usr/bin
lrwxrwxrwx. 1 root toot 8 Jan 5 13:03 sbin -> usr/sbin

xargs

xargsは標準入力からデータを読み込み、それを引数として実行したいコマンドを実行します。書式は以下になります。

xargs <オプション> <実行したいコマンド>

xargsの使い方としては、例えばLesson3で紹介したfindでディレクトリ内のファイルを検索し、実行したいコマンドの引数に指定して実行することができます。

以下を試してみてください。 findコマンドでsample.txtというファイルをカレントディレクトリ内で検索し、その結果をcatコマンドの引数として指定して実行しています。

 $ find . -name 'sample00.txt' | xargs cat 
 A1 A2 A3 A4 A5
 B1 B2 B3 B4 B5
 C1 C2 C3 C4 C5
 D1 D2 D3 D4 D5

もう一つ見てみましょう。 これは、findコマンドでディレクトリ内から「sample」が入っているファイル名を探して、それをlsコマンドにリストとして引数で渡しています。 このようにxargsはほかのコマンドの出力をあるコマンドの引数にしてコマンドラインを組み立てて実行することができます。 なお、下の「*」は正規表現といって、次にまとめて紹介します。現時点では「sample*」とは「sampleと、その後に続く0文字以上の任意の文字」と理解しておいてください。

$ find . -name 'sample*' | xargs ls 
./sample.txt ./sample00.txt ./sample2.txt

正規表現とは

正規表現とは、特定の条件を表す文字列を抽象的に表現したものです。 複雑な文字列の検索・抽出をしたいときに使用されます。例えば、以下のような場合です。

  • 先頭は数字で文末は英語の文章のテキストを検索したいとき
  • 先頭はaで始まり2文字目はaかbのファイルを抽出したいとき
このような複雑な条件を指定したいとき、正規表現を使えば検索あるいは抽出が可能となります。

初めての場合は理解しづらいかと思いますので、まずはイメージを掴んでいきましょう。 準備として、以下コマンドを実行してファイルを作成します。

$ touch testsample.txt 

これで新たにtestsample.txtファイルが作成されたので、lsコマンドで確認してみてください。

sample.txt、sample00.txt、sample2.txt、testsample.txtというファイルが作成されていれば準備完了です。

まずは既にやってきたように、lsコマンドでの出力結果から、grepコマンドでsampleという文字が含まれている行のみを出力します。 以下のように四つの行が表示されます。

 $ ls -a | grep 'sample'
 sample.txt
 sample00.txt
 sample2.txt
 testsample.txt

それでは次に、上の例でgrepの引数に正規表現を指定してみましょう。 先ほどの「sample」という文字の左側に、「^」という文字を追加して下さい。 「^」とは「行頭」を意味します。 また、正規表現を使う際は「’」(シングルコーテーション)で囲むようにして下さい。 これは、指定した文字が正規表現なのだということを、きちんシェルに認識させるためです。 以下コマンドを実行してみてください。

 $ ls -a | grep '^sample'
 sample.txt
 sample00.txt
 sample2.txt

この実行例では、先ほどの実行例とは異なり「testsample.txt」は表示されなくなっています。 「^」の文字を加えたことで、「行頭がsampleで始まる」行が検索されるようになったためです。 従って行頭がsampleで始まらない「testsample.txt」は表示されなかったのです。

今回の例で、「^」という特別な文字を使うことで、単に「sample」と指定した場合よりも複雑な条件で検索を出来たことを感じて頂けましたでしょうか? 以降では、その他の正規表現について学んでいきます。

メタ文字とは

正規表現を作るために特別な意味を持つ文字をメタ文字と呼びます。先ほど登場した「^」もメタ文字の一つです。 主なメタ文字とその内容は以下のとおりです。

記号 内容
. 任意の一文字
\ 直後のメタ文字の意味を打ち消す
[ ] [ ]の中に含まれる、いずれかの一文字
[^ ] [ ]の中に含まれない、いずれかの一文字
^ 行の先頭
$ 行の末尾
* 前の文字の0回以上の繰り返し
\{m,n\} m回以上n回以下の繰り返し
\{m\} m回の繰り返し
\{m,\} m回以上の繰り返し

「.」の使い方

最初に、任意の文字列を表す正規表現を見てみましょう。 任意の1文字を表す時は、「 . 」(ピリオド)を使います。以下を実行してみてください。

 $ grep -i 'a...e' fruits.txt
 apple/banana/grape/melon
 banana/strawberry/pineapple
 cherry/apple/melon/grape

この例では、「fruits.txt」の中にある「a***e」という文字列を検索しています。その結果「a"ppl"e」がヒットしてその文字列が含まれる行が出力されています。

「\」の使い方

また、「 . 」(ドット)そのものを検索したい場合は直前に「 \ 」(バックスラッシュ)をつけます。 なお、以下のように「¥」マークで表示される場合がありますが、内部的には「\」と同じです。

 $ echo "apple.banana.grape.melon" >> fruits.txt 
$ cat fruits.txt | grep '\.'
apple.banana.grape.melon

ここで「\.」ではなく単に「.」とした場合は、ファイル中の全ての行が出力されてしまいます。 「\」という文字が、メタ文字としての「.」の意味を打ち消していたことに注目してください。

 $ cat fruits.txt | grep '.'
apple/banana/grape/melon
banana/strawberry/pineapple/
cherry/apple/melon/grapefruit
APPLE/BANANA/GRAPE/MELON
apple.banana.grape.melon

「[]」の使い方

続いて、特定の文字列を表したい場合は、 「 [] 」 というメタ文字を使います。 以下では、「fruits.txt」の中に「gra(eまたはp)e」という文字列がないかを検索しています。今回は「gra"p"e」という文字列がヒットしたのでその行を出力しています。

 $ grep -i 'gra[ep]e' fruits.txt
 apple/banana/grape/melon
 cherry/apple/melon/grape
 apple.banana.grape.melon

「[^ ]」の使い方

上の例とは逆に、特定の文字列以外の一文字を表したい場合は、 「 [^ ] 」 というメタ文字を使います。 以下では、「lのあとにeとa以外の文字がある」場合に出力されています。

$ grep 'l[^ea]' fruits.txt
apple/banana/grape/melon
cherry/apple/melon/grape
apple.banana.grape.melon

「^ 」の使い方

初めに登場しましたが、行頭を表す「 ^ 」の使い方を改めて紹介します。 先頭が「apple」で始まる行が出力されています。

 $ grep '^apple' fruits.txt
 apple/banana/grape/melon
 apple.banana.grape.melon

「$ 」の使い方

「 ^ 」とは逆に、「 $ 」は行末を表します。以下では最後の文字が「e」となっている行を出力しています。

 $ grep -i 'e$' fruits.txt
 banana/strawberry/pineapple
 cherry/apple/melon/grape

「*」の使い方

繰り返しを表すメタ文字は「*」を使います。直前の文字が0回以上繰り返される場合にマッチします。 例えば、'e*'という正規表現は、「」(eがゼロ回繰り返される。つまり何も文字がない)、「e」、「ee」、「eee」...「eがn個」にマッチします。 以下の内容の「seek.txt」ファイルを用意してから下のコマンドを試してみてください。

 seeek
 seeeekseeeekseeeek
 sek
 sk
 seak
 saek
 $ grep -i 'se*k' seek.txt
 seeek
 seeeekseeeekseeeek
 sek
 sk

*の直前のeが0回以上の繰り返し、すなわちeがない場合でもマッチします。なのでskもヒットします。 一方seakやsaekには、sとkの文字の間に「eの0回以上の繰り返し」のみならず、「a」の文字までもが含まれるため、マッチしません。

「\{m,n\}」の使い方

「\{m,n\}」は以下のように使います。 この例では、eが1回以上4回以下の繰り返しをしている場合にマッチします。 なお、下のように「\」が半角の「¥」で表示される場合がありますが、内部的には同じ意味です。

 $ grep 'se\{1,4\}k' seek.txt
 $ grep 'se\{1,4\}k' seek.txt
 seeek
 seeeekseeeekseeeek
 sek

「\{m\}」の使い方

「\{m\}」は以下のように使います。 この例では、eが3回の繰り返しをしている場合にマッチします。

 $ grep 'se\{3\}k' seek.txt
 $ grep 'se\{3\}k' seek.txt
 seeek

「\{m,\}」の使い方

「\{m,\}」は以下のように使います。 この例では、eが3回以上の繰り返しをしている場合にマッチします。

 $ grep 'se\{3,\}k' seek.txt
 $ grep 'se\{3,\}k' seek.txt
 seeek
 seeeekseeeekseeeek

拡張正規表現について

これまで紹介してきた正規表現は「基本正規表現」と呼ばれます。 対して、基本正規表現以外に使えるメタ文字を増やしたものを「拡張正規表現」と呼びます。

grepコマンドでは「-E」オプションを利用することにより「拡張正規表現」を使うことができます。 基本正規表現とは異なり-Eオプションを指定して使うため、使用する際には基本正規表現と拡張正規表現とを区別して理解しておく必要があります。 主な拡張正規表現は以下のとおりです。

記号 内容
+ 1回以上の繰り返し
? 0回又は1回の繰り返し
{m,n} m回以上n回以下の繰り返し
{m} m回の繰り返し
{m,} m回以上の繰り返し
() 複数の文字をグループ化する。
| 複数の正規表現をOR条件でつなげる

「+」の使い方

拡張正規表現の「+」は、直前の文字の1回以上の繰り返しを意味します。そのため、基本正規表現の「*」の場合とは異なりskはマッチしていません。

 $ grep 'se+k' seek.txt   //-Eオプションをつけないで拡張正規表現を使ってもマッチしない
 $ grep -E 'se+k' seek.txt   //拡張正規表現を使ってマッチしている
 seeek
 seeeekseeeekseeeek
 sek

「?」の使い方

拡張正規表現の「?」は、直前の文字の0回又は1回以上の繰り返しを意味します。以下ではsの後にeが0回又は1回続く文字を含む行が出力されています。

 $ grep -E 'se?k' seek.txt   
 sek
 sk

「{m,n}」の使い方

拡張正規表現の「{m,n}」は以下のように使います。 この例では、eが1回以上4回以下の繰り返しをしている場合にマッチします。

 $ grep -E 'se{1,4}k' seek.txt
 seeek
 seeeekseeeekseeeek
 sek

「{m}」の使い方

拡張正規表現の「{m,}」は以下のように使います。 この例では、eが3回の繰り返しをしている場合にマッチします。

$ grep -E 'se{3}k' seek.txt
 seeek

「{m,}」の使い方

拡張正規表現の「{m,}」は以下のように使います。 この例では、eが3回以上の繰り返しをしている場合にマッチします。

 $ grep -E 'se{3,}k' seek.txt
 seeek
 seeeekseeeekseeeek

「()」の使い方

今まで紹介したものは、メタ文字の直前の「1文字」について、繰り返し回数を指定していました。 拡張正規表現の「()」を用いれば、複数の文字をまとめたうえで、繰り返し回数を指定することが出来ます。 以下のように使用します。

$ grep -E '(seeeek)+' seek.txt
 seeeekseeeekseeeek

「|」の使い方

拡張正規表現の「|」は以下のように使用します。 この例では、文字中にskまたはeeが含まれる場合にマッチします。

 $ grep -E '(sk|ee)' seek.txt
 seeek
 seeeekseeeekseeeek
 sk

終わりに

以上でLesson5は終了です。 標準入出力の概念を理解するところからはじめ、最終的にはファイルやテキストに対して様々な処理を出来るようになりました。 色々な操作をしていく中で、「他のユーザーに大切なファイルを誤って変更されてしまったら大変だ」と思われた方もいるのではないでしょうか? 次回では、アクセス権について解説を行います。 適切なアクセス権を付けることで、大切なファイルを安全に管理することが出来るようになります。