Back List


制御文字と図形文字


まずは本題ではないのだが、この項では制御文字と図形文字について、7ビットのASCIIを例にあらかじめ整理しておく。ASCIIでは、7ビットが制御文字(C0、C1グループ)と図形文字(GLグループ)とにあてがわれている。8ビットになるとこれに拡張図形文字(GRグループ)が加わる。「図形文字」とは見えるもの/表示するもの、すなわちアルファベットや数字や記号のことで、「制御文字」とはそうではないもののことをここでは言う。

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

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

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

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



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

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

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


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

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


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文字)など新しい機能も装備された。詳細は【参考1:BREとEREとの比較】も参照のこと。





脱線するが、本題で混乱しないよう、初めにいくつか確認しておく。

1. 正規表現は、単体では長い文字列の中で一致を期待する部分的文字列(の条件)を表現しているが、grepはその正規表現と1行との比較であって、1行の一部と一致した場合に、一致しない部分を含めた行全体、または一致した部分だけ(-oオプション)を返してくる。パターンを1行の前後にアンカリングすれば(^$)完全に一致する1行を探すことになるので、その1行が正規表現そのものになる。
2. 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'とコートすればシェルの展開から守ることはできるようになる。)

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

4. 正規表現で特殊な処理を行うメタキャラクタは、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、^にマッチする。
^が[]の中にあってもそれが先頭以外なら単に「^」という文字として扱われる。






【覚え1:制御コードの検索】

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






【覚え2:fileコマンド】

fileコマンドは、エンコード情報などに加え改行についても教えてくれる。
file filename
filename: HTML document, UTF-8 Unicode text, with CRLF line terminators






【参考1:BREとEREとの比較】

BRE    .    [    \    *    ^    $
Collation-related bracket symbols   [==]    [::]    [..]
Escaped characters                  \ 
Bracket expression                  []
Grouping                            \(\)    \n
Single-character duplication        *    \{m,n\}
Anchoring                           ^    $

ERE    .    [    \    ()    *    +    ?    {}    |    ^    $
Collation-related bracket symbols   [==]    [::]    [..]
Escaped characters                  \
Bracket expression                  []
Grouping                            ()    \n
Single-character duplication        *    +    ?    {m,n}
Anchoring                           ^    $

機能を比較すると、BREでは、「+」、「?」、「|」の3つが使えないが、
・「+」→「{1,}」
・「?」→「{0,1}」
というふうに同じ機能は実現可能で、さらにGNUであればBREでも
・「|」→「\|」
を用いることができる。すなわちほとんど変わらない。

また使いやすさも一長一短である。EREではスクリプトでパターンにシェル変数を使う場合やパターン内に文字として()や{}を含める場合はエスケープアウトのために読みにくくなり、一方BREでは、()や{}をRE特殊文字として使う場合はエスケープインのために読みにくくなる。

そして使われ方だが、最近ではEREが使われることが多くなってきているかもしれない。コマンドラインではハンドリングを優先してEREを使いたいし、スクリプトにおけるEREの混乱はプログラムによって避ける事ができるからだ。いずれにしても、共に「POSIX正規表現」として現在でも並び立っている。






【参考2:正規表現における論理積】

正規表現には論理積が無い。代わりに使われるのがPerl正規表現の「肯定的先読み(positive lookahead)」である。使える場合と使えない場合があるので注意する。(grepでは-P(Perl)オプションが必要。)
「肯定的先読み(positive lookahead)」とは、
・(?=)の直前のポインタからの文字列と(?=)内のパターンとを比較し(移動サーチではない)
・一致すれば直前のポインタに戻ってから次の処理に進む(マッチした文字列は抽出しない)
・一致しなければそこで終了する
という、ポインタを進めず文字列も抽出しない特殊なシーケンスであり、このことを条件(積)として使うのである。

以下は、例えばパスワードに「数字と英小文字と英大文字の3種類を全て使った16文字以上」という条件を付けるための例である。
--- ERE本体 ---
(?=.*\d)(?=.*[a-z])(?=.*[A-Z])^[a-zA-Z\d]{16,}$

--- formのinputでは(html) ---
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])^[a-zA-Z\d]{16,}$$"

--- grepでは ---
grep -P "(?=.*\d)(?=.*[a-z])(?=.*[A-Z])^[a-zA-Z\d]{16,}$" filename

--- 動作の内訳---
  (?=.*\d):直前のポインタから(ここでは先頭)と.*[0-9]とを比較/一致しなければそこで終了/一致すれば文字列は抽出せず直前のポインタに戻って次の処理へ
  (?=.*[a-z]):直前のポインタからと.*[a-z]とを比較/一致しなければそこで終了/一致すれば文字列は抽出せず直前のポインタに戻って次の処理へ
  (?=.*[A-Z]):直前のポインタからと.*[A-Z]とを比較/一致しなければそこで終了/一致すれば文字列は抽出せず直前のポインタに戻って次の処理へ
ここまで全てにパスすれば、次に書かれた通常のサーチが行われる。
  ^[a-zA-Z\d]{16,}$:数字か英小文字か英大文字またはそれらの混在が合計16文字以上/前後アンカリングによって他の文字種を認めない


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