各言語で正規表現「^」「$」「\A」「\z」を試してみる
徳丸浩の日記: 正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう https://t.co/Lc20UYnwMT
— HHeLiBeX (@hhelibex) 2017年12月11日
ということで、あちこちから突っ込みが来ないことを祈りつつ(謎)、手元にある各言語でテストプログラムを書いてみたメモ。
入力は以下のような文字列。
abc 123 *+=
最大4つのパターンを試すが、すべてのパターンでマッチすると、
1234
のように出力される。逆に、マッチしないパターンや、そもそも存在しないマッチ方法の場合は「0」や「-」をそれぞれ出力する。
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
Java
import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { private static boolean matches(String str, String pattern, int flags) { Pattern p = Pattern.compile(pattern, flags); Matcher m = p.matcher(str); return m.matches(); } public static void main(String[] args) { try (Reader in = new InputStreamReader(System.in); PrintWriter out = new PrintWriter(System.out) ) { char[] buf = new char[1024]; int len = in.read(buf); String s = new String(buf, 0, len); // s = s.trim(); if (matches(s, "^[0-9]+$", 0)) { out.print("1"); } else { out.print("0"); } if (matches(s, "^[0-9]+$", Pattern.MULTILINE)) { out.print("2"); } else { out.print("0"); } if (matches(s, "\\A[0-9]+\\z", 0)) { out.print("3"); } else { out.print("0"); } if (matches(s, "\\A[0-9]+\\z", Pattern.MULTILINE)) { out.print("4"); } else { out.print("0"); } out.println(); } catch (IOException e) { e.printStackTrace(); } } }
出力。
0000
どのパターンでもマッチしない。完全にマッチしないとダメなようだ。
C
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <regex.h> int matches(const char* str, const char* pattern, int flags) { regex_t rb; if (regcomp(&rb, pattern, flags)) { perror(pattern); exit(1); } regmatch_t rm; int res; if (!regexec(&rb, str, 1, &rm, 0)) { res = 1; } else { res = 0; } regfree(&rb); return res; } int main(int argc, char** argv) { char str[1024]; memset(str, '\0', sizeof(str)); fread(str, sizeof(str), sizeof(char), stdin); // while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') { // str[strlen(str) - 1] = '\0'; // } if (matches(str, "^[0-9]+$", REG_EXTENDED)) { printf("1"); } else { printf("0"); } // そもそも複数行モードが無い printf("-"); // 文字列の先頭・末尾という正規表現が無い printf("-"); // そもそも複数行モードが無い printf("-"); printf("\n"); return 0; }
出力。
0---
まぁ、C言語は仕方がない。パターンが1つしかないので。
C++
#include <iostream> #include <locale> #include <string> #include <boost/regex.hpp> using namespace std; bool matches(string str, const char* pattern, boost::match_flag_type flags) { boost::regex re(pattern); boost::smatch sm; return boost::regex_search(str, sm, re, flags); } int main(int argc, char** argv) { istreambuf_iterator<char> it(cin); istreambuf_iterator<char> last; string str(it, last); // str.erase(str.find_last_not_of("\r\n") + 1); if (matches(str, "^[0-9]+$", boost::regex_constants::match_single_line)) { cout << "1"; } else { cout << "0"; } if (matches(str, "^[0-9]+$", boost::regex_constants::match_default)) { cout << "2"; } else { cout << "0"; } if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_single_line)) { cout << "3"; } else { cout << "0"; } if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_default)) { cout << "4"; } else { cout << "0"; } cout << endl; return EXIT_SUCCESS; }
出力。
0200
これがうわさに聞く、複数行モードで「^」「$」を使うと部分文字列にマッチするというものか。
最初のパターンでわざわざ「boost::regex_constants::match_single_line」をフラグに指定していることから分かるように、C++(Boost)のデフォルトは複数行モードのようだ。
PHP
<?php $s = file_get_contents('php://stdin'); //$s = trim($s); if (preg_match("/^[0-9]+$/", $s)) { echo '1'; } else { echo '0'; } if (preg_match("/^[0-9]+$/m", $s)) { echo '2'; } else { echo '0'; } if (preg_match("/\A[0-9]+\z/", $s)) { echo '3'; } else { echo '0'; } if (preg_match("/\A[0-9]+\z/m", $s)) { echo '4'; } else { echo '0'; } echo PHP_EOL;
出力。
0200
同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。
Python 2 / 3
import sys import re s = sys.stdin.read() #s = s.strip() if re.search(r'^[0-9]+$', s): sys.stdout.write('1') else: sys.stdout.write('0') if re.search(r'^[0-9]+$', s, re.MULTILINE): sys.stdout.write('2') else: sys.stdout.write('0') if re.search(r'\A[0-9]+\Z', s): sys.stdout.write('3') else: sys.stdout.write('0') if re.search(r'\A[0-9]+\Z', s, re.MULTILINE): sys.stdout.write('4') else: sys.stdout.write('0') sys.stdout.write("\n")
出力。
0200
同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。
なお、文字列の末尾を表す正規表現が「\z」ではなく「\Z」となることに注意。
Ruby
s = STDIN.read #s.chomp! # 単一行モードが無いので。 print "-" if s.match(/^[0-9]+$/) print "2" else print "0" end # 単一行モードが無いので。 print "-" if s.match(/\A[0-9]+\z/) print "4" else print "0" end print "\n"
出力。
-2-0
調べた限りでは複数行モードしかなかったので、複数行モードのみの出力。
確かに、「^」「$」で部分文字列にマッチする。
Perl
my $s; { local $/ = undef; $s = <STDIN>; } #chomp($s); if ($s =~ /^[0-9]+$/) { print '1'; } else { print '0'; } if ($s =~ /^[0-9]+$/m) { print '2'; } else { print '0'; } if ($s =~ /\A[0-9]+\z/) { print '3'; } else { print '0'; } if ($s =~ /\A[0-9]+\z/m) { print '4'; } else { print '0'; } print "\n";
出力。
0200
PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。
Go
package main import ( "fmt" "os" "regexp" "bufio" "io/ioutil" // "strings" ) func main() { stdin := bufio.NewReader(os.Stdin) b, _ := ioutil.ReadAll(stdin) s := string(b) // s = strings.Trim(s, "\r\n") { m := regexp.MustCompile(`^[0-9]+$`) if m.MatchString(s) { fmt.Print("1") } else { fmt.Print("0") } } { m := regexp.MustCompile(`(?m)^[0-9]+$`) if m.MatchString(s) { fmt.Print("2") } else { fmt.Print("0") } } { m := regexp.MustCompile(`\A[0-9]+\z`) if m.MatchString(s) { fmt.Print("3") } else { fmt.Print("0") } } { m := regexp.MustCompile(`(?m)\A[0-9]+\z`) if m.MatchString(s) { fmt.Print("4") } else { fmt.Print("0") } } fmt.Println() }
出力。
0200
PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。
まとめ
表にまとめると、以下のような感じか。マッチするケースに「○」、マッチしないケースに「×」を入れている。存在しないパターンは「-」としている。
- (1) 単一行モードで、「^」「$」を使ったパターン
- (2) 複数行モードで、「^」「$」を使ったパターン
- (3) 単一行モードで、「\A」「\z」を使ったパターン
- (4) 複数行モードで、「\A」「\z」を使ったパターン
(1) | (2) | (3) | (4) | |
---|---|---|---|---|
Java | × | × | × | × |
C | × | - | - | - |
C++ | × | ○ | × | × |
PHP | × | ○ | × | × |
Python 2 / 3 | × | ○ | × | × |
Ruby | - | ○ | - | × |
Perl | × | ○ | × | × |
Go | × | ○ | × | × |
自分も「^」「$」をついつい使ってしまっていたので、気に留めておくことにしよう。