Back List


まえ書き


「文字」や「キャラクタ」は、普通は目に見える文字を指す単語だが、デジタルデータを扱う場合は、単独で目に見えるわけではない制御(命令)や約束も合わせて「文字」「キャラクタ」と呼ぶことがある。例えばASCIIで規定されている128個のコードのうち、
7Ah
は、z (小文字のゼット:目に見える図形)が割り当てらている。このようなアルファベットの大文字小文字と数字それにひととおりの記号を表すには128個という数は充分で、余ったコードが制御(命令)に使われている。例をあげると、
0ah
は、タイプライターで紙を一行送れという制御(命令)だ。もともとこの約束は、目に見えるアルファベットや数字に混ぜて制御(命令)コードを受け渡すという使い方を意図したものなのだが、コンソールの入出力や読み書きするテキストの処理だけでなく、コマンドとしてシステムとのインターフェイスにも古くから使われてきた。



こうしたルールとCRTやキーボードによって、人の手になるプログラミング作業の能率は大きく向上することになった。しかしすぐに、作業効率と作業量とのバランスは崩れることになる。オブジェクト化や再利用などでプログラミング作業の能率が上がってアプリケーションが広がり、プログラムのコードが大きくなってくると、テープに穴を開けていた時代には許されなかった反対向きの作業が目立って必要になってきたのである。それは、大きなプログラムの中の小さな(だが数の多い)修正や、「細部を作りながらコンセプトを造り上げる」といった、検索や編集に大きな負担がかかる現代風のオペレーションだ。
これを助けるため、同じ128個の枠の中で、別の間接的な表現機能である「正規表現」が多く用いられるようになる。「正規表現」は、あるひとつの「見える文字」で他の「見える文字」の組み合わせを代表できるようにする約束である。制御コードを使わないので「見える文字」を扱うどのような入出力も通過することができる。考えられる無数の文字列をひとつの文字列で表すことができる「正規表現」は、特に同じ(または類似の)文字列を複数のファイルの中で繰り返し正確に使わなければならないプログラミングには、無くてはならない大切な道具となっていった。


Back List














































Back List


文字コード


7ビットのASCIIは、制御文字(C0、C1グループ)と図形文字(GLグループ)とにあてがわれている。8ビットになるとこれに拡張図形文字(GRグループ)が加わる。くりかえしになるが、「図形文字」とは見えるもの/表示するもの、すなわちアルファベットや数字や記号のことで、「制御文字」とはそうではないもののことである。

-1.制御文字
制御文字は、多くのシステムで、読みとった時点で特別な処理に使われる(システムが動く)可能性のある文字として認識されている。有名なものにはラインフィード、キャリッジリターン、エンドオブテキストなどがある。

-2.エスケープ:図形文字
経由するシステムに与える影響や逆にシステムから受ける影響を考慮すれば、送り手と受け手が安心して扱えるのは、図形文字グループと、制御文字ではラインフィードくらいである。しかしその受け渡しのあとにも(その上位の層でも)、受け手に向けて図形文字以外の処理や独自に定義した命令を送りたい場合がある。このための特別な約束に現在多く使われているのが図形文字バックスラッシュ\である。図形文字バックスラッシュ\を図形文字とは解釈せず、次に続く図形文字1文字も図形文字とは解釈せず、この2文字の組み合わせによって別の処理を表す、という約束だ。この考え方は「エスケープ」と呼ばれる。「エスケープ」とは、「次に続く図形文字とセットで図形文字から外す」というくらいの意味で、送り手と受け手双方のこの約束によって複数の機能が透過的に実現することになる。送り手と受け手のプログラムが双方に対応していなければ、バックスラッシュがエスケープを意味するのかどうか、そのあとの一文字が表す機能が何であるのかなどは当然異なってくる。

注)
制御文字の1つである1Bh(ESC)を用い、他の制御文字の機能を超えた特別な処理が定義されることがある。こちらもエスケープと呼ばれ(本家だしそもそもESCだ)古い型の装置やソフトウエアに多い。混乱しないよう気をつけなければならない。

-3.メタキャラクタ:図形文字
後出の正規表現に用いるメタキャラクタは、エスケープと同じように受け手(例えばgrepなどのプログラム)が独自に解釈する図形文字で、やはり図形文字であることを放棄して用いられる。ファイルの中から指定の文字列を探索する目的で、そのマッチングの範囲を表現する。メタキャラクタには、比較的使われない図形文字が複数(多すぎて亜種もあるのが難点)選ばれている。エスケープ(バックスラッシュ)との組み合わせになっていないのは、メタキャラクタは連続して使う性質のものだから(読みにくくてしようがないから)かもしれない。正規表現でエスケープ(バックスラッシュ)は、定義されてしまっているメタキャラクタをもとの図形文字に戻す場合に用いられる。



<C0 Group (制御文字)>
ASC
00 NUL NULL
01 SOH Start Of Heading
02 STX Start of TeXt
03 ETX End of TeXt
04 EOT End Of Transmission
05 ENQ ENQuiry
06 ACK ACKnowledge
07 BEL BELL
08 BS Back Space
09 HT Horizontal Tabulation
0A LF Line Feed
0B VT Vertical Tabulation
0C FF Form Feed
0D CR Carriage Return
0E SO Shift Out
0F SI Shift In
10 DLE Data Link Escape
11 DC1 Device Control 1
12 DC2 Device Control 2
13 DC3 Device Control 3
14 DC4 Device Control 4
15 NAK Negative AcKnowledge
16 SYN SYNchronous idle
17 ETB End of Transmission Block
18 CAN CANcel
19 EM End of Medium
1A SUB SUBstitute
1B ESC ESCape
1C FS File Separator
1D GS Group Separator
1E RS Record Separator
1F US Unit Separator

<GL Group (図形文字)>
ASC EUC UTF-8
20 20 20
21 21 21 ! エクスクラメーション
22 22 22 " ダブルクオォート
23 23 23 # シャープ
24 24 24 $ ダラー
25 25 25 % パーセント
26 26 26 & アンパサンド
27 27 27 ' シングルクオォート
28 28 28 ( パレンサシス
29 29 29 ) パレンサシス
2A 2A 2A * アスタリスク
2B 2B 2B +
2C 2C 2C , カンマ
2D 2D 2D -
2E 2E 2E . ピリオド
2F 2F 2F / スラッシュ
30 30 30 0
31 31 31 1
32 32 32 2
33 33 33 3
34 34 34 4
35 35 35 5
36 36 36 6
37 37 37 7
38 38 38 8
39 39 39 9
3A 3A 3A : コロン
3B 3B 3B ; セミコロン
3C 3C 3C < レスザン
3D 3D 3D = イコール
3E 3E 3E > グレータザン
3F 3F 3F ? クエスチョン
40 40 40 @ アットマーク
41 41 41 A
42 42 42 B
43 43 43 C
44 44 44 D
45 45 45 E
46 46 46 F
47 47 47 G
48 48 48 H
49 49 49 I
4A 4A 4A J
4B 4B 4B K
4C 4C 4C L
4D 4D 4D M
4E 4E 4E N
4F 4F 4F O
50 50 50 P
51 51 51 Q
52 52 52 R
53 53 53 S
54 54 54 T
55 55 55 U
56 56 56 V
57 57 57 W
58 58 58 X
59 59 59 Y
5A 5A 5A Z
5B 5B 5B [ ブラケット
5C 5C C2A5 \ バックスラッシュ
5D 5D 5D ] ブラケット
5E 5E 5E ^ ハット
5F 5F 5F _ アンダースコア
60 60 60 ` バッククオォート
61 61 61 a
62 62 62 b
63 63 63 c
64 64 64 d
65 65 65 e
66 66 66 f
67 67 67 g
68 68 68 h
69 69 69 i
6A 6A 6A j
6B 6B 6B k
6C 6C 6C l
6D 6D 6D m
6E 6E 6E n
6F 6F 6F o
70 70 70 p
71 71 71 q
72 72 72 r
73 73 73 s
74 74 74 t
75 75 75 u
76 76 76 v
77 77 77 w
78 78 78 x
79 79 79 y
7A 7A 7A z
7B 7B 7B { ブレイス
7C 7C 7C | パイプ
7D 7D 7D } ブレイス
7E 7E E280B ~ チルダ

<C1 Group (制御文字)>
ASC
80
81
82 BPH Break Permitted Here
83 NBH No Break Here
84
85 NEL NExt Line
86 SSA Start of Selected Area
87 ESA End of Selected Area
88 HTS Horizontal Tabulation Set
89 HTJ Horizontal Tabulation with Justification
8A VTS Vertical Tabulation Set
8B PLD Partial Line Down
8C PLU Partial Line Up
8D RI Reverse line feed
8E SS2 Single Shift 2
8F SS3 Single Shift 3
90 DCS Device Control String
91 PU1 Private Use 1
92 PU2 Private Use 2
93 STS Set Transmit State
94 CCH Cancel CHaracter
95 MW Message Waiting
96 SPA Start of Protected Area
97 EPA End of Protected Area
98 SOS Start Of String
99
9A SCI Single Character Introducer
9B CSI Control Sequence Introducer
9C ST String Terminator
9D OSC Operating System Command
9E PM Privacy Message
9F APC Application Program Command


Back List














































Back List


キャラクタセット


例えばEUC(Extended Unix Code)は、UNIX上で日本語を扱う場合によく利用されていたキャラクタセットだ。RFC1945などには、HTML中でcharsetを指定しなかった場合はデフォルトとしてISO-8859-1が使われるという記載がある。したがって、日本語を表示するWebページではソースのMETAタグでこれが指定されていなければならない。キャラクタセットという考え方(charset)は、HTML以外にも、UNIXやいくつかのマークアップランゲージなどで使われている。漢字を扱う場合はEUC-JP、ISO-2022-JP、UTF-8、SHIFT-JIS、扱わない場合はISO-8859などがよく用いられているようだ。これらは単に文字とコードとの関係を示しているわけではない。以下、符号化文字集合とエンコーディングとに分けて整理してみることにする。



<符号化文字集合>

ASCIIという文字順序(配列)は、元々は記述にキャラクタを利用するプログラミング言語に必要とされ成立した。当初はローカルだった(と言ってもそこにしか必要がなかった)ASCIIだが、データ交換の対象となる団体や地域が広がるにつれ、まずはコンピュータの世界で独立したルールとして広く認められるようになる。更に文字情報がインターネット上で頻繁に扱われるようになると、一部の企業だけで推進するものではない規格が必要となり、ASCIIはISO-646として規格化され、さらに例えばISO-8859やユニコードUTF-8の中に格納され、その配列はほとんどスタンダードなものとして長い間用いられている。ただしJISX0201ではバックスラッシュとチルダが円記号とオーバーラインになる。

ISO-646は7ビットのASCIIそのものだったが、東欧/北欧諸国、日本などではこれを8ビットに拡張することでローカルな言語が一部表現できたことから、ISO-8859各国版として8ビット版が規格化された(日本ではJISX0201の円記号とオーバーラインとカタカナ(半角):これらに起因する問題はいまだに続く)。世界的にはISO-8859-1がLatin-1として西欧を中心に普及している。更に日本ではこれに加え、漢字を指すための空間がJISX0208,211など(94区×94点)として独自に定められた。

符号化文字集合の中には限定された空間にそのグループの文字を並べただけのものもあり(例えばJISX0208)、それは単独ではまだコードではない。これらは後述のエンコーディングという操作によって、すべてのグループを集めた全体の空間にマッピングされ、コードが決まることになる。下のリストで先頭の+印はマッピングの必要な符号化文字集合である。一方・印は、符号化文字集合がそれ自身で全体の中の位置を指す(マッピングの必要がない)例で、最近ではこちらのほうが主流である。

(アルファベットの符号化文字集合:例)
・ISO-646(アメリカ版は=ASCII)
・ISO-8859-1
(漢字の符号化文字集合:例)
+JISX208
+JISX211
・SHIFT-JIS *注1)
(ユニバーサルな符号化文字集合:例)
・UTF-8(UCS) *注2)



<エンコーディング>

ISO-2022は、エンコーディングの方法を規格化したものである。バイト(8ビット)単位のハンドリング方法と、上述の符号化文字集合をどこにマッピングするかが定められている。

-漢字を扱う例-
EUC-JP(ASCII、JISX201、JISX208、JISX212などのマッピング)
ISO-2022-JP(ASCII、JISX201、JISX208などのマッピング)=JISエンコーディング
-英文を扱う例-
ISO-8859エンコーディング(ASCII、ISO-8859-1右半分のマッピング)



注1)
Microsoftが策定した仕様。日本のPC黎明期によく使われたこともあって表示システムは今でもこれを無視できないが、いくつか欠点もあり徐々に他のものに移行していくと考えられている。

注2)
ユニコードはマッピングの必要がなく、単一の文字集合として世界の主要な文字や記号を直接指そうとするものである。この規格はISO-10646として策定され、UCS(Universal Character Set)とも名付けられている。ユニコードには、ISO-2022に準拠した処理系(例えばSMTP)において、制御文字との誤認を避けるための変換方法によっていくつかのエンコーディングがある。
UTF-16(UCS Transformation Format)
UTF-8
蛇足であるが、現在一般的に"UTF-8"ではバイトオーダーマークBOMは付けない。この点、"UTF-8N"をBOM無/"UTF-8"をBOM有と定義する一部のプログラムとの間で摩擦がいまだに残っているようである。


Back List














































Back List


正規表現


この節では、grepを例に正規表現(Regular Expression)という考え方を見てゆく。
grepの1つめの引数は検索パターン、2つめはファイル名である。
例えば以下は、/etc/filenameという名のファイルの中で、abcdefという文字列を探す。
grep -inI 'abcdef' /etc/filename (-inI: ケースインセンス、行番号付、バイナリファイル無視)
正規表現は、この検索パターンの中で以下のように使う。この例では . と * が正規表現(のメタキャラクタ)である。
grep -inI 'a.cdef' /etc/filename
grep -inI 'abcde*f' /etc/filename

正規表現とは、文字の組み合わせをダイナミックに定義できるコンパクトな方法であり、テキストの検索や修正などあらゆる場面で、プログラムの引数に対してユーザーが用いる表現方法だ。実際には正規表現は比較的ゆるやかな約束または仕様に過ぎず、実際にはそれぞれのプログラムがこれを独自に処理することが多い。正規表現はシェルやOSの基本機能ではないが、UNIXのコマンド(プログラム)には正規表現をバインドするものも多くある。インターネットで活躍するPerlという言語でも正規表現は頻繁に使われている。上述のとおりプログラムによってその仕様がいくぶん異なる可能性は覚悟しておかなければならないが、それでも、BRE(基本正規表現)、ERE(拡張正規表現)というおおまかなくくりで、主な汎用プログラムがどちらを使っているかを分けることはできる。

プログラム    BRE/ERE/ERE+
grepBRE
sedBRE
viBRE
moreBRE
edBRE
exBRE
egrepERE
grep -EERE
AWKERE(+AWK)

正規表現では、文字の組み合わせを指定するのにメタキャラクタを定義してそれを用いる。EREではメタキャラクタを増やすことによって、BREで複雑だったメタキャラクタへのエスケープが整理され、+(1文字以上)や ?(0か1文字)など新しい機能も装備された。




初めに、本題で混乱しないよう、3点ほど脱線し確認しておく。

1. grepで2つ目の引数は対象とするファイル名である。UNIXのシェルでは、パス名展開 (path name expansion) とその一部であるファイル名展開 (file name expansion) にはワイルドカードを用いることができ、シェルは実際に存在するディレクトリやファイル名にこれを展開してコマンドに渡す。したがってgrepの検索パターンとファイル名では * の意味が違うのである。
$grep -in ab*f /etc/f*name
(ab*f はファイル/パス名ではないのでこのままgrepに渡され、正規表現なので例えばabcdefにはマッチしない。)
(/etc/f*name はファイル/パス名なのでワイルドカードとして展開され、したがってファイル名filenameは検索対象になる。ただし、'/etc/f*name'とコートすればシェルの展開から守ることはできるようになる。)

2. シェルは、シェルのメタキャラクタ(正規表現のメタキャラクタではない) ; & ( ) | ^ < > ? * [ ] $ ` ' " \ ! { } space tab を解することで、引数などを区切ったり文字の演算や入出力制御を行ったりすることが可能だが、コーテーションは、それらシェルの動作から文字列を守る。シングルコーテーションは全てをカプサライズするが、ダブルコーテーションはこのうち「 $ ` \ 」を例外とする。
echo '$PATH'
$PATH
慣れないうちは、ファイル/パス名以外の引数は、面倒でもシングルコーテーションで囲っておく/シェルの機能を使いたい場合に限り注意してコーテーションを外す、という使い方が、意味の無い決まりごとのバリエーションに振り回されたりせず、安全かもしれない。

3. 正規表現で特殊な処理を行うメタキャラクタは、ASCIIでは普通の文字である。これに特別な意味が割り当てられて正規表現は機能する。これら予約されたメタキャラクタを図形文字として扱いたい(この場合検索する)場合は、バックスラッシュを用いてメタキャラクタから図形文字に向かってエスケープする。
grep -in 'a\.cdef' /etc/filename
(a.cdefそのものを検索する。abcdefなどにはマッチしない。)
さらに脱線だが、エスケープはファイル名の指定でも使われる。
grep -in 'a\.cdef' /etc/f\*name
(f*nameというファイルを対象とする。filenameというファイルは対象としない。)
この違いを確認しておくと混乱の予防に役立つだろう。1つめの引数である検索パターンの中のエスケープは正規表現からのエスケープだが(grepが処理します)、2つめの引数であるコートされていないファイル名で同様に使われているバックスラッシュは、シェル機能(この例ではワイルドカード)からのエスケープである。





では戻って、以下、grepやsedの標準にもなっている基本正規表現について、実際のパターンを例に使い方を見てみよう。

1.ブラケット外部のメタキャラクタ(基本的なもの)

.(ピリオド)
全ての一文字にマッチする。
例:a.c
abc  adc  a4c

*(アスタリスク)
直前の文字の零個以上の連続にマッチする。
例:a*
NULL  a  aa  aaa ...
例:aa*
a  aa  aaa ...

^(ハット)
行の最初にマッチする。

$(ダラ)
行の最後にマッチする。

[](ブラケット)
囲まれた文字のうちのどれか一個にマッチする。ブラケット内部では、ブラケット外部で使ったメタキャラクタは意味を持たない。単なる文字列として扱われる。ブラケット内部の^と-はここだけで通用するメタキャラクタである。「3.ブラケット内部のメタキャラクタ」を参照のこと。
例:[ack]
a  c  k



2.ブラケット外部のメタキャラクタ(エスケープして使うブレースとパーレン)
(以下、項目と機能のみ)

\{m\}(エスケープ+ブレース:繰り返し量を指定する)
\{m,\}
\{m,n\}
\(.....\)(エスケープ+パーレン:繰り返しなどに再利用するために包括指定する)



3.ブラケット内部のメタキャラクタ
ブラケット内部では、外部とはまた異なるメタキャラクタ(すなわち機能)が使われる。
[^](ブラケット内の先頭のハット)
[-](ブラケット内の先頭のハット)
^が先頭にあれば、それに続く(範囲の)文字以外の文字全てを並べたことと同じである。
-はその範囲の文字全てを並べたことと同じでである。

例:[^abc]
a、b、c以外の文字にマッチする。

例:[a-k]
aからkまでの文字にマッチする。

例:[^0-9]
0から9までの文字以外にマッチする。

例:[1-3a-c]
1、2、3、a、b、cにマッチする。

例:[abc^]
a、b、c、^にマッチする。
^が[]の中にあってもそれが先頭以外なら単に「^」という文字として扱われる。





正規表現とは関係ないが、参考までに、grepで制御コードを検索する例を紹介する。grepには16進数を指定する機能が無い。そのため、シェルの機能をもってgrepに文字コードを渡す、という方法が簡単でよく使われている。ダラとそれに続くシングルコーテーションがそれである。
$'\xff' 16進数FF
$'\x0a' 16進数0A (= $'\n'でも可)
$'\x0d' 16進数0D (= $'\r'でも可)
例えば、改行などにCRを含んだ行を検索する場合の例。
grep $'\x0d' filename

また、これも正規表現とは関係ないが、fileコマンドは、エンコード情報などに加え改行についても教えてくれる。
file filename
filename: HTML document, UTF-8 Unicode text, with CRLF line terminators


Back List














































Back List


sed


sedはテキストを編集するためのプログラムである。HTMLや各種プログラムのメンテナンスでは百を超えるファイルを扱うことがあるが、複数のファイルで繰り返し使われる文字列をほとんど全て書き換えなければならないというケースはめずらしくない。それらを一括して処理してくれるのがsedである。開発環境が特別なものに制限されてさえいなければ、sedは誰にでも簡単に扱うことができるプリミティブなツールのひとつと言えるだろう。sedはgrepを飛び越えて使われることも多く、処理はgrepと同じく行単位だが、実際には複数のメモリを使うことによって、文字、文字列、行、複数行、昇順逆順など、さまざまな処理が可能になっている。例えば文字列の編集(sコマンド)では、行の抽出のための検索と文字列の編集のための検索が別々に行われる。以下、よく使われる一部の機能を抜粋して整理する。(GNU sed 4.2.1-10)





--- 処理の流れ

sedでは、下記①から④までの処理が1行毎に繰り返し行われる。このループは、②で該当行が行検索パターンとマッチしたかどうかにかかわらず止まらない。したがってマッチした行が複数あればそれら全てについての処理が行われる。以下、「パターンスペース」とは、作業用のバッファとして使われる1行~複数行分のメモリのことである。

①パターンスペースを削除する(NULLにする)。
②ファイルから次の1行を読み込み(-iオプションでは同時にファイルからこの1行を\nを含めて削除)、\nを除いてパターンスペースに追加し、行検索パターンと比較する。
 →読み込んだ1行が複数行検索の最初の行、または途中の行であれば、パターンスペースに\nを加えてから②をくり返す   (したがって複数行の最後の行の\nは検索されず、それ以外の行の\nは検索されることになる)  →その1行が複数行検索の終わりの行か、または1行検索パターンにマッチしていれば③へ  →その他の場合(行検索にかからなかった場合)はパターンスペースの最後に\nを戻して出力し、④へ
③sedコマンドとその引数を読み、実行する。
 [行一括処理=a, c ,i, dコマンドの場合]  ・sedコマンドに従って次のとおり出力する。    a文字列:  パターンスペース + \n + 文字列 + \n    c文字列:  文字列+ \n    i文字列:  文字列+ \n + パターンスペース + \n    d:  NULL  [行内検索処理=sコマンドの場合]  ・引数の文字列に従ってパターンスペースを編集する。  ・パターンスペース + \n を出力する。
④ファイルの終わりでなければ①に戻る。





--- 行検索とsedコマンド

sedを使うということは、この手順②と③についてのsedスクリプト、
【行検索パターン】
【sedコマンド】
【sedコマンドの引数】
を、シェルのインラインに例えば次のように書くということである。
sed -e '【行検索パターン】【sedコマンド】【sedコマンドの引数】' filename (*3)
実際にはこのようなシェルでの記述になるだろう。

$ sed -e '/RegExpression/cSTRING' filename


・コーテーションは、sedスクリプトの記述をシェルの機能からカプサライズするかどうか、通常のルールにしたがうこと。(*4)(*5)
・スラッシュは、検索パターンの前後と、sコマンドの目的文字列の後に必要である。(*2)

【行検索パターン】(処理する行を抽出するための条件)(手順②で参照される)
  (行検索パターンが無ければ全ての行)   6  6行目   5,8  5行目から8行目までの行   /RegExpression/  この検索パターンを含む行   /RegExpression1/,/RegExpression2/  /検索パターン1/を含む行から/検索パターン2/を含む行までの行   3,/RegExpression2/  3行目から/検索パターン2/を含む行までの行   /RegExpression1/,8  /検索パターン1/を含む行から8行目までの行

【sedコマンド】(手順③で参照される)
通常、行の追加/挿入/置換/削除には a, i, c, dコマンド、文字の置き換えにはsコマンドが使われる。
  a  該当行の後に指定の1行を追加する。(*1)   i  該当行の前に指定の1行を挿入する。(*1)   c  該当行を指定の1行に置換する。(*1)   d  該当行を削除する。   s  該当行に対して、さらに文字列の検索と置換を行う。
パターンスペースとは別にホールドスペースという記憶領域を活用することもできる。行の処理が進んでいっても残しておけることが特徴。
  h  パターンスペースの内容全てをホールドスペースに上書きする。   H  同、追加する。   g  ホールドスペースの内容全てをパターンスペースに上書きする。   G  同、追加する。   x  パターンスペースとホールドスペースの内容を交換する。

【sedコマンドの引数】(手順③で参照される)
(目的文字列には新しく \n を複数含むことができる:行を増やす事が可能)
 ・STRING  a, c ,i コマンドの目的文字列  ・/RegEspression/STRING/  sコマンドの/行内検索パターン/目的文字列/ (*2)





--- オプション

・-eオプションが無い場合は、最初のシェル引数だけがsedスクリプトとして処理される。-eオプションは、その直後のシェル引数がsedスクリプトであることを宣言するために使う。これによって、それぞれ直前に-eを伴った複数のsedスクリプトを並べることができる。複数のスクリプトは検索ループの1行毎に左から順に全てが実行される。このようにsedスクリプトを連ねた場合の特別な動作は、最後ではないsedスクリプトの出力先がパターンスペースになるということだ。これが次のsedスクリプトの入力になる。(*3)

・-iオプションが無ければパターンスペースの出力先はコンソールである。-iオプションがある場合はファイルの全ての行が上書きで出力先になる(行検索に1行もマッチしない場合であってもファイルは新しくなるということだ)。操作の確認を行う場合は-iオプションを外しておくが、それだけでは全ての行がコンソールに出力されてしまうことになる。-i.bakなどオプション名に続けて別の拡張子を指定すれば、バックアップファイルを自動的に出力することができる。

・-nオプションは、出力をコンソールとして処理の確認を行う場合に使えるかもしれない。このオプションは出力時にパターンスペースをNULLにする。したがって、 a, c, i コマンドの場合は、追加/挿入/置換文字列のみが該当した回数だけ出力される。dコマンドの場合は該当するとNULLが出力されるのでコンソールには何も起こらない。つまり a, c, i, d コマンドの場合、どの行が該当したのかを知ることはできないのである。また、sコマンドではパターンスペースが出力になるので、このオプションを用いると通常は何も出力されなくなるが、pフラグを付けることによって該当した処理結果だけを出力することができる。しかしこのまま-nオプションを外してしまうと今度は該当行が二重に出力されることになり(-iオプションではファイルを上書き)、確認のためにpフラグを用いる事はあまり安全とは言えない。また、ここまででお気づきのとおり、-nオプションと-iオプションを同時に使ってはいけない。これらを同時に使うと、コンソールへの-nオプションによる表示の場合と同様に、ファイル上でも全ての/または殆どの行が削除されてしまうことになるからである。

したがって、実際には、-eオプションは必ず付けておき、-nオプションも、sコマンドのpフラグも使わないようにするのが無難と言えるだろう。使用例にある【簡単なルーチンの例】を参照のこと。





--- 使用例

【簡単なルーチンの例】
1. まずは、grepで検索パターンと該当行(変更前として)を確認する。

$ grep 'searchpattern' *.extention


2. 確認できたら、同じgrepの出力を今度はsedにパイプし(該当行に限定して)、同じ検索パターンを用い-iオプション無しで結果を確認する。
(全ての行を表示させても良いならここでgrepは不要。)

$ grep 'searchpattern' *.extention | sed -e '/searchpattern/s/beforepattern/aftertext/g'


3. これも確認できたら、コマンドラインからgrep部分を削除し、sedのみを(-i.bakオプションとファイル名を追加して)実行し、ファイルを上書きする。

$ sed -i.bak -e '/searchpattern/s/beforepattern/aftertext/g' *.extention




【cコマンド】(*1)
行の全てを置き換え

$ sed -e '/I am.......$/,/^You are/cSTRING1\nSTRING2\nSTRING3' *.* */*.*


文字列"I am a pen."などで終わる行から、"You are"で始まる行までを抽出し、それら全ての行を"STRING1\nSTRING2\nSTRING3"の3行に置き換える。
\nのバックスラッシュはシングルコーテーションでシェルからはカプサライズされている。コンソールには全行は表示せず、編集の行われた行だけを表示する。ファイルへの上書きは行わない。対象とするファイルは、カレントディレクトリとその下のディレクトリにある、拡張子のあるもの全て。

$ sed -e 'cSTRING' filename


全ての行で、各行を"STRING"の1行に置き換える。対象ファイルのファイル名はfilenameである。

$ sed -e '2cSTRING' filename


2行目を"STRING"の1行に置き換える。

sed -e '2,5cSTRING' filename


2行目から5行目の4行を"STRING"の1行に置き換える。

$ sed -e '/abc/cSTRING' filename


文字列"abc"を含む行を抽出し、その行を"STRING"の1行に置き換える。

$ sed -e '/abc/,/def/cSTRING1\nSTRING2' *.* */*.* */*/*.*


文字列"abc"を含む行から文字列"def"を含む行までを抽出し、
それら全ての行を"STRING1\nSTRING2"の2行に置き換える。
対象とするファイルは2レベル下のディレクトリまで。


【sコマンド】(*2)
行の中の一部を置き換え

$ sed -e 's/abc/def/' filename


全ての行で、最初の文字列"abc"を文字列"def"に置き換える。

$ sed -e '3s/abc/def/' filename


3行目のみで、最初の文字列"abc"を文字列"def"に置き換える。

$ sed -e '3,6s/abc/def/g' filename


3行目から6行目までの全行で、全ての文字列"abc"を文字列"def"に置き換える。
gはその行でマッチした全ての文字列を処理するためのフラグであって、gコマンドではない。

$ sed -e '/uvw/,/xyz/s/abc/def/g' filename


"uvw"を含む行から"xyz"を含む行までを抽出し、その全行で、全ての文字列"abc"を文字列"def"に置き換える。行を選択する行検索パターンとその中で置換を行うための文字列検索パターンとは別のものである。




(*1)
c, a, i など、行を一括して扱うsedコマンドを表す識別子1文字の直後に目的文字列(目的行)を続けるというフォーマット(インライン:cline12, aline34, iline56 ...)は、実は alternative syntax である。これらsedコマンドの元々のフォーマットは、c\, a\, i\ の直後に必ず改行を行ってから目的文字列を書く、という、シェルに書いたスクリプトだ。シェルが通常はバックスラッシュと改行をセットで削除してくれるということを考えると、一見これら2つの使い方の間に違和感はない。だが注意しなければならないのは、現在のsedの実装では、sed自身もこの位置のバックスラッシュを処理するということである。シェルから渡された後であっても(sedが読む段階でも)、c, a, i 直後のバックスラッシュは有ってもかまわない、すなわち有ったら削除されることになっているのだ。こうなると不都合が起こる。新しい行の先頭(c, a, i の直後)にエスケープシーケンスを入れようと思っても、そのバックスラッシュは蹴っとばされてしまうのである。
このことに対しては、常に変数やシングルコーテーションの中で(シェルが展開しないよう) c\, a\, i\ を使う、という手も無くはないが、これではさらにコーテーションを使いたいときの記述が複雑になりすぎる。「c, a, i で新しい行の先頭に改行やタブを入れたいならバックスラッシュを2連打」、これが最もシンプルで気楽な策になるだろう。その他のsedコマンド、たとえばsコマンドの目的文字列にこのような問題はない。




(*2)
sedコマンドは数種類の決められた1文字に限定されている。検索パターンはスラッシュで囲まれる。パラメータは引数の区切りによって終わりが定義される。ただし、考え方はよくわからないが、パラメータであってもsedコマンド s または y の目的文字列に限っては、後にフラグが続かなくても引数の区切りでは終わらない。必ずスラッシュで終えなければならない。




(*3)
sedで-eに続かない最後の引数は対象ファイル名であり、もちろん、grepで使う入力ファイル名などと同様、シェルがワイルドカードを展開する。

<grep>

$ grep -inI "i am a pen" filen*e

$ grep -rinI "i am a pen" [DirectoryPath]

$ grep -inI 'l.*x' ~/MyLinuxMemo/*



<find>

$ find [DirectoryPath] -name "abcd*.txt"






(*4)
コーテーションに関し、シェルにおける一般的な使い方を確認しておく。

シェルにとっての特殊文字を多く含む可能性がある文字列(プログラムやマークアップのコードなど)を扱う場合、それらをひとつひとつエスケープによって守るのは煩わしい。したがってコーテーションを使うのが一般的だが、ダブルコーテーションで囲みその中の「ダバダバ」4文字をケアするよりも、シングルコーテーションで囲みその中のシングルコーテーション (') 1文字をケアするほうが楽である。このとき特殊文字を実行する場合は、一旦そのシングルコーテーションを閉じ、外でそれを実行し、あらためてシングルコーテーションを再開する、といったひと手間が必要になる。たとえば途中で文字列変数 ($HOGE) を利用するというケースだ。また、シングルコーテーションの中ではエスケープが効かなくなる(特殊文字であるはずのバックスラッシュが通常文字になる)ということも忘れてはならない。

01:HOGE='inserted'
02:echo -e 'start:\t\t'$HOGE'\t\t:end'
03:start: inserted :end


また、シングルコーテーション自身を通常文字(ただのカタチ)として使いたい場合にも同じ手順を使うことになる。それは、シングルコーテーションの外で、もともと特殊文字であるはずの 【'】 をエスケープアウトし通常文字としてその位置に置くということである 【'\''】) 。

01:echo -e '\t\t1st Line:ADD START\n\t\t\t2nd Line:"Double Quoted"\n\t\t\t3rd Line:'\''Single Quoted'\''\n\t\t4th Line:ADD END'
02: 1st Line:ADD START
03: 2nd Line:"Double Quoted"
04: 3rd Line:'Singke Quoted'
05: 4th Line:ADD END


これに対しダブルコーテーションの中では、バックスラッシュ 【\】 はそのままエスケープシーケンスとして処理される。本来この約束だけでどんなエスケープ動作も表現できるはずなのだが、実際にはダブルコーテーション自身とバックスラッシュだけでなく、バックコーテーション【`】、ダラ【$】を合わせた4キャラクタ(ダバダバ)がシェルによって展開される。




(*5)
変数について。まずはシェル全般について確認し、次いでsedにおける用法をまとめる。

下記①の01:行目では、コーテーションや特殊文字をルールどおりに処理しつつ、変数が定義される。そしてコーテーションはここで剥がれる。シェルは02:行目でこの変数を行の中に展開するのだが、ここで注意しなければならないのは、変数の中の文字は既に一度処理が済んでいるということである。したがってコーテーションで囲まれていなくても再度処理されることはない。したがって02:行に展開されたであろう 【>】 はリダイレクトでなく通常文字であり、これは動作しない。

01:PP='>'
02:echo 'abc' $PP hoge
03:abc > hoge


しかし、半角スペースは特別だ。シェルが展開する変数の中にあっても、半角スペースは区切り文字としての役割を保つのだ。したがって下記②③は正常に動作する。逆に、(④のように)変数の中に本当の空白を入れたつもりでも、行の中に展開された後それは区切り文字としてシェルに認識されてしまうことになる。
注)変数になった後もシェルに対して特別な役割を保っていられるのは半角スペース(区切り文字)だけである。パイプ | 、リダイレクト > 、コーテーション " ' 、バックスラッシュ \ などの特殊文字は、この時点でシェルによって何の解析も処理もされないただの文字(通常文字)になってしまっている。これは、後述するように、半角スペースに対する手当さえ正しく行っておけば、変数の中にあるかもしれない特殊文字をシェルの動作から自動的に隔離できるということでもある。たしかに、正規表現など、シェルにとっても特殊文字である文字を多く含むだろう文字列を変数にした場合、展開によってその特殊な意味がいちいち復活していたのでは面倒なことになるだろう。

01:echo -e 'abc defg\n1234' > hoge
02:ARG='abc hoge'
03:grep $ARG
04:abc defg


01:echo -e 'abc defg\n1234' > hoge
02:ARG='grep abc hoge'
03:$ARG
04:abc defg


01:echo -e 'abc defg\n1234' > hoge
02:ARG1='bc de'
03:ARG2='hoge'
04:grep $ARG1 $ARG2
05:エラー(deというファイルが無い)


上の例④に対する対策は、通常文字である空白を含む変数は外側をダブルコーテーションで囲む、ということである(上に書いた理由によって内側=変数定義にコーテーションやエスケープを重ねて用いてもそれらは機能しない)。【$】を展開しなければならないのでシングルコーテーションは使えない。

04:grep "$ARG1" $ARG2
05:abc defg


さて、やっとsedである。sedも同様で(sedの場合はsedスクリプト全体がひとつの引数である)、通常文字である空白を含む変数を裸にしたままではうまくない。以下の例ではひとつの引数(sedスクリプト)に複数の変数を使っている。
01:FILENAME='hoge'
02:SEARCH='ab cd'
03:TARGET='ef gh'
04:sed -e /$SEARCH/i$TARGET $FILENAME


上の例の04:行は、次の行を直接タイプしたのと同じことである。半角スペースは変数を経由してもなお区切り文字として認識されるからである。これでは動作しない。
$ sed -e /ab cd/ief gh hoge


対策は先の例と同じで、それぞれの変数を外からコーテーションで囲むか、あるいはsedスクリプト全体を一括してコーテーションで囲めば良い。
04:sed -e /"$SEARCH"/i"$TARGET" $FILENAME
04:sed -e "/$SEARCH/i$TARGET" $FILENAME


その結果、04:行は、
$ sed -e /"ab cd"/i"ef gh" hoge
$ sed -e "/ab cd/ief gh" hoge

として処理され、いずれも正しく動作するようになる。コーテーションの範囲は、記述が複雑にならないようsedスクリプト全体とするのが楽だろう。

まとめると、sedの引数(sedスクリプト)に変数を用いその中に通常文字である空白が含まれる可能性がある場合は(そうでなくても)、sedスクリプト全体をダブルコーテーションで囲んでおくのがお気楽で良い。変数を使わないならシングルコーテーションだ(sedスクリプトをシェルから隔離する)。一方ファイル名はワイルドカードで展開することが多いのでコーテーションには要注意。




(*6)
特殊文字(シェルと正規表現のメタ文字を含む)について整理する。

【シェルのメタ文字】
| & ; > < ( ) $ ` ' \ " ~ # * [ ] ?

( \ はメタ文字が続かなくてもメタ文字:ただし消えるだけ)
-- エスケープアウト:上記すべて(通常文字が続けばそのバックスラッシュは消える)
-- エスケープインによって特殊な意味を持つ文字:なし


【シェルのダブルコーテーションの中にあってもメタ文字のままである文字】
" $ ` \

( \ はメタ文字が続く場合のみメタ文字:そうでない場合は通常文字として残る)
-- エスケープアウト:上記すべて
-- エスケープインによって特殊な意味を持つ文字:なし


【シェルのシングルコーテーションの中にあってもメタ文字のままである文字】
'

-- エスケープアウト:なし
-- エスケープインによって特殊な意味を持つ文字:なし


【基本正規表現のメタ文字】
. [ ] ^ $ \ *

-- エスケープアウト:上記すべて
-- エスケープインによって特殊な意味を持つ文字:
\( \) \{ \}



【sedの引数の中で特殊な意味を持つ文字(正規表現区間以外で)】
sedコマンドやフラグに割り当てられたアルファベット / \



【ASCIIの特殊文字(制御コード除く)】
(特殊文字 -単体で特殊な意味を持つ文字- は無い)
-- エスケープインによって特殊な意味を持つ文字:
\a \b \f \n \r \t \v \\ \? \' \" \0

(これらはそれぞれ制御コードとも対応付けられている)




(*7)
本文は改行コードが \n である場合を想定している。使っているシステムによってはこれが \r\n または \r であるかもしれないことには注意が必要だ。\r が検索されるのかどうかという問題である。例えばsedに似たいくつかのプログラムがWindowsでも使われているが、Linuxとの間でファイルの受け渡しを伴う業務では、行末の \r に関して何らかの処理が必要になるだろう。






(参考)
https://www.gnu.org/software/sed/manual/sed.html



Back List