空白文字(スペース)の注意点

# はじめに
空白文字(スペース)は基本は単語の切れ目やコマンドの区切りとして使用される、MarkDownでは行末に空白2つを入れると改行に変換される。

**空白文字はコンピューターで使用する上では意外と厄介者である。**

自分が体験した空白があることによる問題について幾つか列挙してみます。

# フォルダ名に空白
Windows上でフォルダ名に空白が含まれるのに`Program Files`や`Program Files (x86)`や`Documents and Settings`が有名である。
`Documents and Settings`フォルダは、Windows7以降では保護対象となり自動的に`Users`フォルダに置き換える仕組みとなった。
`Program Files`は単語としては`Program`と`Files`は別物なので空白を入れたのかも知れないが、Vistaから追加された`ProgramData`フォルダにはスペースが入っていない。

## 末尾空白がある場合
Windowsでファイル検索する際に末尾空白を除去して検索するようになっているため、ファイルやフォルダの末尾に空白(全角空白も同様)があった場合、見つからないと判断されます。そのため、削除が出来なくなるなどの問題が発生します。
このことは「フォルダ名 末尾 空白」でネット検索すれば、それなりに記事に見つかります。

以前、ユーザーからバックアップが出来ないと問い合わせがあり、キャプチャ画像を頂いたのですが原因が分からず、フォルダコピーをもらってやっと原因が分かりました。画像だけでは末尾空白があるか判断できなかった。
[「ファイル ‘～’ が見つかりませんでした。」「 ディレクトリが空ではありません。」エラー原因](https://qiita.com/yaju/items/96f67797ac1f6fcd3c87)

# バッチ作成時の空白
## フォルダの指定
バッチを作成する場合、`Program Files`のように空白を含むフォルダを指定する場合にはダブルクォーテーションで囲む必要がある。

```bat
Set InstallDir="D:\Program Files (x86)\Trend Micro\OfficeScan"
```

後でファイル名を追記したい場合、ダブルクォーテーションで囲まない変数を作成して、ファイル名を含めてダブルクォーテーションで囲む。

```bat
Set InstallDir=D:\Program Files (x86)\Trend Micro\OfficeScan
echo F|XCopy "%InstallDir%\PCCSRV\Admin\ssnotify.ini" PCCSRV\Admin\ /S /Y /I /D
```

## パラメータ渡し
空白含む内容のパラメータを渡す際、「~ (チルダ)」を%の直後に付けると「"」を除去することが出来ます。例 「%~2」

```bat
Set InstallReg=HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\TrendMicro
REM レジストリのバックアップ
call :regSub Database_Backup.reg "Database Backup"
 
:regSub
regedit /E %1 "%InstallReg%\%~2" >> regexport.log 2>&1
exit /b
```

## 構文エラー
バッチを作成した際に「コマンドの構文が誤っています。」のエラーで悩んでいたところ、"("の直前にスペースが無いことが原因でした。

```bat
IF "%ERRORLEVEL%" == "0" (
```

# シェルスクリプト
>シェルスクリプト、特にBash(中略)
>i = 0のように空白を開ければエラーになるし、かといってif[$i!=0]のように詰めてもやっぱりエラーになる
>[プログラマーの君！ 勘違いするな！ シェルスクリプトでは読みやすさのためにスペースを置くな！！ という話](https://qiita.com/piroor/items/0cb44663430bfc66c204)

* 空白を開けてはいけない：`i=0`のような変数の代入文
* 空白を開けなければいけない：`if [ $i != 0 ]`のような条件分岐や制御構造

[シェルスクリプトは変数代入で = の前後にスペースを置けない！･･･の本当の理由を知ると優れた文法が見えてくる](https://qiita.com/ko1nksm/items/9650ed1fc21d668f2732)

# SQL-92の仕様
## WHERE条件時の末尾空白無視
### 現象
Oracleで実行した更新系SQLをSQLServerの更新系SQLで実行してデータ移行するレプリケーション機能を使った際、WHERE条件時に末尾空白があったことで失敗する現象が発生した。

```sql
DELETE FROM HOGE WHERE AA='TEST ' -- 末尾空白有り
UPDATE HOGE SET AB='FUGA' WHERE AA='TEST'　-- 末尾空白無し
```

OracleではHOGEテーブルのAA列に末尾空白のある'TEST 'のDELETEが実行され、次の末尾空白が無い'TEST'のUPDATEが正常に更新される。
しかし、SQLServerでは末尾空白のある'TEST 'と末尾空白が無い'TEST'がDELETEで削除されてしまい、次のUPDATEでデータが存在せずに失敗してしまう。

### 原因
SQLServerの可変長文字列(varchar / nvarchar)は末尾空白をセットすることが出来るが、「比較では後続の空白(全角空白も同様)は無視される」仕様となっている。
これは、ANSI/ISO SQL-92 specification にのっとった仕様でバグではありません。MySQLも同様に末尾空白は無視されます。
**※Oracleのvarchar2型、PostgreSQLのvarchar型は末尾空白を無視しない。**

[INF: SQL Server が末尾のスペースを含む文字列を比較方法](https://support.microsoft.com/ja-jp/help/316626/inf-how-sql-server-compares-strings-with-trailing-spaces)

### 対応
比較する前にバイナリ型に変換「CONVERT(varbinary, column-name)」することで末尾空白を無視しないように出来る。
実際の対応としては、アプリケーション側で末尾空白が入らないように改修した。

# XMLで空白処理
Classic aspで、XMLのPOSTリクエストを行うアプリケーションを作成した際に、ユーザーから空白が含まれる文字列だと照合が一致しないという報告を受けた。
調べてみると、POSTリクエストを行うと空白が削除されてしまうので、空白は「+」または「%20」で置換する必要があった。

* [POSTパラメータの空白はエスケープしないと削除される（MSXML2.ServerXMLHTTP）](http://qiita.com/noobar/items/614f03663b812afbb3ea)
* [技術/HTTP/URLエンコードで 0x20(スペース) を "+" にすべきか "%20" にすべきか](http://www.glamenv-septzen.net/view/1170)
* [情報セキュリティ技術動向調査 URI のエスケープ](https://www.ipa.go.jp/security/fy21/reports/tech1-tg/b_09.html)

# 固定長ファイルでの照合方法
以前、固定長ファイルを扱うアプリケーションの開発を行った。この時の一部の技術は記事「[【.NET】固定長ファイル(複数行一組対応)の読み込み](https://qiita.com/yaju/items/02bfc427dee41678771a)」に書いた。

今回は仕様の問題である。下記の管理Noの3パターンがあった場合、空白を除去した上で照合するのか、それとも空白含めて照合するのか。
照合方法の仕様を詰めておかないと、後で修正するはめになる。

その時の決まった仕様では、左詰であることを前提に右空白除去して照合を行うようにした。

```bat
管理No
"JJ     "  ①
"     JJ"  ②
"  JJ   "  ③
```

# 「＆nbsp;」は半角スペースではない
HTMLを書く際に半角スペースは「&amp;nbsp;」の特殊文字にすると習ってから使用し続けてきましたが、&amp;nbspは、Non-Breaking SPaceの省略で英文とかで「ここの空白では改行したくない」という時に使うもの。
 [&amp;nbsp; は半角スペースではないというお話](https://blog.fenrir-inc.com/jp/2011/06/post_51.html)
英語圏では単語の区切りに半角スペースを使いますが、日本語を扱う私達と感覚が違うので気にしなくてもいいかも知れませんね。

下記サイトを見ると分かりますが、空白文字を使用した場合は単語の切れ目で自動改行されますが、&amp;nbsp;ではNon-Breaking SPaceの省略ということで単語区切りで一切改行されません。
[空白文字「 」と「&amp;nbsp;」との違い](https://allabout.co.jp/gm/gc/23899/file/sample.html)

# 半角空白と全角空白
SQL-92の仕様のところで上述したが、対応としてアプリケーション側で末尾空白が入らないよう正規表現のチェック処理を追加しました。
ユーザーさんからスペースがエラーにならないとの報告で調べたところ、全角スペースが使われていました。スペースなんだから全角スペースも対象だろ、何で気が付かないのかとお叱りを受けました。全くその通りですね。

開発側だと半角スペースと全角スペースは違うものという認識でいるのですが、一般ユーザーからすれば半角スペースと全角スペースも区別しません。

# VCLのTStrings::CommaText
Visual Component Library(VCL)は、Borland社のDelphiとC++ BuilderのRAD用ライブラリーでObject Pascalで記述されている。
[TStrings.CommaTextプロパティ](http://docs.embarcadero.com/products/rad_studio/radstudio2007/RS2007_helpupdates/HUpdate4/JA/html/delphivclwin32/Classes_TStrings_CommaText.html)は、カンマ以外にも半角スペースも区切り文字として認識される。CommaTextという名前に騙される。
※カンマのみで区切りたい場合、Delimiterプロパティが別途用意されている。

改修作業にて開発環境がDelphi 5用のアプリケーションがあり、正しく認識されないとの報告から調査したところ、文字列にスペースが含まれていた。

参照：[TStrings::CommaTextは半角スペースも区切り文字とみなす](http://ryo021021.hatenablog.com/entry/2013/11/25/130621)

# System.Text.Jsonと全角スペース
.NETのSystem.Text.JsonのJsonSerializerでシリアル化したときに全角スペースがUES形式(\u3000)になってしまう。
例 "あ い"→　"あ\u3000い"

仕様らしいです。以下の記事の GrabYourPitchforks さんの回答を見てください。

>Can't serializ the '\u3000' when using with UnicodeRanges.All
https://github.com/dotnet/docs/issues/22147
>
Space_Separator [Zs] category に属する [\u0020\u3000\u1680\u2000-\u2006\u2008-\u200A\u205F\u00A0\u2007\u202F] は U+0020 (半角空白) 以外はブロックされるそうです。理由は "their potential to cause problems or errors in consuming applications." だそうです
https://teratail.com/questions/318802

どうしてもという対応策として、System.Text.JsonではなくNewtonsoft.Jsonで使用する案もあるようです。

参照：[ASP.NET Core MVC の JSON シリアライズ](http://surferonwww.info/BlogEngine/post/2020/03/11/aspnet-core-mvc-controller-json-method-replaces-non-ascii-characters-with-5cuxxxx.aspx)

# 余談
古代インドではゼロは空白で表していました。しかし、下記のようにすると見た目の表現的にも位の位置の区別が難しかったので、「0」を記号として使うようになったのです。
[ローマ数字のⅣとⅥ、ⅨとⅪを混同しなくなる方法](https://qiita.com/yaju/items/cd578d1a7b7bc9602156)

```
2 3  … (203)
2  3 … (2003)
```

# 最後に
空白の扱いについて問題点を書いてみました。
半角スペースと全角スペースについては、ファイル検索やSQL-92の仕様でも同一扱いですので、全角スペースであっても照合時には無視されます。照合については別データベース間のデータ移行する際には嵌まりポイントですね。

他にもこんなのがあるならコメントを頂ければと思います。