2026年5月22日

2026年5月22日

bashの文字列操作(スライス・置換・長さ取得)|パラメーター展開の使い方

bashでは、sed や cut などの外部コマンドを呼ばなくても、「パラメーター展開」という機能だけで文字列の長さ取得・切り出し(スライス)・置換・前後の削除ができます。ファイル名から拡張子を外す、パスからディレクトリ名を取り出す、ログの一部を置き換えるといった処理が1行で書け、ループの中でも高速に動きます。この記事では、よく使う書き方を目的別に整理し、パターンの書き方、bash 5.2で変わった置換の挙動、正規表現との使い分け、つまずきやすい点まで解説します。

前提は(2026年10月現在)一般的な Linux の bash 5 系です。一部の書き方は POSIX sh(dash など)では使えないため、該当箇所で注記します。

長さを取得する:${#変数}

str="WordPress"
echo "${#str}"    # 9

${#変数} は文字数を返します。日本語のようなマルチバイト文字は、ロケールが UTF-8(LANG=ja_JP.UTF-8 や C.UTF-8 など)なら1文字として数えられますが、LANG=C のような環境ではバイト数になります。cron から実行するとロケールが異なることがあるため、文字数で判定する処理は実行環境でも確認してください。

s="日本語"
echo "${#s}"                  # UTF-8 ロケールなら 3
LC_ALL=C bash -c 's="日本語"; echo "${#s}"'   # 9(バイト数)

切り出す:${変数:開始:長さ}

開始位置は0から数えます。長さを省略すると末尾までです。

date_str="2026-10-18"
echo "${date_str:0:4}"   # 2026
echo "${date_str:5:2}"   # 10
echo "${date_str:5}"     # 10-18

末尾から数えたいときは負の値を使いますが、: の直後に - を書くと後述の既定値の書き方(:-)と解釈されるため、空白を入れるか括弧で囲みます。

file="backup-20261018.tar.gz"
echo "${file: -6}"       # tar.gz
echo "${file:(-6)}"      # 同じ
echo "${file:7:-7}"      # 20261018(長さに負の値: 末尾から7文字を除く。bash 4.2以降)

スライスは bash の拡張機能で、POSIX sh では使えません。

前後を削除する:# ## % %%

パスやファイル名の加工でもっともよく使うのがこの4つです。ここで書くのは正規表現ではなく、* や ? を使うシェルのパターン(グロブ)です。

  • ${変数#パターン}:先頭から、パターンに一致するもっとも短い部分を削除
  • ${変数##パターン}:先頭から、もっとも長い部分を削除
  • ${変数%パターン}:末尾から、もっとも短い部分を削除
  • ${変数%%パターン}:末尾から、もっとも長い部分を削除
path="/var/www/html/wp-content/uploads/photo.min.jpg"

echo "${path##*/}"     # photo.min.jpg(ファイル名: basename 相当)
echo "${path%/*}"      # /var/www/html/wp-content/uploads(dirname 相当)

name="${path##*/}"
echo "${name%.*}"      # photo.min(最後の拡張子だけ外す)
echo "${name%%.*}"     # photo(最初の . 以降をすべて外す)
echo "${name##*.}"     # jpg(拡張子)

覚え方として、キーボード上で # は $ の左、% は右にあるので「# は左(先頭)、% は右(末尾)」と覚える方法がよく紹介されます。1つなら最短一致、2つ重ねると最長一致です。

ファイル名に . が含まれない場合、${name##*.} はパターンに一致しないので元の文字列がそのまま返ります。拡張子の有無で分岐したいときは、先に [[ $name == *.* ]] で確認してください。なお、この4つは POSIX sh でも使えます。

置換する:${変数/検索/置換}

url="http://example.com/wp/?p=1&p=2"

echo "${url/http:/https:}"   # 最初の1か所だけ置換
echo "${url//p=/page=}"      # すべて置換
echo "${url/#http/https}"    # 先頭に一致する場合だけ置換
echo "${url/%=2/=3}"         # 末尾に一致する場合だけ置換
echo "${url//\//_}"          # / を _ に(検索側の / は \ でエスケープ)

置換文字列を空にすれば削除になります。${str// /} は空白をすべて取り除きます。検索側も * や [0-9] などのグロブパターンが使えます。

bash 5.2以降は置換文字列の & に注意

bash 5.2では patsub_replacement というシェルオプションが追加され、既定で有効になっています。このオプションが有効だと、置換文字列の中の & は「一致した部分」に置き換わります。

s="tom"
echo "${s/tom/& and jerry}"     # bash 5.2以降: tom and jerry
echo "${s/tom/"&" and jerry}"   # & and jerry(引用符で囲むと文字として扱われる)
echo "${s/tom/\& and jerry}"    # & and jerry(\ でエスケープしても同じ)

URL のクエリ文字列のように & を含む値を置換文字列に使うと、古い bash と結果が変わります。置換文字列は変数に入れたうえで "${s/tom/"$rep"}" のように引用符で囲むのが安全です。shopt -u patsub_replacement で以前の挙動に戻すこともできます。

大文字・小文字の変換

bash 4.0以降では、tr を使わずに変換できます。

env="Production"
echo "${env^^}"   # PRODUCTION(すべて大文字)
echo "${env,,}"   # production(すべて小文字)
echo "${env^}"    # Production(先頭だけ大文字)

入力値を小文字にそろえてから比較すると、Yes / YES / yes のような揺れを1つの条件で扱えます。

既定値を補う

変数が未設定または空のときの扱いもパラメーター展開で書けます。

  • ${変数:-既定値}:未設定か空なら既定値を返す(変数は変わらない)
  • ${変数:=既定値}:未設定か空なら既定値を代入して返す
  • ${変数:?メッセージ}:未設定か空ならメッセージを出してスクリプトを終了する
  • ${変数:+代替値}:値が入っているときだけ代替値を返す
backup_dir="${BACKUP_DIR:-/var/backups/wp}"
: "${DB_NAME:?DB_NAME が設定されていません}"
rm -rf -- "${backup_dir:?}/tmp"   # 変数が空なら rm を実行させない

最後の行のように、削除コマンドのパスに :? を付けておくと、変数が空のときに rm -rf /tmp のような意図しない対象を消す事故を防げます。

部分一致と正規表現

文字列を含むかどうかは [[ ]] のパターン一致で判定できます。

ua="Mozilla/5.0 (compatible; Googlebot/2.1)"
if [[ $ua == *Googlebot* ]]; then
  echo "Googlebot を含む"
fi

より複雑な条件は =~ で拡張正規表現が使え、括弧で囲んだ部分は配列 BASH_REMATCH に入ります。

line='203.0.113.5 - - [18/Oct/2026:10:00:00 +0900] "GET /wp-login.php HTTP/1.1" 403 512'
re='"([A-Z]+) ([^ ]+) HTTP/[0-9.]+" ([0-9]{3})'

if [[ $line =~ $re ]]; then
  echo "method=${BASH_REMATCH[1]} path=${BASH_REMATCH[2]} status=${BASH_REMATCH[3]}"
fi

正規表現はこの例のように変数に入れ、=~ の右辺は引用符で囲まずに書きます。右辺を "..." で囲むと、正規表現ではなく文字列そのものとの一致になってしまいます。

前後の空白を取り除く

組み込みの trim 関数はありませんが、パターン削除の組み合わせで書けます。

trim() {
  local s="$1"
  s="${s#"${s%%[![:space:]]*}"}"   # 先頭の空白を削除
  s="${s%"${s##*[![:space:]]}"}"   # 末尾の空白を削除
  printf '%s' "$s"
}
value="$(trim "   wp_options   ")"
echo "[$value]"   # [wp_options]

内側の ${s%%[![:space:]]*} で「先頭に並んでいる空白部分」を取り出し、外側でそれを先頭から削除しています。内側を引用符で囲むのは、取り出した部分をパターンではなく文字列として扱うためです。

パラメーター展開と sed・awk の使い分け

ループの中で $(echo "$x" | sed ...) と書くと、1回ごとにサブシェルと外部コマンドが起動するため、件数が多いと目に見えて遅くなります。変数1つを加工する処理はパラメーター展開で書き、ファイル全体を加工する処理は sed や awk に任せる、という分け方が基本です。

また、スクリプトを sh で実行する可能性がある場合は、POSIX で使える ${#変数}・#・##・%・%%・:- などにとどめ、スライス・置換・大文字小文字変換は bash で実行することを shebang(#!/usr/bin/env bash)で明示してください。

まとめ

  • 長さは ${#変数}。マルチバイト文字はロケールによって数え方が変わる
  • 切り出しは ${変数:開始:長さ}。負の値は空白か括弧を付けて書く
  • #・## は先頭、%・%% は末尾を削除し、パスやファイル名の加工に使える
  • 置換は / で1か所、// ですべて。bash 5.2以降は置換文字列の & に注意する
  • 1つの変数の加工はパラメーター展開、ファイル全体の加工は sed・awk と使い分ける

お気軽にご相談ください

お見積りへ お問い合わせへ
▲