# TLCL 輪読会
### 19 – Regular Expressions
2023/03/28 [@kdnakt](https://twitter.com/kdnakt)
---
## 今日の範囲
### 19 – Regular Expressions
- grep
- Metacharacters and Literals
- The Any Character
- Anchors
- Bracket Expressions and Character Classes
- POSIX Basic vs. Extended Regular Expressions
- Alternation
- Quantifiers
- Putting Regular Expressions to Work
---
### 正規表現とは
- テキスト中のパターンを見つけるための記法
- シェルのワイルドカードと類似
- 多くのコマンドや言語がサポート
- バリエーションがある
- 以下、POSIX標準を中心に進める
---
### grep (p.251~)
- Global Regular Expression Printに由来
- 正規表現にマッチするテキストを検索
- マッチした行を出力
```shell
$ ls /usr/bin | grep zip
```
----
#### grepのオプション (1)
```shell
$ grep [options] regex [file...]
```
- -i,--ignore-case
- 大文字小文字無視
- -v,--invert-match
- 反転マッチ
- -c,--count
- マッチした行数を出力
----
#### grepのオプション (2)
- -l,--files-with-matches
- マッチした行を含むファイル名を出力
- -L,--files-without-matches
- マッチしない行を含むファイル名を出力
- -n,--line-number
- 行番号を出力
- -h,--no-filename
- 複数ファイル検索でファイル名非表示
----
#### grep練習
```shell
# 準備
$ ls /bin > dirlist-bin.txt
$ ls /usr/bin > dirlist-usr-bin.txt
$ ls /sbin > dirlist-sbin.txt
$ ls /usr/sbin > dirlist-usr-sbin.txt
# 実践
$ grep bzip dirlist*.txt
$ grep -l bzip dirlist*.txt
$ grep -L bzip dirlist*.txt
```
---
### Metacharacters and Literals (p.253~)
- リテラル
- 文字自身にマッチする
- 例) `bzip`
- メタ文字
- 複雑なマッチを実現する
- `^ $ . [ ] { } - ? * + ( ) | \`
- バックスラッシュ(\\)でエスケープ
- 例) `\$`
----
#### 正規表現のメタ文字とシェル
- メタ文字の多くはシェルでも特殊な意味
```shell
$ echo {a..c}
a b c
$ a="b"
$ echo $a
b
$ a=(1 2 3)
$ echo ${a[@]}
1 2 3
```
- コマンドラインでのメタ文字を含む正規表現
-> 引用符必須
---
### The Any Character (p.254~)
- .(ドット): 任意の1文字にマッチ
```shell
$ grep -h '.zip' dirlist*.txt
bunzip2
bzip2
gunzip
gzip
(略)
```
- 注) `zip`は検索結果に出ない
---
### Anchors (p.255~)
- キャレット(^): 行の先頭
- ドル記号($): 行の終端
```shell
$ grep -h '^zip' dirlist*.txt
zip
zipgrep
(略)
$ grep -h 'zip$' dirlist*.txt
gunzip
gzip
(略)
$ grep -h '^zip$' dirlist*.txt
zip
```
----
#### A Crossword Puzzle Helper
- 5文字の単語
- 3文字目がj
- 最後がr?
```shell
$ grep -i '^..j.r$' /usr/share/dict/words
Major
major
```
---
### Bracket Expressions and Character Classes (p.256~)
- `[ ]`: マッチする文字の組を指定
```shell
$ grep -h '[bg]zip' dirlist*.txt
bzip
bzip2recover
gzip
```
- `[ ]`内のメタ文字は普通の文字扱い
- 例外は`^`(否定)と`-`(範囲指定)
----
#### Negation
- `[^bg]zip`: bまたはg以外の文字+zip
- zipの前に文字が来る必要がある
- zipはマッチしない
- `[ ]`内の先頭でのみ`^`は否定の意味となる
- 他の位置の場合は文字の組の1つ
----
#### Traditional Character Ranges (1)
- 大文字アルファベット始まりのファイル名を探す
- :no_good: `grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' dirlist*.txt`
- :ok_woman: `grep -h '^[A-Z]' dirlist*.txt`
----
#### Traditional Character Ranges (2)
- アルファベットまたは数字始まりのファイル名を探す
- `grep -h '^[A-Za-z0-9]' dirlist*.txt`
- ダッシュ(-)またはAまたはZを含む
- `grep -h '[-AZ]' dirlist*.txt`
----
#### POSIX Character Classes
- 次のような結果になるOSもある
```shell
$ ls /usr/sbin/[A-Z]*
/usr/sbin/biosdecode
/usr/sbin/chat
(略)
```
- 照合順序collation orderの差異
- ASCII: `ABC..Zabc...z`
- 辞書: `aAbBcC...zZ`
- 上の例は辞書順
- なのでa始まりの結果がない
----
##### 文字クラス (1/2)
| 文字クラス | 詳細 |
|---------|---|
| [:alnum:] | [A-Za-z0-9] |
| [:word:] | [:alnum:] と `_` |
| [:alpha:] | [A-Za-z] |
| [:blank:] | スペースとタブ文字 |
| [:cntrl:] | アスキーコードの0-31と127 |
| [:digit:] | [0-9] |
----
##### 文字クラス (2/2)
| 文字クラス | 詳細 |
|---------|---|
| [:graph:] | アスキーコードの33-126 |
| [:lower:] | [a-z] |
| [:punct:] | -!"#$%&'()*+,./:;<=>?@[\\]_`{\|}~ |
| [:print:] | [:graph:] + スペース |
| [:space:] | [ \t\r\n\v\f] |
| [:upper:] | [A-Z] |
| [:xdigit:] | [0-9A-Fa-f] |
----
##### 文字クラスの利用方法
```shell
$ ls /usr/sbin/[[:upper:]]*
/usr/sbin/MAKEFLOPPIES
/usr/sbin/NetworkManagerDispatcher
/usr/sbin/NetworkManager
```
----
#### Reverting to Traditional Collation Order
- 照合順序: LANG環境変数で制御可能
```shell
# LANGは言語と文字コードからなる
$ echo $LANG
en_US.UTF-8
# 伝統的照合順序(ASCII)を利用したい場合
$ export LANG=POSIX
# 文字コードがASCIIになるので注意
```
---
### POSIX Basic vs Extended Regular Expressions (p.262~)
- POSIXにも2種類の正規表現
- BRE: Basic Regular Expressions
- ERE: Extended Regular Expressions
----
#### BREとERE
- 違いはメタ文字
- BRE: `^ $ . [ ] *`
- ERE: BRE + `( ) { } ? + |`
- 注) `( ) { }`はバックスラッシュでエスケープするとBREでもメタ文字扱い
- egrep: EREをデフォルトにしたgrep
- `grep -E`と同じ
----
#### POSIX
- 1980年代、Unixが商用OSとして人気に
- 個々のベンダが差別化を図る
- IEEEがPOSIXとして標準化
- Portable Operating System Interface
- 語感を良くするためXがついた
---
### Alternation (p.263~)
- 拡張正規表現の機能のひとつ
- `|`で表す。論理和(or)
```shell
$ echo "AAA" | grep -E 'AAA|BBB|CCC'
AAA
$ echo "BBB" | grep -E 'AAA|BBB|CCC'
BBB
$ echo "DDD" | grep -E 'AAA|BBB|CCC'
$
```
---
### Quantifiers
- EREで特定の回数、文字にマッチさせる方法
- ?: 0回または1回
- *: 0回以上
- +: 1回以上
- {}: 指定回数以上
----
#### 電話番号のカッコ有無
- 有効な電話番号
- (nnn) nnn-nnnn
- nnn nnn-nnnn
```shell
$ grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$'
```
- `()`の有無を`?`で判定
----
#### 大文字で始まる文章
- OK: This works.
- NG: this does not works
```shell
$ grep -E '[[:upper:]][[:upper:][:lower:] ]*\.'
```
- [:upper:]または[:lower:]または半角スペース、が0回以上
----
#### アルファベットと半角スペース1個のみの文字列
- OK
- This is it
- a b c
- NG
- a b 9
- abc d
```shell
$ grep -E '^([[:alpha:]]+ ?)+$'
```
----
#### 指定回数マッチさせる
| 指定子 | 意味 |
|-------|-----|
| {n} | 直前の文字がちょうどn回 |
| {n,m} | 直前の文字がn回以上m回以下 |
| {n,} | 直前の文字がn回以上 |
| {,m} | 直前の文字がm回以下 |
- 手元のMacのzshでは{,m}は動かず...
```shell
# 電話番号マッチの改良版
$ grep -E `^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$`
```
---
### Putting Regular Expressions to Work
- 以下、実践編
- grep
- find
- locate
- less & vim
----
#### Validating a Phone List With grep
```shell
# ランダムな電話番号リストを作成
$ for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done
# -vオプションでマッチしない無効な電話番号を抽出
$ grep -Ev `^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$` phonelist.txt
(292) 108-518
(129) 44-1379
```
----
#### Finding Ugly Filenames with find
- grep: 文字列の一部がマッチしたら結果出力
- find: 完全一致した場合に結果出力
```shell
# スペースやその他の有害な文字を含むパスを見つける
# パス全体にマッチする必要があるので、前後に`.*`
$ find . -regex '.*[^-_./0-9a-zA-Z].*'
```
----
#### Searching for File with locate
- locateコマンド
- --regexpオプション: BRE
- --regexオプション: ERE
```shell
$ locate --regex 'bin/(bz|gz|gip)'
/bin/bzcat
/bin/bzcmp
/bin/bzdiff
(略)
/bin/gzip
/usr/bin/zip
```
----
#### Searching for Text with less and vim
- /キー+正規表現で検索可能
- less: EREをサポート
- vim: BREをサポート
- 量指定子はエスケープ必要
---
### Summing Up
- 正規表現の利用方法を少し学んだ
- コマンドごとの使い方はman pageをzgrep
```shell
$ cd /usr/share/man/man1
$ zgrep -El 'regex|regular exp' *.gz
```
{"metaMigratedAt":"2023-06-17T23:46:29.821Z","metaMigratedFrom":"YAML","title":"TLCL 輪読会","breaks":false,"slideOptions":"{\"transition\":\"slide\"}","description":"2023/03/28 @kdnakt","contributors":"[{\"id\":\"df36d0f0-b67e-41ac-96b3-f3988326d230\",\"add\":6975,\"del\":395}]"}