Bash組み込みの正規表現機能を利用してファイルパスを要素に分割してみる

2022-10-12Bash

はじめに

Bash には正規表現を使ったマッチ判定機能やマッチグループ文字列を取得する機能が組み込まれています。

grepsed を利用すれば同様の機能は実現できるため、あまりお目にかかる機会は少ないですが、触ってみます。

検証環境

$ uname -moi
aarch64 aarch64 GNU/Linux

$ head -n 3 /etc/os-release
PRETTY_NAME="Ubuntu Kinetic Kudu (development branch)"
NAME="Ubuntu"
VERSION_ID="22.10"

$ bash -version | head -n 1
GNU bash, バージョン 5.2.0(1)-release (aarch64-unknown-linux-gnu)

拡張子付きのファイルフルパスを受け取り分割してみる

早速 Bash 組み込みの正規表現機能を利用してみます。
サンプルプログラムを作り動作を見ていきます。

ここでは「拡張子付きのファイル」のフルパスを引数で受け取ると、以下の情報に分割して出力する処理を記述します。

  1. フルパス
  2. 親ディレクトリパス
  3. ファイル名
  4. 拡張子なしファイル名
  5. 拡張子

出来上がったコードは以下になります。

正規表現を使った Bash サンプルコード regexp_sample.sh

#!/usr/bin/env bash
set -o nounset
set -o noclobber

# 正規表現を利用した関数
function analyze_filepath() {
  [[ ${1} =~ ^(.*/)(([^.]+)\.(.*))$ ]] || {
    echo "[ERROR] The file path does not contain an extension."
    return 1
  }

  echo "${BASH_REMATCH[@]}"
}

# いくつかのパラメータで動作確認
analyze_filepath "/a/b/c/xxx.txt"
analyze_filepath "/yyy.sh"
analyze_filepath "/a/b/c/zzz.1.2.js"
analyze_filepath "/a/b/c/Dockerfile"

regexp_sample.sh というファイルを作成し、上記の内容を記述し保存します(chmod +x regexp_sample.sh を実行し、権限を付与しておくことを忘れないこと)。

実行結果は以下のようになります。

$ ./regexp_sample.sh
/a/b/c/xxx.txt /a/b/c/ xxx.txt xxx txt
/yyy.sh / yyy.sh yyy sh
/a/b/c/zzz.1.2.js /a/b/c/ zzz.1.2.js zzz 1.2.js
[ERROR] The file path does not contain an extension.

動作確認した結果、出力内容が 4 行となりました。
それぞれ 5 カラムの情報が出力されており、左から以下の情報の並びとなっています。

  1. フルパス
  2. 親ディレクトリパス
  3. ファイル名
  4. 拡張子なしファイル名
  5. 拡張子

ただし、最終行だけはエラーメッセージが表示されています。

2 点だけフォーカスして解説します。

ポイント 1:文字列が正規表現にマッチするかを判定

7 行目の部分は、文字列が正規表現にマッチするかを判定するための命令となっています。

  [[ ${1} =~ ^(.*/)(([^.]+)\.(.*))$ ]] || {

=~ の右側に正規表現を記述して、 =~ の左側の文字がマッチするかを検証できます。

ポイント 2:マッチグループにマッチした要素を取り出し出力

12 行目の処理では、 BASH_REMATCH という配列変数のすべての要素を出力しています。
Bash の正規表現判定を実行したあと、正規表現の各キャプチャグループ( (...) で囲まれた部分をキャプチャグループとよんでいます )が BASH_REMATCH 変数にセットされます。

各キャプチャグループは、左から開始括弧 ( の登場順ごとに 1 からの連番が振られ、 BASH_REMATCH 配列変数の添字として利用されます。各要素の値にはマッチした文字列が代入されます。

この結果、 BASH_REMATCH 変数の各要素には以下のような値が格納されます。

  • ${BASH_REMATCH[1]} : 親ディレクトリパス
  • ${BASH_REMATCH[2]} : ファイル名
  • ${BASH_REMATCH[3]} : 拡張子なしファイル名
  • ${BASH_REMATCH[4]} : 拡張子

${BASH_REMATCH[0]} は特殊で、キャプチャグループの値ではなく正規表現全体にマッチした文字列が格納されます。
ここでは以下のようになります。

  • ${BASH_REMATCH[0]} : フルパス

先程のコードを以下のように書き換え、キャプチャグループの 2 番目の値だけを表示させてみましょう。

#!/usr/bin/env bash
set -o nounset
set -o noclobber

# 正規表現を利用した関数
function analyze_filepath() {
  [[ ${1} =~ ^(.*/)(([^.]+)\.(.*))$ ]] || {
    echo "[ERROR] The file path does not contain an extension."
    return 1
  }

  echo "${BASH_REMATCH[3]}"
}

# いくつかのパラメータで動作確認
analyze_filepath "/a/b/c/xxx.txt"
analyze_filepath "/yyy.sh"
analyze_filepath "/a/b/c/zzz.1.2.js"
analyze_filepath "/a/b/c/Dockerfile"

実行結果は以下の通りです。

$ ./regexp_sample.sh
xxx
yyy
zzz
[ERROR] The file path does not contain an extension.

ひとこと

正規表現については理解している前提の内容となったため、 いくつか説明が端折られていてわかりにくい内容になってしまいました。

今回は文章も上手にかけなかった気がします。

2022-10-12Bash