2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

\\だとバグるが、\\\\だとバグらないプログラム(正規表現)

Last updated at Posted at 2024-09-22

話のはじまり

こんにちは、来年度までに就職をしたい甘いレモン市場と言います。
皆さんはよき正規表現ライフを送っているでしょうか?正規表現を使いこなしたいですよね。
正規表現で文字列パターンを指定する際に、「\」(バックスラッシュ記号もしくは半角¥記号。区別のため、この1文字を「」付きで記述します)という文字列が含まれているか調べたくなることがあると思います。
たとえば『スッキリわかるJava入門 第4版』 の次の練習問題のシチュエーションを考えてみましょう。

練習15-2
フォルダ名が入っている変数folderと、ファイル名が入っている変数fileがあります。fileは必ず「readme.txt」のような形式をしていますが、folderは末尾に\記号が付いている場合と付いていない場合の両方がありえます。たとえば、「c:\javadev」や「c:\user\」のどちらもfolderの値としては正しい形式です。
folderとfileを連結して、「c:\javadev\readme.txt」のような完全なファイル名を表す文字列を組み立てるメソッドを作成してください。

これは、「c:\user\」はフォルダの階層関係のなかで既に子フォルダかファイルを持つ親フォルダであり、「c:\javadev」はそうなっていない末端のフォルダであるという現実的なシチュエーションを想定しています。
そこでフォルダの文字列の末尾が「\」であるか否かをチェックできればいいわけです。

正規表現の活用

条件を言い換えると、フォルダの文字列が「任意の文字列 + \」という文字列パターンに一致するか、という条件になります。この考え方で、Stringクラスのmatchesメソッドを使うとどうにかなりそうですね。(落とし穴その1)
任意の文字列を表現する文字列パターンは.*(任意の1文字の任意回の繰り返し)でした。これに「\」を表現する文字列\\を連結した文字列パターンでマッチングを調べれば良さそうです。

ConcatString.java
public class ConcatString {

	public static String concatPath(String folder, String file) {
		if (!folder.matches(".*\\")) {
			folder += "\\";
		}
		return folder + file;
	}
}

これをmainメソッドで実行しましょう。

Main.java
public class Main {

	public static void main(String[] args) {
		String folder1 = "c:\\user\\";
		String folder2 = "c:\\javadev\\";
		String file = "readme.txt";
		
		System.out.println(ConcatString.concatPath(folder1, file));
		System.out.println(ConcatString.concatPath(folder2, file));
	}
}

これを実行すると、例外 Exception in thread "main" java.util.regex.PatternSyntaxException: Unexpected internal error near index 3

が返ってきます。バグりました!!!! 何がいけなかったのでしょうか?(落とし穴2)

風呂敷を畳む

落とし穴2の解決

「\」は特殊文字であったため、文字列リテラルとして指定する際には\\というエスケープシーケンスで書く必要がありました。
しかし、「\」は正規表現(文字列パターン)においても特殊記号であるため、正規表現としてもエスケープシーケンスで書く必要があります。参照
つまり、「\」を文字列パターンを指定するには2重にエスケープシーケンスで書いて\\\\と書く必要があります。

ConcatString.java
public class ConcatString {

	public static String concatPath(String folder, String file) {
		if (!folder.matches(".*\\\\")) {
			folder += "\\";
		}
		return folder + file;
	}
}

concatPathメソッドをこのように書き換えて、mainメソッドを実行すると無事にコンパイルが通り、

c:\user\readme.txt
c:\javadev\readme.txt

がコンソールに表示されます。

落とし穴1の解決

ここまででフォルダの文字列が「任意の文字列 + \」という文字列パターンに一致するか、という条件をmatchesメソッドで調べました。
しかし、Stringクラスには「末尾が指定された文字列で終わるか」を調べるendWithメソッドが存在します。そのため、わざわざ文字列パターンを考えなくとも下記で済みます。

ConcatString.java
public class ConcatString {

	public static String concatPath(String folder, String file) {
		if (!folder.endsWith("\\")) {
			folder += "\\";
		}
		return folder + file;
	}
}

おわり

正規表現での「\」の扱い

  • 正規表現で「\」を表現するには、文字列パターンで\\\\と書く必要がある
  • 「\」は文字列リテラルと正規表現の両方で特殊文字であるため、二重のエスケープが必要である

フォルダパス連結の問題

  • フォルダ名の末尾に「\」があるかどうかを確認し、必要に応じて追加する処理が必要である
  • String.matchesメソッドを使用する代わりに、String.endsWithメソッドを使用するとより簡単に実装できる

プログラミングの注意点

  • 特殊文字を扱う際は、文字列リテラルと正規表現の両方でのエスケープを考慮する
  • 既存のメソッド(例:endsWith)を活用することで、より簡潔で効率的なコードを書ける

私について

  • 甘いレモン市場は就活中なので興味のある雇用主の方がいたら DM してください。
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?