スプレッドシートに、その名前のシートがあるか見る

GAS

※本記事はプロモーションを含みます

目次

スプレッドシートを日々の業務基盤として運用していると、月次の締め処理や日次バッチの実行時に今日の日付のシートがすでに作られているかをスクリプト側で確かめたい場面が頻繁に訪れます。あるいは過去のログシートが存在しているかをスクリプト側で確かめたい場面が頻繁に訪れます。目的のシートがまだ無いなら新しく作成したいです。すでにあるなら既存のシートに対して追記や参照を行いたいです。この要件はGAS開発において極めて一般的です。ただしシートの取得処理を無防備に記述していると、シートが見つからなかった瞬間にスクリプト全体が停止します。なので後続の業務データ連携が途切れてしまうトラブルが発生します。シートの有無を事前に判定するロジックは、自動化の堅牢性を保つための最初の防壁となります。

getSheetByNameの戻り値をそのまま操作しようとして例外を投げる落とし穴

Google Apps Scriptでシートを取得しようとする際、誰もが最初に使うのがSpreadsheetクラスのgetSheetByNameツールです。getSheetByNameツールは直感的で分かりやすいです。というのは引数にシート名として文字列を渡すだけで、一致するシートオブジェクトを返してくれるからです。ただしこのツールが該当する名前のシートが存在しなかったときに何を返すかを意識していないと、深刻なランタイムエラーを招くことになります。

指定した名前のシートが見つからない場合、getSheetByNameツールは例外を投げるのではなく、静かにnullを返します。この挙動自体は非常に理にかなった設計です。ただし問題はその戻り値に対してそのまま別のツールをチェーンして呼び出した瞬間に生じます。例えばシートを取得してすぐに最終行を調べようとして「ss.getSheetByName(“2026_ログ”).getLastRow()」といった書き方をしていると、対象シートが存在しなかった場合に「TypeError: Cannot read properties of null (reading ‘getLastRow’)」というエラーが発生します。なので処理が強制終了します。

開発時にはテスト用のシートを手動であらかじめ用意していたためエラーに気づかないことがあります。なので翌月になって新しい月次シートが未作成の状態でバッチ処理が走り、深夜にスクリプトが落ちてしまうというパターンが実務でよくあります。この落とし穴を回避するためには、シートオブジェクトに対して操作を行う前に、戻り値がnullであるかどうかを判定しましょう。シートの存在確認を確実に行うステップを挟む必要があります。

またシートの有無を確認するために、わざわざスプレッドシート内のすべてのシートをgetSheetsツールで全件取得するコードを見かけることもあります。なのでforループでシート名を1つずつ照合していくコードを見かけることもあります。もちろんその方法でも確認自体は可能です。ただしシート数が何十枚にも増えてくると無駄なループ処理が発生します。なので実行時間の浪費に繋がります。getSheetByNameツールが対象シートの不在時にnullを返すという仕様を正しく理解していれば、ループを回すことなく一撃で存在チェックを完了させることができます。

null判定を利用してシートの存在を安全に判定するコード

対象のシートが存在するかどうかを真偽値(boolean)として安全に判定するには、getSheetByNameツールの戻り値がnullと一致しないかどうか(!== null)を評価します。このシンプルな判定ロジックを挟むことで、シートが存在しない状況を例外エラーではなく通常想定される分岐条件として安全に処理できるようになります。

指定したシートが存在するかどうかをチェックし、その結果をログに出力する基本コードを示します。

const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheetName = "2026_ログ";
const exists = ss.getSheetByName(sheetName) !== null;
Logger.log("シート存在: " + exists);

このコードでは、アクティブなスプレッドシートに対してgetSheetByNameツールを呼び出します。なのでその結果を厳格な不等価演算子(!==)でnullと比較しています。もしシートが存在していればSheetオブジェクトが返ってきます。というのは「Sheet !== null」はtrueとなるからです。存在しなければnullが返ってきます。なので「null !== null」はfalseとなります。変数existsには確実に真偽値が格納されます。なのでこの後のif文で安全に分岐を作ることができます。

この処理が必要になる典型的なタイミングは、外部APIから取得したログを保存する際です。あるいは定期実行トリガーで新しい日次・月次データを書き込む直前です。存在しなければ新規作成(insertSheetツール)を行います。なので存在していれば既存シートを取得して追記(appendRowツール)します。この一連の流れを組み立てる基盤として、この判定ロジックが必須となります。

ただしここで注意しなければならないのは、getSheetByNameツールに渡す引数の型です。というのは引数にundefinedや空文字や意図しないオブジェクトを渡してしまうと、当然ながら期待通りのシート取得はできないからです。シート名を動的に生成している場合は、引数に渡す変数そのものが意図した文字列になっているかを事前に確認しましょう。例えば日付文字列から生成している場合は確認が必要です。

半角全角や不可視の空白による不一致を現場で防ぐ工夫

ロジックとしては完璧に「!== null」の判定を書いていたとします。ただし現場では目視で確実にシートが存在しているのに、なぜかfalseと判定されることがあります。なので新規シート作成に走ってしまい、名前が重複してエラーになるという奇妙なトラブルが発生することがあります。この原因の多くは、スプレッドシートのシート名に含まれる細かな文字列の差異に起因しています。

スプレッドシートのタブ名は、人間の手作業によって変更される機会が多い部分です。ユーザーがシート名を変更する際、末尾や先頭に意図せず半角スペースや全角スペースを残してしまうケースが珍しくありません。getSheetByNameツールは厳密な文字列一致を要求します。なのでコード側で「2026_ログ」を探していても、実際のシート名が「2026_ログ 」(末尾に半角スペース)だった場合、完全一致とはみなされません。なのでnullが返されます。

さらに全角と半角の違いも大きな落とし穴です。「2026_ログ」のアンダースコアが全角の「_」になっていたり、英数字部分が全角の「2026」になっていたりすることがあります。なので人間が画面を見ただけでは違いに気づくことが極めて困難です。コード側で固定文字列を指定している場合、ユーザーの手入力によってタブ名が揺らぐことがあります。というのは存在チェックをすり抜けてしまうからです。

このズレを実務で防ぐためのアプローチとしては、二通りの考え方があります。一つは、運用ルールとしてシート名の手動編集を禁止することです。なのでシート作成からリネームまでをすべてスクリプトの管理下に置きます。もう一つは、チェック側の処理を堅牢にすることです。どうしても人間の入力ブレを許容しなければならない環境では、前述したgetSheetsツールによる全シート走査をあえて採用します。なので各シートのgetName()ツールに対してtrim()ツールをかけて前後の空白を除去した上で比較します。あるいは正規表現を使って曖昧検索を行ったりします。

ただし日次バッチなどの定型処理においては、無秩序な柔軟性を持たせるよりも厳密な命名ルールを定義しましょう。というのは期待通りの名称で存在しているかを判定するほうが見通しを保てるからです。意図しない空白混入で悩まされた経験があるなら、まずは実際のシートのタブ名をダブルクリックしてみましょう。なのでカーソルを末尾に合わせ、余計な空白が潜んでいないかを点検しましょう。

存在しない場合の新規作成と名前重複例外のハンドリング

シートの存在確認を行う最大の動機は、存在しない場合に新しくシートを作ることです。なので存在する場合はそれを使うという初期化処理を実装することにあります。存在チェックを行った後、insertSheetツールを呼び出してシートを生成するパターンはGASにおける定番の流れです。

ただしここでも実行タイミングのズレによる例外リスクが潜んでいます。存在チェックで「false」が返ってきたため、コードが新規作成の分岐に入ります。なので「ss.insertSheet(sheetName)」を実行しようとしたとします。このとき、もし別のトリガーや別のユーザーの同時操作によって、全く同じ名前のシートがミリ秒単位の差で先に作成されてしまっていたとします。この場合、insertSheetツールは「同じ名前のシートが既に存在します」という例外を投げて異常終了します。

単一ユーザーが手動で実行するツールであれば、このような競合は滅多に起きません。ただしフォーム送信トリガーや時間主導型トリガーが短い間隔で並行稼働している環境では、競合状態(レースコンディション)が発生する余地があります。というのは存在チェックを行ってから作成するまでのわずかな隙間に状態が変わる可能性があるからです。

このようなケースでは、存在チェックによる分岐だけでなく、作成処理そのものをtry…catch文で保護することがあります。万が一作成時に名前重複エラーが飛んできた場合には、catch節で再度getSheetByNameツールを実行します。なので既存シートを再取得する、といった二重の防御策を講じることがあります。例外を単に無視するわけではありません。というのは競合が起きた場合でも最終的に目的のシートオブジェクトを確実に確保して後続処理を続行する状態を作ることが重要だからです。

また新規作成時に特定のテンプレートシートを複製(copyToツール)してリネームする運用を行っている場合も同様です。複製した直後は「〇〇 のコピー」という自動命名がなされます。なのでそれをsetNameツールで目的の名前に変更する処理を行います。ただしここでもリネーム先の名前に重複があるとエラーになります。事前の存在確認チェックが正しく機能していれば、こうした事故の大部分は未然に防ぐことができます。なので設定を確認しましょう。

シートチェックをユーティリティ化して業務コードの可読性を保つ

業務用のGASスクリプトが肥大化してくると、複数の関数やファイルの中で何度も同じ処理を記述することになります。つまり「このシートはあるか?」「なければ作る」という処理を記述することになります。毎回「ss.getSheetByName(name) !== null」とインラインで記述しているとコードが複雑になります。なのでその後にif文で分岐させていると、業務ロジックそのものよりもシートの準備に関するコードが目立ってしまい、保守性が低下します。

実務においては、シートの存在チェック、および「無ければ作成して返す」という一連の振る舞いを独立した関数(ユーティリティ関数)として切り出しておくのが賢明な設計です。例えば「getOrCreateSheet(ss, sheetName)」のような関数を1つ定義しておきます。なので内部でgetSheetByNameツールを実行します。nullであればinsertSheetツールを実行します。なので最終的に必ず目的のSheetオブジェクトを返すようにカプセル化します。

このように設計しておけば、メインの業務処理側ではシートが存在するかどうかを意識する必要がありません。なので常に安全にシートオブジェクトを受け取って即座に書き込みや読み込みを開始できます。例外が発生しやすい境界部分を1箇所に集約することができます。なので将来的にシート名の命名規則が変わったり、前述したようなトリム処理や例外ハンドリングを追加したくなったりした際も、修正箇所を単一の関数内に限定することができます。

GASでシートの存在確認を行うチェックの処理は、単体で見れば数行のコードに過ぎません。ただしエラーでバッチを落とさないためのnull安全の担保が必要です。なので人間の手入力による表記ゆれの防止や、並行処理時の例外回避など、考慮すべき要素が凝縮されています。単に動くというコードから一歩進めて、想定外の入力や状態変化にも耐えうる判定ロジックを組み込みましょう。仕組みを整えることが、現場で止まらないスプレッドシート運用の基盤を作ることにつながります。

タイトルとURLをコピーしました