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

Bash

はじめに

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

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

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-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/x.txt"
analyze_filepath "/y.sh"
analyze_filepath "/a/b/c/z.1.2.js"
analyze_filepath "/a/b/c/Dockerfile"

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

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

$ ./regexp_sample.sh
/a/b/c/x.txt /a/b/c/ x.txt x txt
/y.sh / y.sh y sh
/a/b/c/z.1.2.js /a/b/c/z.1. 2.js 2 js
[ERROR] The file path does not contain an extension.

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

  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/x.txt"
analyze_filepath "/y.sh"
analyze_filepath "/a/b/c/z.1.2.js"
analyze_filepath "/a/b/c/Dockerfile"

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

$ ./regexp_sample.sh
x
y
2
[ERROR] The file path does not contain an extension.

ひとこと

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

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

Bash