MENU

GAS×正規表現入門|文字列処理を自動化するテクニック

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

次のステップ

  • 正規表現をカスタム関数として活用
  • データクレンジングの自動化
  • フォーム入力のバリデーション


関連記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次