【Ruby】改行コードの種類に影響されずに、行頭行末の空白を削除する

## 概要
Ruby on Railsで開発しているときに、複数行ある文字列の行頭行末の空白を削除したいときがありました。

しかし、正規表現メタ文字の`^`と`$`にマッチさせるやり方ではうまくいかず、正規表現の先読み・後読みを使う必要がありました。

今回は、同じような悩みを抱えている人向けに、Rubyで行頭行末の空白を削除する方法をお伝えします。

## この記事で伝えたいこと

- Rubyにおける、一般的な行頭行末の空白を削除する方法の問題点
- 改行コードの種類によって生まれてしまう、行頭行末の空白を削除したときの挙動の違い
- 改行コードの種類に影響されずに、行頭行末の空白を削除する方法

## 結論

改行コードの種類に影響されずに行頭行末の空白を削除するためには、`^`と`$`を使うだけではなく、以下のように正規表現の先読み・後読みも使う必要があります。

```ruby:
# 行頭の空白を削除
.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')

# 行末の空白を削除
.gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')
```

なぜこのようなめんどくさいコードになったのか説明していきます。

## 何を目指したかったのか？

以下の3つの文字列を例にします。
```ruby:index.rb
hoge_fuga_1 = "  hoge fuga     \n　　　hoge fuga　　　\n　　hoge fuga   \n     "
hoge_fuga_2 = "  hoge fuga     \r　　　hoge fuga　　　\r　　hoge fuga   \r     "
hoge_fuga_3 = "  hoge fuga     \r\n　　　hoge fuga　　　\r\n　　hoge fuga   \r\n     "

# => "  hoge fuga     \n" + "　　　hoge fuga　　　\n" + "　　hoge fuga   \n" + "     "
# => "  hoge fuga     \r　　　hoge fuga　　　\r　　hoge fuga   \r     "
# => "  hoge fuga     \r\n" + "　　　hoge fuga　　　\r\n" + "　　hoge fuga   \r\n" + "     "
```
それぞれの文字列は、改行コードの種類が違う[^1]こと以外は全く同じです。

文字列には、行頭・行末・行の途中に空白がふくまれています。

今回は以下のように、行頭行末の空白だけ削除することを目指していました。
```ruby:index.rb
hoge_fuga_1_after = "hoge fuga\nhoge fuga\nhoge fuga\n"
hoge_fuga_2_after = "hoge fuga\rhoge fuga\rhoge fuga\r"
hoge_fuga_3_after = "hoge fuga\r\nhoge fuga\r\nhoge fuga\r\n"

# => "hoge fuga\n" + "hoge fuga\n" + "hoge fuga\n"
# => "hoge fuga\rhoge fuga\rhoge fuga\r"
# => "hoge fuga\r\n" + "hoge fuga\r\n" + "hoge fuga\r\n"
```

では、実際に失敗例から試してみましょう。

## 【失敗例】^と$を使って行頭行末の空白を削除する
Rubyにおいて、行頭行末の空白文字を削除するためには正規表現メタ文字の`^`（行頭）と`$`（行末）を使うのが一般的です。

そのため、次のようなコードをまず考えました。
### 行頭行末の空白を削除するコード例
```ruby:
# 行頭の空白を削除する
.gsub(/^( |　)+/, '')
# 行末の空白を削除する
.gsub(/( |　)+$/, '')
```
`( |　)+`は「半角スペースもしくは全角スペースが1文字以上続く」という意味です。

そのため、`( |　)+`と行頭を表す`^`、行末を表す`$`を組み合わせることによって、以下のように行頭行末の空白にマッチさせることができます。
```ruby:
# 行頭から一文字以上続く空白にマッチ
/^( |　)+/

# 行末まで一文字以上続く空白にマッチ
/( |　)+$/
```
この正規表現をgsubメソッドの第一引数に指定し、第二引数で`''`を指定することで、空白を削除することができます。

では、gsubメソッドを使って、複数行ある文字列の行頭行末の空白を削除してみましょう。




### ^と$を使って行頭行末の空白を削除してみた結果
先ほど紹介したgsubメソッドを使って、結果を確認してみましょう。
```ruby:index.rb

hoge_fuga_1 = "  hoge fuga     \n　　　hoge fuga　　　\n　　hoge fuga   \n     "
hoge_fuga_2 = "  hoge fuga     \r　　　hoge fuga　　　\r　　hoge fuga   \r     "
hoge_fuga_3 = "  hoge fuga     \r\n　　　hoge fuga　　　\r\n　　hoge fuga   \r\n     "

hoge_fuga_1.gsub(/^( |　)+/, '')
           .gsub(/( |　)+$/, '')
hoge_fuga_2.gsub(/^( |　)+/, '')
           .gsub(/( |　)+$/, '')
hoge_fuga_3.gsub(/^( |　)+/, '')
           .gsub(/( |　)+$/, '')

# => "hoge fuga\n" + "hoge fuga\n" + "hoge fuga\n"
# => "hoge fuga     \r　　　hoge fuga　　　\r　　hoge fuga   \r"
# => "hoge fuga     \r\n" + "hoge fuga　　　\r\n" + "hoge fuga   \r\n"
```

すると...

なんということでしょう...

改行コードが`\n`のときは、想定どおり行の途中の空白は残しつつ、行頭行末の空白を削除することができています。

しかし、`\r`と`\r\n`のときは、あってはいけない空白がしっかりこの目に見えます。

今回の結果をまとめると以下のようになります。


| 改行コード | 結果 |
|:-:|:-:|
|\n   |全ての行頭・行末の空白が意図通りに削除される|
|\r   |文頭・文末の空白のみ削除され、<INS>行頭・行末の空白は削除されない</INS>|
|\r\n   |文頭・行頭・文末の空白が削除され、<INS>行末の空白は削除されない</INS>|


`^`と`$`を使っても、改行コードが`\n`のとき以外は正しく挙動しないということになります。

なぜこのような挙動になるのでしょうか？

実は、使用されている改行コードの種類に原因があります。

## なぜ改行コードの種類によって挙動がちがうのか？
改行コードの種類は前述の3種類がありますが、実はRubyで改行コードとして認識されるのは`\n`のみです。

これは、Rubyの公式リファレンスを確認するとわかります。


> バックスラッシュ記法
> 文字列中でバックスラッシュ(環境によっては￥記号で表示されます)の後に記述する文字によっては特別な意味を持たせる事ができます。
> \n 改行(0x0a)

[ruby3.2リファレンスマニュアル バックスラッシュ記法](https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#backslash "ruby3.2リファレンスマニュアル")

つまり、Rubyが以下の`^`と`$`で説明している改行とは、`\n`のことを意味します。
> - ^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
> - $ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。

[ruby3.2リファレンスマニュアル　正規表現>アンカー](https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html#anchor "ruby3.2リファレンスマニュアル")


そのため、`\r`は改行コードとして認識されず、`\r\n`は`\n`の部分のみ改行コードとして認識されています。

以上を踏まえると、以下の通り行頭行末の空白を削除したときの挙動を説明することができます。

### 1.行頭の空白を削除したときの挙動の違い

`.gsub(/^( |　)+/, '')`を使用したときは、改行コードの種類によって3種類の挙動が発生します。

| 改行コード | 挙動 |
|:-:|:-:|
|\n   |`\n`が改行コードとして認識され、行頭の空白が削除される。|
|\r   |`\n`が存在しないため、<INS>文字列は改行していると認識されず、行頭の空白は削除されない。</INS>|
|\r\n   |`\r\n`の`\n`部分が改行コードとして認識される。その結果、`\n`以降の行頭にマッチする空白が削除される。|


### 2.行末の空白を削除したときの挙動の違い
`.gsub(/( |　)+$/, '')`を使用した場合、改行コードの種類によって3種類の挙動が発生します。
| 改行コード | 挙動 |
|:-:|:-:|
|\n   |空白が`\n`の直前まで続くため、空白が削除される。|
|\r   |`\n`が存在しないため、<INS>文字列は改行していると認識されず、行末の空白は削除されない。</INS>|
|\r\n   |空白としても改行コードとしても認識されない`\r`が`\n`の手前に存在する。そのため、<INS>行末まで空白が続いているとは認識されず、行末の空白は削除されない。</INS>|




## 【成功例】先読み・後読みを使って行頭行末の空白を削除する
ではどうすれば、改行コードの種類に影響されずに、行頭行末の空白を削除することができるのでしょうか？

結論から述べると、正規表現の先読み・後読みを使って以下のように記述します。

そうすると、改行コードの種類に影響されずに行頭行末の空白が削除できます。

### 行頭行末の空白を削除するコード例（改）
```ruby:
# 行頭の空白を削除
.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')

# 行末の空白を削除
.gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')
```
### 正規表現の先読み・後読みを使って、行頭行末の空白を削除してみた結果
これらを使用すると以下の結果になります。
```ruby:index.rb
hoge_fuga_1 = "  hoge fuga     \n　　　hoge fuga　　　\n　　hoge fuga   \n     "
hoge_fuga_2 = "  hoge fuga     \r　　　hoge fuga　　　\r　　hoge fuga   \r     "
hoge_fuga_3 = "  hoge fuga     \r\n　　　hoge fuga　　　\r\n　　hoge fuga   \r\n     "


hoge_fuga_1.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')
           .gsub(/[ |　]+($|(?=\n|\r|\r\n))/, '')
hoge_fuga_2.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')
           .gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')
hoge_fuga_3.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')
           .gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')

# => "hoge fuga\n" + "hoge fuga\n" + "hoge fuga\n"
# => "hoge fuga\rhoge fuga\rhoge fuga\r"
# => "hoge fuga\r\n" + "hoge fuga\r\n" + "hoge fuga\r\n"
```

ちゃんと全ての改行コードのパターンで、行頭行末の空白が削除できましたね！

行の途中の空白もしっかりと残っています！

バッチリですね！

### 1.行頭の空白を削除する（改）
```ruby:
.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')
```
では、改良したコードを簡単に説明します。

`^`を、`(^|(?<=\n|\r|\n\r))`に変更しました。

`^`または、`(?<=\n|\r|\n\r)`の直後に該当する空白という意味になります。

`(?<=\n|\r|\n\r)`は正規表現の後読みと呼ばれる書き方の一つで、`\n`・`\r`・`\r\n`のどれかに該当したら、その直後からの文字について、正規表現にマッチする文字列を探します。

そのため、`^`（行頭）または改行コードのどれかにマッチしたら、その直後から１文字以上続く空白を探し、マッチした空白を削除してくれます。

### 2.行末の空白を削除する（改）
```ruby:
.gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')
```
`$`を、`($|(?=\n|\r|\r\n))`に変更しました。

`$`または、`($|(?=\n|\r|\r\n))`の直前に該当する空白という意味になります。

`($|(?=\n|\r|\r\n))`は正規表現の先読みと呼ばれる書き方の一つで、`\n`・`\r`・`\r\n`のどれかに該当したら、その直前より前の文字列について、正規表現にマッチする文字列を探します。

そのため、`$`（行末）もしくは改行コードのどれかにマッチしたら、その直前まで１文字以上続く空白を探索し、マッチした空白を削除してくれます。

## どのようなときに活用できるのか？
Ruby on Railsでは、テキストエリア内で改行すると、改行コードは`\r\n`になります。

そのため、テキストエリアに入力した文章について、行頭行末の空白を削除したいなという時は、今回の方法を活用することができます。

## まとめ
Rubyにおける改行コードは`\n`です。

そのため、`\n`以外の改行コードを含む文字列について、行頭行末の空白文字を削除したいときは、

- 行頭の空白を削除 `.gsub(/(^|(?<=\n|\r|\n\r))( |　)+/, '')`
- 行末の空白を削除 `.gsub(/( |　)+($|(?=\n|\r|\r\n))/, '')`

を使うと良いことがわかりました。




[^1]:改行コードはOSの種類によって、`\r`・`\n`・`\r\n`の3種類があります。`\r`はMac OS 9以前で、`\n`はUNIX系、macOS（X以降）、iOS、Androidで、`\r\n`はWindowsで使われています。