2026年6月24日

2026年6月24日

bashの文字列操作(スライス・置換・長さ取得)をパラメータ展開で行う方法

bash では、文字列の長さは ${#変数}、一部の切り出しは ${変数:位置:長さ}、置換は ${変数/前/後} と、外部コマンドを呼ばずにパラメータ展開だけで処理できます。この記事では、長さ・スライス・前後の削除・置換を中心に、大文字小文字の変換、既定値、分割、正規表現までを、ファイル名やバージョン番号を扱うサーバー作業の例とあわせて説明します。日本語を含む文字列で結果がずれる原因と、bash のバージョン差によるつまずきにもふれます。

前提:bash のバージョンとシバン

ここで紹介する書き方の多くは bash 独自の機能で、/bin/sh(Debian や Ubuntu では dash)では動かないものがあります。スクリプトの1行目は #!/bin/bash にし、sh script.sh ではなく bash script.sh か実行権限を付けて起動します。

echo "$BASH_VERSION"

Linux の主要ディストリビューションでは、2026年10月現在 bash 5 系が標準です。一方、macOS に標準で入っている /bin/bash は 3.2 と古く、後述の大文字小文字の変換(bash 4.0 以降)や負の長さの指定(bash 4.2 以降)が使えません。手元の Mac で試すときは、Homebrew などで新しい bash を入れるか、サーバー上で確認してください。

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

s="wordpress-6.8.3.tar.gz"
echo "${#s}"    # 22

日本語を含む場合、数え方はロケールで変わります。UTF-8 のロケールなら文字数、C ロケールならバイト数になります。

j="ワードプレス"
echo "${#j}"                                     # 6 (UTF-8 ロケール)
LC_ALL=C bash -c 'j="ワードプレス"; echo "${#j}"'  # 18 (バイト数)

cron から実行したスクリプトでだけ文字数がずれる場合は、cron 環境のロケールが C になっていることが多いです。スクリプトの先頭で export LANG=C.UTF-8 のように明示すると挙動がそろいます(使えるロケール名は locale -a で確認できます)。

切り出す(スライス):${var:offset:length}

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

s="wordpress-6.8.3.tar.gz"
echo "${s:0:9}"      # wordpress
echo "${s:10}"       # 6.8.3.tar.gz
echo "${s: -6}"      # tar.gz   (末尾から6文字)
echo "${s:(-6):3}"   # tar
echo "${s:10:-7}"    # 6.8.3    (末尾の7文字を除く。bash 4.2以降)

末尾から数えるときの負の数には注意が必要です。${s:-6} と詰めて書くと、後述の「未設定なら既定値」の構文として解釈され、文字列全体が返ります。コロンの後に空白を入れるか、(-6) のように括弧で囲みます。

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

パターン(ワイルドカード)に一致する部分を、先頭または末尾から取り除きます。ファイル名の加工でとくによく使います。

書き方 意味
${var#パターン} 先頭から最短一致を削除
${var##パターン} 先頭から最長一致を削除
${var%パターン} 末尾から最短一致を削除
${var%%パターン} 末尾から最長一致を削除
path="/var/www/html/wp-content/uploads/2026/10/photo.final.jpg"
file="${path##*/}"
echo "$file"          # photo.final.jpg   (basename 相当)
echo "${path%/*}"     # /var/www/html/wp-content/uploads/2026/10   (dirname 相当)
echo "${file%.*}"     # photo.final   (最後の拡張子を除く)
echo "${file%%.*}"    # photo         (最初のドット以降を除く)
echo "${file##*.}"    # jpg           (拡張子だけ)

覚え方は、キーボードで # が $ の左、% が右にあるので「# は左(先頭)、% は右(末尾)」です。記号が2つなら最長一致になります。

拡張子のないファイル名に ${file##*.} を使うと、一致する部分がないためファイル名全体が返ります。拡張子の有無で処理を分けたいときは、先に [[ $file == *.* ]] で確認します。

置換する:${var/pattern/replacement}

s="a-b-c"
echo "${s/-/_}"     # a_b-c   (最初の1つだけ)
echo "${s//-/_}"    # a_b_c   (すべて)
echo "${s//-}"      # abc     (置換後を省略すると削除)

url="http://example.com/wp-content/uploads/a.jpg"
echo "${url/#http:/https:}"   # 先頭が一致するときだけ置換
f="photo.jpeg"
echo "${f/%.jpeg/.jpg}"       # 末尾が一致するときだけ置換 → photo.jpg

/# と /% は、文字列の途中にある同じ並びを誤って置換しないために使います。URL のスキームや拡張子の差し替えでは、こちらのほうが安全です。

bash 5.2 からは、置換後の文字列に含まれる & が「一致した部分」に置き換わる機能(patsub_replacement)が既定で有効になりました。& を文字として入れたいときは \& と書くか、shopt -u patsub_replacement で無効にします。古い bash で書いたスクリプトを新しい環境に移したときに結果が変わる原因になるので、置換後に & を含むスクリプトは確認しておくと安心です。

大文字・小文字の変換(bash 4.0 以降)

name="WordPress"
echo "${name^^}"   # WORDPRESS
echo "${name,,}"   # wordpress
echo "${name^}"    # WordPress (先頭1文字だけ大文字)

比較の前に小文字へそろえると、入力の揺れ(Yes/YES/yes など)を吸収できます。

未設定・空のときの既定値

WP_PATH="${WP_PATH:-/var/www/html}"     # 未設定か空なら既定値を使う
: "${BACKUP_DIR:=/var/backups/wp}"      # 未設定か空なら既定値を代入する
: "${DB_NAME:?DB_NAME が設定されていません}"  # 未設定か空ならエラーで終了
echo "${DEBUG:+デバッグモードです}"      # 設定されているときだけ表示

:? は、必須の値がないまま処理が進むのを防ぐのに便利です。たとえば削除先のディレクトリを変数で組み立てるスクリプトで、変数が空のまま rm -rf "$DIR/"* が実行されると、意図しない場所を消すおそれがあります。危険な操作の前には :? で空でないことを確かめてください。

区切り文字で分割する

csv="nginx,php-fpm,mariadb"
IFS=',' read -r -a services <<< "$csv"
for svc in "${services[@]}"; do
  echo "$svc"
done

IFS=',' をコマンドの直前に書くと、その read の間だけ区切り文字が変わり、スクリプト全体の IFS には影響しません。-r はバックスラッシュをそのまま読むための指定で、付けておくのが基本です。

正規表現で取り出す:=~ と BASH_REMATCH

ver="6.8.3"
re='^([0-9]+)\.([0-9]+)(\.([0-9]+))?$'
if [[ $ver =~ $re ]]; then
  major="${BASH_REMATCH[1]}"
  minor="${BASH_REMATCH[2]}"
  echo "major=$major minor=$minor"
else
  echo "バージョンの形式が想定外です: $ver" >&2
fi

正規表現は変数に入れてから、引用符を付けずに =~ の右側に置きます。右側を引用符で囲むと、正規表現ではなく普通の文字列として比較されます。

前後の空白を取り除く

s="   hello world  "
s="${s#"${s%%[![:space:]]*}"}"   # 先頭の空白を削除
s="${s%"${s##*[![:space:]]}"}"   # 末尾の空白を削除
echo "[$s]"                      # [hello world]

内側の展開で「先頭(末尾)の空白の並び」を求め、外側でそれを削除しています。読みにくいので、関数にしてコメントを添えておくと後で困りません。

実践例:ファイル名を安全に組み立てる・一括で変える

サイト名からバックアップのファイル名を作る例です。小文字にそろえ、英数字とドット・ハイフン以外を _ に置き換えます。

site="Example.com/Blog"
safe="${site,,}"
safe="${safe//[!a-z0-9.-]/_}"
backup="${safe}_$(date +%Y%m%d_%H%M).sql.gz"
echo "$backup"   # 例: example.com_blog_20261018_0300.sql.gz

拡張子を一括で変える例です。最初は echo を付けたまま実行し、表示された mv コマンドが意図どおりかを確かめてから echo を外します。-n は同名のファイルがあれば上書きしない指定です。

for f in /var/www/html/wp-content/uploads/2026/10/*.jpeg; do
  [ -e "$f" ] || continue
  echo mv -n -- "$f" "${f%.jpeg}.jpg"
done

WordPress のアップロードファイルの名前を変えると、記事や添付ファイルのデータが参照している URL と食い違い、画像が表示されなくなります。実際に変える場合は、バックアップを取ったうえで、データベース側の参照も合わせて更新する手順をステージング環境で確認してください。

つまずきやすい点

  • 変数を引用符で囲まない:echo $s のように書くと、空白で分割されたりワイルドカードが展開されたりします。展開結果は基本的に "${...}" と引用符で囲みます。
  • 先頭が 0 の数字を計算に使う:08 のような値は 8 進数として扱われエラーになります。$((10#$n)) と書くと 10 進数として計算されます。逆にゼロ埋めしたいときは printf -v num '%03d' 7 で 007 が得られます。
  • ループの中で sed や awk を呼ぶ:数千回のループで $(echo "$s" | sed ...) を使うと、そのたびにプロセスが起動して遅くなります。パラメータ展開で書けるものは置き換えると速くなります。逆に、ファイル全体を加工するなら sed や awk のほうが向いています。

まとめ

  • 長さは ${#var}、スライスは ${var:位置:長さ}、末尾から数えるときは ${var: -6} のように空白か括弧を入れる
  • 前後の削除は #(先頭)と %(末尾)、記号2つで最長一致。ファイル名・拡張子・ディレクトリの取り出しに使える
  • 置換は / で最初の1つ、// ですべて、/# と /% で先頭・末尾だけ。bash 5.2 以降は置換後の & に注意
  • 日本語の長さはロケールで文字数とバイト数が変わるため、cron などではロケールを明示する
  • 大文字小文字の変換や負の長さは古い bash(macOS 標準の 3.2 など)では使えないので、実行環境のバージョンを確認する

お気軽にご相談ください

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