GAS×正規表現入門|文字列処理を自動化するテクニック
正規表現(Regular Expression) を使えば、これらの文字列処理を一瞬で自動化できます。
目次
正規表現とは
正規表現の基本概念
正規表現とは、文字列のパターンを表現するための記法です。
例えばメールアドレスを正規表現で表すと:
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
一見複雑に見えますが、分解すると:
[a-zA-Z0-9._%+-]+ → @の前の部分(英数字や記号)
@ → @マーク
[a-zA-Z0-9.-]+ → ドメイン名
\. → ドット
[a-zA-Z]{2,} → com, jpなどのTLD
なぜ正規表現が必要なのか
| 処理 |
正規表現なし |
正規表現あり |
| メール抽出 |
if文を大量に書く |
1行で完了 |
| 電話番号統一 |
replace()を何度も |
1行で完了 |
| URL検出 |
複雑なロジック |
パターン1つ |
正規表現を覚えると、複雑な文字列処理が驚くほど簡単になります。
正規表現チートシート
基本パターン
| パターン |
意味 |
例 |
. |
任意の1文字 |
a.c → abc, a1c, a@c |
* |
0回以上の繰り返し |
ab*c → ac, abc, abbc |
+ |
1回以上の繰り返し |
ab+c → abc, abbc(acは×) |
? |
0回または1回 |
colou?r → color, colour |
^ |
行頭 |
^Hello → 行頭のHello |
$ |
行末 |
end$ → 行末のend |
文字クラス
| パターン |
意味 |
例 |
[abc] |
a, b, cのいずれか |
[abc] → a, b, c |
[a-z] |
a〜zの小文字 |
[a-z]+ → hello |
[A-Z] |
A〜Zの大文字 |
[A-Z]+ → HELLO |
[0-9] |
0〜9の数字 |
[0-9]+ → 12345 |
[^abc] |
a, b, c以外 |
[^0-9] → 数字以外 |
特殊文字(ショートカット)
| パターン |
意味 |
同等の表現 |
\d |
数字 |
[0-9] |
\D |
数字以外 |
[^0-9] |
\w |
英数字とアンダースコア |
[a-zA-Z0-9_] |
\W |
\w以外 |
[^a-zA-Z0-9_] |
\s |
空白文字 |
スペース、タブ、改行 |
\S |
空白以外 |
– |
量指定子
| パターン |
意味 |
例 |
{n} |
ちょうどn回 |
\d{3} → 123 |
{n,} |
n回以上 |
\d{2,} → 12, 123, 1234 |
{n,m} |
n〜m回 |
\d{2,4} → 12, 123, 1234 |
グループとキャプチャ
| パターン |
意味 |
例 |
(abc) |
グループ化 |
(ab)+ → ab, abab |
(?:abc) |
キャプチャしないグループ |
– |
\1 |
後方参照(1番目のグループ) |
– |
| ` |
` |
OR(または) |
`cat |
dog` → catまたはdog |
GASでの正規表現の使い方
RegExpオブジェクトの作成
// リテラル記法(推奨)
const regex1 = /pattern/flags;
// コンストラクタ記法(動的パターン向け)
const regex2 = new RegExp('pattern', 'flags');
フラグの種類
| フラグ |
意味 |
g |
グローバル検索(全てのマッチを検索) |
i |
大文字小文字を区別しない |
m |
複数行モード(^と$が各行に適用) |
主要メソッド3つ
1. test() – マッチするか確認
/**
* test(): パターンにマッチするかどうかを判定
* 戻り値: true/false
*/
function testExample() {
const text = 'お問い合わせ: info@example.com';
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
if (emailPattern.test(text)) {
Logger.log('メールアドレスが含まれています');
} else {
Logger.log('メールアドレスは含まれていません');
}
}
2. match() – マッチした文字列を取得
/**
* match(): マッチした文字列を配列で取得
* gフラグあり: 全てのマッチを配列で返す
* gフラグなし: 最初のマッチと詳細情報を返す
*/
function matchExample() {
const text = 'お問い合わせ: info@example.com、sales@example.com';
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const matches = text.match(emailPattern);
Logger.log(matches); // ['info@example.com', 'sales@example.com']
}
3. replace() – 置換
/**
* replace(): パターンにマッチした部分を置換
* gフラグあり: 全てのマッチを置換
* gフラグなし: 最初のマッチのみ置換
*/
function replaceExample() {
const text = '電話番号: 03-1234-5678、090-1234-5678';
// ハイフンを削除
const result = text.replace(/-/g, '');
Logger.log(result); // '電話番号: 0312345678、09012345678'
}
実践例①:メールアドレスの抽出・検証
スプレッドシートからメールアドレスを抽出
/**
* スプレッドシートのテキストからメールアドレスを抽出
* 使い方: A列にテキスト、B列に抽出結果を出力
*/
function extractEmailsFromSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
// メールアドレスのパターン
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
for (let i = 1; i <= lastRow; i++) {
const text = sheet.getRange(i, 1).getValue().toString();
const emails = text.match(emailPattern);
if (emails) {
// 複数あればカンマ区切りで結合
sheet.getRange(i, 2).setValue(emails.join(', '));
} else {
sheet.getRange(i, 2).setValue('なし');
}
}
Logger.log('メールアドレス抽出完了');
}
メールアドレスの形式チェック
/**
* メールアドレスの形式が正しいかチェック
* @param {string} email - チェックするメールアドレス
* @return {boolean} - 形式が正しければtrue
*/
function isValidEmail(email) {
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailPattern.test(email);
}
/**
* スプレッドシートのメールアドレスを検証
* A列: メールアドレス、B列: 検証結果
*/
function validateEmailsInSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
for (let i = 2; i <= lastRow; i++) { // 2行目から(ヘッダー除く)
const email = sheet.getRange(i, 1).getValue().toString().trim();
if (email === '') {
sheet.getRange(i, 2).setValue('-');
} else if (isValidEmail(email)) {
sheet.getRange(i, 2).setValue('✓ 有効');
} else {
sheet.getRange(i, 2).setValue('✗ 無効');
}
}
}
カスタム関数として使う
/**
* セルから直接メールアドレスを抽出するカスタム関数
* 使い方: =EXTRACT_EMAILS(A1)
*/
function EXTRACT_EMAILS(text) {
if (!text) return '';
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const emails = text.toString().match(emailPattern);
return emails ? emails.join(', ') : '';
}
/**
* メールアドレスの形式をチェックするカスタム関数
* 使い方: =IS_VALID_EMAIL(A1)
*/
function IS_VALID_EMAIL(email) {
if (!email) return false;
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailPattern.test(email.toString().trim());
}
実践例②:電話番号のフォーマット統一
バラバラな形式を統一
/**
* 電話番号のフォーマットを統一
* 入力: 03-1234-5678, 0312345678, 03 1234 5678 など
* 出力: 03-1234-5678(ハイフン区切り)
*/
function formatPhoneNumber(phone) {
if (!phone) return '';
// 数字以外を全て削除
const digits = phone.toString().replace(/\D/g, '');
// 桁数に応じてフォーマット
if (digits.length === 10) {
// 固定電話(10桁): 03-1234-5678
return digits.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3');
} else if (digits.length === 11) {
// 携帯電話(11桁): 090-1234-5678
return digits.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
} else {
return phone.toString(); // 元の値を返す
}
}
/**
* スプレッドシートの電話番号を一括フォーマット
* A列: 元の電話番号、B列: 整形後
*/
function formatPhoneNumbersInSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
for (let i = 2; i <= lastRow; i++) {
const phone = sheet.getRange(i, 1).getValue();
const formatted = formatPhoneNumber(phone);
sheet.getRange(i, 2).setValue(formatted);
}
Logger.log('電話番号のフォーマット完了');
}
電話番号の検証
/**
* 日本の電話番号として有効かチェック
* @param {string} phone - チェックする電話番号
* @return {boolean}
*/
function isValidJapanesePhone(phone) {
if (!phone) return false;
// 数字以外を削除
const digits = phone.toString().replace(/\D/g, '');
// 固定電話(10桁)または携帯電話(11桁)
const phonePattern = /^(0[1-9]\d{8}|0[789]0\d{8})$/;
return phonePattern.test(digits);
}
/**
* 電話番号のカスタム関数
* 使い方: =FORMAT_PHONE(A1)
*/
function FORMAT_PHONE(phone) {
return formatPhoneNumber(phone);
}
電話番号の種類を判定
/**
* 電話番号の種類を判定
* @param {string} phone - 電話番号
* @return {string} - 種類(携帯/固定/フリーダイヤル/不明)
*/
function getPhoneType(phone) {
if (!phone) return '不明';
const digits = phone.toString().replace(/\D/g, '');
if (/^0[789]0\d{8}$/.test(digits)) {
return '携帯電話';
} else if (/^0120\d{6}$/.test(digits)) {
return 'フリーダイヤル';
} else if (/^0[1-9]\d{8}$/.test(digits)) {
return '固定電話';
} else if (/^050\d{8}$/.test(digits)) {
return 'IP電話';
} else {
return '不明';
}
}
実践例③:テキストからURLを抽出
URLの抽出
/**
* テキストからURLを抽出
* @param {string} text - 対象テキスト
* @return {string[]} - URL配列
*/
function extractUrls(text) {
if (!text) return [];
// URLパターン(http/https)
const urlPattern = /https?:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]+/g;
const matches = text.match(urlPattern);
return matches || [];
}
/**
* スプレッドシートからURLを抽出
*/
function extractUrlsFromSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
for (let i = 1; i <= lastRow; i++) {
const text = sheet.getRange(i, 1).getValue().toString();
const urls = extractUrls(text);
if (urls.length > 0) {
sheet.getRange(i, 2).setValue(urls.join('\n'));
} else {
sheet.getRange(i, 2).setValue('なし');
}
}
}
ドメイン名のみ抽出
/**
* URLからドメイン名のみを抽出
* @param {string} url - URL
* @return {string} - ドメイン名
*/
function extractDomain(url) {
if (!url) return '';
const domainPattern = /https?:\/\/([a-zA-Z0-9.-]+)/;
const match = url.match(domainPattern);
return match ? match[1] : '';
}
/**
* カスタム関数: URLからドメイン抽出
* 使い方: =EXTRACT_DOMAIN(A1)
*/
function EXTRACT_DOMAIN(url) {
return extractDomain(url);
}
リンクテキストの作成
/**
* URLをハイパーリンク形式に変換
*/
function convertUrlsToLinks() {
const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getDataRange();
const values = range.getValues();
const urlPattern = /https?:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]+/g;
for (let i = 0; i < values.length; i++) {
for (let j = 0; j < values[i].length; j++) {
const cell = values[i][j].toString();
const urls = cell.match(urlPattern);
if (urls) {
// 最初のURLをハイパーリンクとして設定
const richText = SpreadsheetApp.newRichTextValue()
.setText(cell)
.setLinkUrl(urls[0])
.build();
sheet.getRange(i + 1, j + 1).setRichTextValue(richText);
}
}
}
}
実践例④:データクレンジング
余分な空白を削除
/**
* 連続する空白を1つに統一
* @param {string} text - 対象テキスト
* @return {string} - 整形後テキスト
*/
function normalizeSpaces(text) {
if (!text) return '';
return text.toString()
.replace(/\s+/g, ' ') // 連続空白を1つに
.replace(/^\s+|\s+$/g, ''); // 前後の空白を削除
}
/**
* 全角スペースを半角に統一
*/
function normalizeFullWidthSpaces(text) {
if (!text) return '';
return text.toString()
.replace(/ /g, ' ') // 全角→半角
.replace(/\s+/g, ' ') // 連続空白を1つに
.trim();
}
数字の全角・半角統一
/**
* 全角数字を半角に変換
* @param {string} text - 対象テキスト
* @return {string}
*/
function toHalfWidthNumbers(text) {
if (!text) return '';
return text.toString().replace(/[0-9]/g, function(char) {
return String.fromCharCode(char.charCodeAt(0) - 0xFEE0);
});
}
/**
* 半角数字を全角に変換
*/
function toFullWidthNumbers(text) {
if (!text) return '';
return text.toString().replace(/[0-9]/g, function(char) {
return String.fromCharCode(char.charCodeAt(0) + 0xFEE0);
});
}
特定の文字を削除
/**
* HTMLタグを削除
*/
function stripHtmlTags(text) {
if (!text) return '';
return text.toString().replace(/<[^>]*>/g, '');
}
/**
* 制御文字を削除
*/
function removeControlChars(text) {
if (!text) return '';
return text.toString().replace(/[\x00-\x1F\x7F]/g, '');
}
/**
* 絵文字を削除
*/
function removeEmoji(text) {
if (!text) return '';
return text.toString().replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '');
}
よくあるエラーと対処法
エラー1: 特殊文字のエスケープ忘れ
// ❌ エラー: ドット(.)は任意の1文字を意味する
const pattern1 = /example.com/; // example_com にもマッチしてしまう
// ✅ 正解: バックスラッシュでエスケープ
const pattern2 = /example\.com/;
エスケープが必要な文字: . * + ? ^ $ { } [ ] ( ) | \ /
エラー2: gフラグの有無を間違える
const text = 'apple, banana, apple';
// gフラグなし: 最初のマッチのみ
const result1 = text.replace(/apple/, 'orange');
Logger.log(result1); // 'orange, banana, apple'
// gフラグあり: 全てのマッチを置換
const result2 = text.replace(/apple/g, 'orange');
Logger.log(result2); // 'orange, banana, orange'
エラー3: 貪欲マッチと最短マッチ
const html = '<div>Hello</div><div>World</div>';
// ❌ 貪欲マッチ(できるだけ長くマッチ)
const greedy = html.match(/<div>.*<\/div>/);
Logger.log(greedy[0]); // '<div>Hello</div><div>World</div>'
// ✅ 最短マッチ(?を追加)
const lazy = html.match(/<div>.*?<\/div>/g);
Logger.log(lazy); // ['<div>Hello</div>', '<div>World</div>']
エラー4: 日本語の扱い
// 日本語を含むパターン
const japanesePattern = /[ぁ-んァ-ン一-龥]/g;
const text = 'Hello こんにちは 123';
const japanese = text.match(japanesePattern);
Logger.log(japanese.join('')); // 'こんにちは'
よく使う正規表現パターン集
/**
* よく使う正規表現パターン集
*/
const PATTERNS = {
// メールアドレス
EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
// 日本の電話番号(ハイフンあり・なし両対応)
PHONE_JP: /^0\d{1,4}[-\s]?\d{1,4}[-\s]?\d{4}$/,
// 郵便番号
POSTAL_CODE_JP: /^\d{3}-?\d{4}$/,
// URL
URL: /^https?:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]+$/,
// 日付(YYYY-MM-DD)
DATE_ISO: /^\d{4}-\d{2}-\d{2}$/,
// 日付(YYYY/MM/DD)
DATE_JP: /^\d{4}\/\d{2}\/\d{2}$/,
// 数字のみ
DIGITS_ONLY: /^\d+$/,
// 半角英数字のみ
ALPHANUMERIC: /^[a-zA-Z0-9]+$/,
// ひらがなのみ
HIRAGANA: /^[ぁ-んー]+$/,
// カタカナのみ
KATAKANA: /^[ァ-ンヴー]+$/,
// 全角カタカナのみ(スペース含む)
KATAKANA_FULL: /^[ァ-ンヴー\s]+$/
};
/**
* パターンを使った検証
*/
function validateWithPatterns() {
Logger.log(PATTERNS.EMAIL.test('test@example.com')); // true
Logger.log(PATTERNS.PHONE_JP.test('03-1234-5678')); // true
Logger.log(PATTERNS.POSTAL_CODE_JP.test('123-4567')); // true
Logger.log(PATTERNS.HIRAGANA.test('ひらがな')); // true
}
まとめ
今回学んだこと
| 項目 |
ポイント |
| 正規表現の基本 |
パターンで文字列を表現 |
| test() |
マッチするか判定(true/false) |
| match() |
マッチした文字列を取得 |
| replace() |
パターンにマッチした部分を置換 |
実務でよく使うパターン
- メールアドレス:
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/
- 電話番号(数字のみ):
/\d{10,11}/
- URL:
/https?:\/\/[^\s]+/
- 数字の抽出:
/\d+/g
次のステップ
- 正規表現をカスタム関数として活用
- データクレンジングの自動化
- フォーム入力のバリデーション
関連記事
コメント