【IT初心者向け】sedの後方参照についてわかりやすく解説してみた

## はじめに
こんにちは！今回はLinuxの**sed**コマンドについてです！
**`sed`**を調べたときに一度は目にするであろう**後方参照**について、なるべくわかりやすく解説できればと思います！

なお今回の記事は**<font color=red>「`cat`コマンドを使ったことがある」</font>**ことを前提としていますので、あらかじめご了承ください。
（後半の章では`grep`や「正規表現」を使うので、これらを知っていればなお良いです。<font color=Silver>一応簡単な解説は載せています</font>）

本稿の内容は大きく分けて、
　　**・[sedの基本的な機能](#sedの基本的な使い方)**
　　**・[後方参照の書き方](#後方参照の書き方)**
　　**・[`\1`とか`\2`の違い](#\1と\2の違い)**
　　**・[「後方参照」という言葉の意味](#後方参照という言葉の意味)**
　　**・[具体的な使用例](#具体的な使い方)**

となっています。
それでは本題に入ります！

<font color=vioret>（※「￥」マークと「\」マークは、**同じ記号**と考えて頂いてOKです。）</font>

## sedの基本的な使い方
今回は下図の**test_data.txt**を使って説明していきたいと思います。
「**<font color=red>ABCDEF</font>**」の部分に着目しながら読み進めていって下さい！
>`cat`より右側の部分は「**<font color=red>ABCDEF</font>**に色を付けるためのただの**おまじない**」なので、気にしなくて大丈夫です。**もう二度と出てきません！**

![sed command1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650537/a3b2a9b8-3c84-700a-0b2c-c46cdefa65bc.png)

**sed**コマンドを使うときは、`sed 's/〇〇〇/△△△/g' いじくりたいファイル名`の形が基本となります。
これによって、ファイル内の**全ての行で、<font color=red>「〇〇〇」が</font><font color=blue>「△△△」</font><font color=red>に変換</font>**されます。

![sed command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650537/f73f6df1-4a3c-82a2-8e0d-188dab3cdb24.png)

このように**`sed`**というコマンドは

###　「<font color=red>左で書いた文字列</font>を<font color=blue>右で書いた文字列</font>に変換する」

という機能をもつという事を頭の隅に入れておいてください！

>・ちなみに`sed 's/〇〇〇/△△△/g' いじくりたいファイル名`は、
`cat いじくりたいファイル名 | sed 's/〇〇〇/△△△/g'`
と書いても同じです。
・より詳しい`sed`の使い方はこちらをご参照ください。[sedコマンドの備忘録](https://qiita.com/takech9203/items/b96eff5773ce9d9cc9b3)

## 後方参照の書き方
**後方参照**という言葉の意味は、**sed**における**`\1`**や**`\2`**の意味を理解してからの方がイメージしやすいかと思うので、いったん置いておいてください！

「**後方参照**」の使いどころは様々ですが、今回は
####「**<font color=red>指定した位置</font>**に、<font color=blue>**自分が入れたい文字列**</font>を**挿入する**」
場合をご説明します。
<font color=LightSkyBlue>（文字列を「置換」するのではなく「**挿入**」する。）</font>

---
この場合の「**後方参照**」の書き方ですが、基本的には冒頭で述べた
`sed 's/〇〇〇/△△△/g' いじくりたいファイル名`
の形がベースとなります。

違いは次の3点です。<font color=DodgerBlue>
1.　△△△の前に「**`\1`**」をつける。
2.　〇〇〇を**`(`**と**`)`**で囲む。（もしくは**`\(`**と**`\)`**で囲む）
3.　**`sed`**の後ろに**`-r`**をつける。（2を後者にした場合はいらないです）</font>
<br>以上3点を踏まえた書き方が以下の通りです。
`sed -r 's/(〇〇〇)/\1△△△/g' いじくりたいファイル名`
または
`sed 's/\(〇〇〇\)/\1△△△/g' いじくりたいファイル名`

これによって、
#### 「<font color=red>〇〇〇</font>を<font color=blue>△△△</font>に変換する」
という機能から、
#### 「<font color=red>〇〇〇</font>の後ろに<font color=blue>△△△</font>を挿入する」
という機能に切り替わります。
![sed command3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650537/07658611-d975-97e0-b8e0-e74dfe467deb.png)

ここまでの内容で「やりたいことができそうだ」という方は、[具体的な使い方](#具体的な使い方)も参考にしながら自分のデータに**`sed`**を利用して頂ければと思います！

## `\1`と`\2`の違い
**sed**の後方参照について調べた方の中には、もしかしたら`\1`とか`\2`といった数字を見かけた方もいるかもしれません。
つぎはこの`\1`や`\2`の意味についてご説明したいと思います。


### ◆`\1`について

まず`\1`にだけ着目して話を進めます。
この`\1`の意味を理解するためには、次のような実験をするとわかりやすいです。

```sh
$ sed -r 's/(ABCDEF)/\1~~~/g' test_data.txt
aaabbbcccABCDEF~~~ggg
aaabbbeeeABCDEF~~~ggg
aaabbbeeeABCDEF~~~ppp
abcabcabcABCDEF~~~ppp
abcabcabcABCGEFyyy
sdsdsdsdsABCDEF~~~yyy

$ sed -r 's/(ABCDEF)/ \1 ~~~/g' test_data.txt   ##右側の\1の両側に半角スペースを入れた
aaabbbccc ABCDEF ~~~ggg
aaabbbeee ABCDEF ~~~ggg
aaabbbeee ABCDEF ~~~ppp
abcabcabc ABCDEF ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds ABCDEF ~~~yyy
```
この結果をみて勘づいた方もいらっしゃるかと思いますが、この結果はつまり
<font color=red>**「右側の`\1`の部分に、左側の`ABCDEF`が代入されている」**</font>
ことを表しています！

つまりこの**後方参照**というのは
**「`ABCDEF`を`ABCDEF ~~~`に変換する」**
ということを行う事で、
結果的に**「ABCDEFの後ろに` ~~~`が<font color=red>挿入されているように見えていた</font>」**だけ
なのです。

以上が`\1`の意味です。つまり`\1`は、
**「左側の`(` `)`に入っている文字列を、<font color=red>代入するための箱</font>」**
だということがわかりました。

---

### ◆`\2`について
まず前提として、`\2`を使うためには左側の`(` `)`を**2つ設ける**必要があります。

下に具体的な出力結果の例を示します。
おそらく実際に結果を見て頂いた方が理解しやすいかと思うので、コードが少し長くなりますが何卒お付き合いください。

```sh
$ sed -r 's/(ABC)(DEF)/ \1 \2 ~~~/g' test_data.txt
aaabbbccc ABC DEF ~~~ggg
aaabbbeee ABC DEF ~~~ggg
aaabbbeee ABC DEF ~~~ppp
abcabcabc ABC DEF ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds ABC DEF ~~~yyy

$ sed -r 's/(ABC)(DEF)/ \1 ~~~/g' test_data.txt
aaabbbccc ABC ~~~ggg
aaabbbeee ABC ~~~ggg
aaabbbeee ABC ~~~ppp
abcabcabc ABC ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds ABC ~~~yyy

$ sed -r 's/(ABC)(DEF)/ \2 ~~~/g' test_data.txt
aaabbbccc DEF ~~~ggg
aaabbbeee DEF ~~~ggg
aaabbbeee DEF ~~~ppp
abcabcabc DEF ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds DEF ~~~yyy

$ sed -r 's/(ABC)(DEF)/ \1 \2 \1 ~~~/g' test_data.txt
aaabbbccc ABC DEF ABC ~~~ggg
aaabbbeee ABC DEF ABC ~~~ggg
aaabbbeee ABC DEF ABC ~~~ppp
abcabcabc ABC DEF ABC ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds ABC DEF ABC ~~~yyy

$ sed -r 's/(ABC) (DEF)/ \1 \2 \1 ~~~/g' test_data.txt  ##左側でスペースを入れると何も変化しなくなる
aaabbbcccABCDEFggg
aaabbbeeeABCDEFggg
aaabbbeeeABCDEFppp
abcabcabcABCDEFppp
abcabcabcABCGEFyyy
sdsdsdsdsABCDEFyyy

$ sed -r 's/(AB)CD(EF)/ \1 \2 \1 ~~~/g' test_data.txt
aaabbbccc AB EF AB ~~~ggg
aaabbbeee AB EF AB ~~~ggg
aaabbbeee AB EF AB ~~~ppp
abcabcabc AB EF AB ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds AB EF AB ~~~yyy
```

`\3`も使って書いてみます。

```sh
$ sed -r 's/(AB)(CD)(EF)/ \1 \2 \3 ~~~/g' test_data.txt
aaabbbccc AB CD EF ~~~ggg
aaabbbeee AB CD EF ~~~ggg
aaabbbeee AB CD EF ~~~ppp
abcabcabc AB CD EF ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds AB CD EF ~~~yyy

$ sed -r 's/(AB)(CD)(EF)/ \1 \3 \2 ~~~/g' test_data.txt
aaabbbccc AB EF CD ~~~ggg
aaabbbeee AB EF CD ~~~ggg
aaabbbeee AB EF CD ~~~ppp
abcabcabc AB EF CD ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds AB EF CD ~~~yyy

$ sed -r 's/(AB(C(DEF)))/ \1 \2 \3 ~~~/g' test_data.txt
aaabbbccc ABCDEF CDEF DEF ~~~ggg
aaabbbeee ABCDEF CDEF DEF ~~~ggg
aaabbbeee ABCDEF CDEF DEF ~~~ppp
abcabcabc ABCDEF CDEF DEF ~~~ppp
abcabcabcABCGEFyyy
sdsdsdsds ABCDEF CDEF DEF ~~~yyy
```

いかがでしょうか。ここまでの結果を踏まえて、ご説明させて頂きます。

**`sed`**が**「<font color=red>左で書いた文字列</font>を<font color=blue>右で書いた文字列</font>に変換する」**コマンドだったことを、いま一度思い出しながら読んでいただければと思います。

---

まず、これらの結果は基本的には**左側の`ABCDEF`をどういう風に変換するか**（置換するか）が違うだけです。
途中何も変化しない例があったのは、<font color=red>**`ABC DEF`という文字列がファイル内のどこにも存在していなかったから**</font>というわけです。

また、`\1` `\2` `\3`にはそれぞれ、左側の`1番目` `2番目` `3番目`の`(` `)`の中身が代入されています。

なので例えば一番最後から2番目、**8番目の例**では`\1`に**`AB`**、`\2`に**`CD`**、`\3`に**`EF`**が代入されているので、`ABCDEF`が**` AB EF CD ~~~`**に変換されていますし、
**6番目の例**では、`\1`に**`AB`**、`\2`に**`EF`**が代入されているので、`ABCDEF`が**`AB EF AB ~~~`**に変換されています。

また、最後の例では`\1`に**`ABCDEF`**、`\2`に**`CDEF`**、`\3`に**`DEF`**が代入されていることから、**左側の`(` `)`は入れ子にできる**ことがわかります。

<br>
つまり話をまとめると、`\1` `\2` `\3` ... `\N`は、
**「左側の<font color=Silver>（`(`で見て）</font><font color=red>N番目</font>の`(` `)`の中身を、<font color=red>代入するための箱</font>」**
だということです！

## 「後方参照」という言葉の意味
**`\1`**や**`\2`**の意味はイメージできましたでしょうか。
前置きが長くなりましたが、ここでようやく「**後方参照**」という言葉の意味に触れたいと思います。

**後方参照**とはざっくりいうと、
**「`(` `)`で囲んだ部分を、後ろの方で参照して使う」**機能のことを言います。
`(` `)`で囲んだ部分を参照する場合は上の章で出てきた`\1`や`\2`を使います。
<font color=Silver>（ちなみにややこしい話なのですが、ここでいう「後方」とは`(` `)`の中のこと（＝文頭方向の文字列）を言います。`(` `)`の中身を参照するので、「後方参照」といいます）</font>


例えば、下の`sed`の例でいえば、
`(` `)`で囲んだ**`ABCDEF`**を、後ろの**`\1`**で参照しています。

```sh
$ sed -r 's/(ABCDEF)/\1~~~/g' test_data.txt
aaabbbcccABCDEF~~~ggg
aaabbbeeeABCDEF~~~ggg
aaabbbeeeABCDEF~~~ppp
abcabcabcABCDEF~~~ppp
abcabcabcABCGEFyyy
sdsdsdsdsABCDEF~~~yyy

## 左側でも使用可能。
$ sed -r 's/((a)\2\2)/\1---/g' test_data.txt
aaa---bbbcccABCDEFggg
aaa---bbbeeeABCDEFggg
aaa---bbbeeeABCDEFppp
abcabcabcABCDEFppp
abcabcabcABCGEFyyy
sdsdsdsdsABCDEFyyy
```

またこの**後方参照**は、Linux以外の言語や、Linuxの`sed`以外のコマンドでも利用することができます。

例えば`grep`コマンドを使った下の例でいうと、<font color=Silver>（`grep`は " 指定した文字列 " を含んでいる行を出力するコマンドです(=文字列検索)）</font>

```sh
$ cat test_data2.txt
1cccABCDEFgggABCDEFyyy
2eeeABCDEFgggAB--EFggg
3eeeABCDEFpppABCDEFppp
4abcABCDEFppp------yyy
5abc------yyyABCDEFyyy

$ grep -E "(ABCDEF)...\1" test_data2.txt 　 ## 「.」は「任意の1文字」を表す
1cccABCDEFgggABCDEFyyy
3eeeABCDEFpppABCDEFppp
```
のように、`(` `)`で囲んだ**`ABCDEF`**を、**`\1`**で参照することができます。（もちろん`\2`や`\3`も、`grep`で利用可能です）

これが**後方参照**のざっくりとした内容です。
注意点として、例えば`(` `)`の中身に複数候補が該当できる場合でも、`(` `)`で指定した中身と`\1`の中身は同一のものになります。（**`(` `)`の中身と`\1`の中身が違う文字列を表すということはない**）

```sh
$ cat test_data3.txt
1xxxABCyyyABCzzz
2xxxABCyyyABD
3xxxABCyyyABDzzzABD
4xxxABCyyyABDzzzABC
5xxxABDyyyABC
6xxxABDyyyABDzzz

$ grep -E '(AB[CD]).*\1' test_data3.txt    ##  AB[CD] と \1 は同一の文字列を表す
1xxxABCyyyABCzzz
3xxxABCyyyABDzzzABD
4xxxABCyyyABDzzzABC
6xxxABDyyyABDzzz

$ grep -E '(AB[CD])(y|z).*\2\1' test_data3.txt    ## \2 も同様
1xxxABCyyyABCzzz
3xxxABCyyyABDzzzABD
6xxxABDyyyABDzzz
```

ここでの**後方参照**に関する説明は、以上となります。
お疲れ様でした！

>・この章を書く上で以下のサイトにお世話になりました。
[「正規表現と後方参照」～マンガでプログラミング用語解説](https://codezine.jp/article/detail/8497)
（閲覧するには無料会員登録が必要です。）

## 具体的な使い方
最後にいくつかの使用例を挙げて本稿を締めさせて頂きたいと思います。
これらの具体的な書き方を参考にしつつ、ご自身のシチュエーションに合った使い方をしていただければと思います。

ご精読ありがとうございました！🙇‍♂️

### 通常の`sed`を用いた例
・特定の文字列に **""** をつける

```sh
$ sed 's/ABCDEF/"ABCDEF"/g' test_data.txt
aaabbbccc"ABCDEF"ggg
aaabbbeee"ABCDEF"ggg
aaabbbeee"ABCDEF"ppp
abcabcabc"ABCDEF"ppp
abcabcabcABCGEFyyy
sdsdsdsds"ABCDEF"yyy
```

・特定の文字列を消す（右側に何も書かない）

```sh
$ sed 's/ABCDEF//g' test_data.txt
aaabbbcccggg
aaabbbeeeggg
aaabbbeeeppp
abcabcabcppp
abcabcabcABCGEFyyy
sdsdsdsdsyyy
```

### `\1`を使った例
・先頭から5文字目の後ろに - を挿入（正規表現を使用）
`-r`を使わない場合は、`(` `)`を`\(` `\)`、`{` `}`を`\{` `\}`にする。

```sh
$ sed -r 's/(^.{5})/\1-/g' test_data.txt
aaabb-bcccABCDEFggg
aaabb-beeeABCDEFggg
aaabb-beeeABCDEFppp
abcab-cabcABCDEFppp
abcab-cabcABCGEFyyy
sdsds-dsdsABCDEFyyy

$ sed 's/\(^.\{5\}\)/\1-/g' test_data.txt   ## -r をつけない場合
aaabb-bcccABCDEFggg
aaabb-beeeABCDEFggg
aaabb-beeeABCDEFppp
abcab-cabcABCDEFppp
abcab-cabcABCGEFyyy
sdsds-dsdsABCDEFyyy
```

>**（補足）**
**正規表現**とは特殊な意味を持つ文字（メタ文字という）を使ってコマンドを書くこと。（**A**とか**b**とかは**ただの文字**ですが、**`^`**とか**`.`**は**特別な意味を持つ文字**です）
**`^`**：「先頭の」、**`.`**：「任意の文字」、**`{`** n **`}`**：「連続するn文字」
という意味なので、これらを組み合わせた**`^.{5}`**は「**先頭の任意の5文字**」を表します。（詳しくはまた別の機会に。）

・特定の文字列に **""** をつける（`\1`を使えばコンパクトに書けます）

```sh
$ sed -r 's/(ABCDEF)/"\1"/g' test_data.txt
aaabbbccc"ABCDEF"ggg
aaabbbeee"ABCDEF"ggg
aaabbbeee"ABCDEF"ppp
abcabcabc"ABCDEF"ppp
abcabcabcABCGEFyyy
sdsdsdsds"ABCDEF"yyy
```

・**`sed`**を複数回使う

```sh
$ cat test_data.txt | sed -re 's/(^.{5})/\1-/g' | sed -re 's/(ABCDEF)/"\1"/g'
aaabb-bccc"ABCDEF"ggg
aaabb-beee"ABCDEF"ggg
aaabb-beee"ABCDEF"ppp
abcab-cabc"ABCDEF"ppp
abcab-cabcABCGEFyyy
sdsds-dsds"ABCDEF"yyy
```

>**（補足）**
・複数個の処理を連動させたい場合は、**`|`** (「Shift」+「￥」)を使います。`|`は左で行った処理を、右に持ち越す機能があります。
今回はこれによって最終的に「-」と「" "」を両方挿入した結果が出力されます。
・`sed`の結果を`|`の向こうに持ち越すためには`-e`を付ける必要があります。今回は`-r`と`-e`を使っているので、あわせて`-re`としています。ちなみに`-er`ではうまくいきません。

### `\2`を使った例
・文字列の位置を入れ替える（連続した文字列）
`ABC`(**\1**) `DEF`(**\2**)を、`DEF`(**\2**) `ABC`(**\1**)に置換しています。

```sh
$ sed -r 's/(ABC)(DEF)/\2\1/g' test_data.txt
aaabbbcccDEFABCggg
aaabbbeeeDEFABCggg
aaabbbeeeDEFABCppp
abcabcabcDEFABCppp
abcabcabcABCGEFyyy
sdsdsdsdsDEFABCyyy
```

・内側の文字列を置換する（「aaa … ppp」の文字列のみ）

```sh
$ sed -r 's/^(aaa).*(ppp)$/\1---\2/g' test_data.txt
aaabbbcccABCDEFggg
aaabbbeeeABCDEFggg
aaa---ppp
abcabcabcABCDEFppp
abcabcabcABCGEFyyy
sdsdsdsdsABCDEFyyy
```
>**（補足）**
**`*`**もメタ文字の一種で、「0文字以上連続している」ことを表します。
つまり**`.*`**は（何もないのも含め）「**あらゆる文字列**」を表します。（※出力結果は最長のモノが選ばれます）
また、**`^`**は「先頭の」、**`$`**は「末尾の」という意味のメタ文字なので、これによって、「**aaa**で始まり、**ppp**で終わる行」だけが選択されます。