文字列操作(パラメータ展開)
| 対応: | 全Linux | |
|---|---|---|
| macOS(2001 Cheetah) | ||
| Bash 1.0(1989) |
Bashのパラメータ展開(Parameter Expansion)は、外部コマンドを呼び出さずにシェル変数の文字列を操作できる機能です。${変数名} の形式に各種オペレーターを組み合わせることで、文字数取得・部分文字列抽出・削除・置換・大文字小文字変換・デフォルト値設定などを実現できます。
文字列の長さ
変数の値の文字数を取得するには ${#変数名} を使います。
${#var}
name="桐生一馬"
echo ${#name}
4
配列に使うと要素数ではなく要素の文字数を返します。配列要素数は ${#array[@]} で取得します。
members=("桐生一馬" "真島吾朗" "秋山駿")
echo ${#members[@]}
3
echo ${#members[0]}
4
部分文字列の抽出
${var:offset:length} でオフセット位置から指定した長さ分の文字列を取り出します。オフセットは 0 始まりです。length を省略すると末尾まで取り出します。
${var:offset}
${var:offset:length}
filename="kamurocho_map.dat"
echo ${filename:10}
map.dat
echo ${filename:10:3}
map
オフセットに負の値を指定すると末尾から数えた位置になります。負の値は スペースを挟んで 書くか、括弧で囲みます(${var: -3} または ${var:(-3)})。スペースなしで書くと後述のデフォルト値構文 ${var:-default} と混同されます。
filename="kamurocho_map.dat"
echo ${filename: -3}
dat
先頭/末尾からの削除
パターンに一致する部分を先頭または末尾から削除します。# は先頭から最短一致、## は先頭から最長一致、% は末尾から最短一致、%% は末尾から最長一致で削除します。
${var#pattern} # 先頭から最短一致で削除
${var##pattern} # 先頭から最長一致で削除
${var%pattern} # 末尾から最短一致で削除
${var%%pattern} # 末尾から最長一致で削除
パターンにはグロブ文字(*・?・[...])が使えます。
| 構文 | 削除方向 | 一致方式 |
|---|---|---|
| ${var#pattern} | 先頭から | 最短一致(非貪欲) |
| ${var##pattern} | 先頭から | 最長一致(貪欲) |
| ${var%pattern} | 末尾から | 最短一致(非貪欲) |
| ${var%%pattern} | 末尾から | 最長一致(貪欲) |
ファイル名からディレクトリパスを削除して basename を取り出す例です。${path##*/} は先頭から */ の最長一致部分(最後の / まで)を削除します。
path="/home/kiryu/clan_roster.txt"
echo ${path#*/}
home/kiryu/clan_roster.txt
echo ${path##*/}
clan_roster.txt
拡張子を削除して dirname を取り出す例です。${path%/*} は末尾から /* の最短一致部分(最後の / 以降)を削除します。
path="/home/kiryu/clan_roster.txt"
echo ${path%.txt}
/home/kiryu/clan_roster
echo ${path%/*}
/home/kiryu
ログのタイムスタンププレフィックスを削除する例です。
line="2024-01-15 [INFO] Kiryu Kazuma logged in"
echo ${line#* }
[INFO] Kiryu Kazuma logged in
置換
${var/pattern/replacement} は最初に一致したパターンを置換します。${var//pattern/replacement} は一致した全箇所を置換します。
${var/pattern/replacement} # 最初の一致を置換
${var//pattern/replacement} # すべての一致を置換
先頭一致のみ置換するには ${var/#pattern/replacement}、末尾一致のみ置換するには ${var/%pattern/replacement} を使います。
${var/#pattern/replacement} # 先頭一致のみ置換
${var/%pattern/replacement} # 末尾一致のみ置換
replacement を省略(または空文字)にすると削除と同じ動作になります。
| 構文 | 置換対象 |
|---|---|
| ${var/pattern/replacement} | 最初の一致 |
| ${var//pattern/replacement} | すべての一致 |
| ${var/#pattern/replacement} | 先頭一致のみ |
| ${var/%pattern/replacement} | 末尾一致のみ |
CSV の区切り文字をタブに変換する例です。
record="Majima Goro,majima@example.com,Majima Family"
echo ${record/,/$'\t'}
Majima Goro majima@example.com,Majima Family
全カンマをタブに変換する例です(// で全置換)。
record="Majima Goro,majima@example.com,Majima Family"
echo ${record//,/$'\t'}
Majima Goro majima@example.com Majima Family
ログファイル内のエラーキーワードを置換する例です。
log_entry="ERROR: majima_everywhere.log access denied"
echo ${log_entry/ERROR/WARN}
WARN: majima_everywhere.log access denied
大文字/小文字変換(Bash 4以降)
Bash 4.0 以降では、パラメータ展開で大文字/小文字を変換できます。${var^^} は全文字を大文字に、${var,,} は全文字を小文字に変換します。${var^} は先頭1文字だけ大文字に、${var,} は先頭1文字だけ小文字にします。
${var^^} # すべて大文字
${var,,} # すべて小文字
${var^} # 先頭1文字を大文字
${var,} # 先頭1文字を小文字
パターンを指定すると、一致する文字のみ変換します(例: ${var^^[a-z]})。
| 構文 | 変換内容 |
|---|---|
| ${var^^} | 全文字を大文字に変換 |
| ${var,,} | 全文字を小文字に変換 |
| ${var^} | 先頭1文字を大文字に変換 |
| ${var,} | 先頭1文字を小文字に変換 |
| ${var^^pattern} | patternに一致する文字を大文字に変換 |
| ${var,,pattern} | patternに一致する文字を小文字に変換 |
name="kiryu kazuma"
echo ${name^^}
KIRYU KAZUMA
echo ${name^}
Kiryu kazuma
echo ${name^^k}
Kiryu Kazuma
環境変数名を大文字に統一したり、ユーザー入力を正規化したりするのに使えます。
status="active"
echo ${status^^}
ACTIVE
echo "Status: ${status^^}"
Status: ACTIVE
デフォルト値・エラーチェック
変数が未定義またはnullの場合にデフォルト値を返したり、変数に値を代入したりできます。
${var:-default} # 未定義またはnullのときdefaultを返す(変数は変更しない)
${var:=default} # 未定義またはnullのときdefaultを返し、変数にも代入する
${var:+value} # 定義済みかつnull以外のときvalueを返す(変数は変更しない)
${var:?error_msg} # 未定義またはnullのときerror_msgを表示してスクリプトを終了する
:- と := の違いは、後者が変数自体に代入する点です。コロン(:)を省くと未定義のみに反応し、null(空文字)は通過します。
| 構文 | 条件 | 動作 | 変数への代入 |
|---|---|---|---|
| ${var:-default} | 未定義またはnull | defaultを返す | しない |
| ${var:=default} | 未定義またはnull | defaultを返す | する |
| ${var:+value} | 定義済みかつnull以外 | valueを返す | しない |
| ${var:?error_msg} | 未定義またはnull | エラー終了 | しない |
| ${var-default} | 未定義のみ | defaultを返す | しない |
| ${var=default} | 未定義のみ | defaultを返す | する |
| ${var+value} | 定義済み(nullも含む) | valueを返す | しない |
| ${var?error_msg} | 未定義のみ | エラー終了 | しない |
unset clan
echo ${clan:-"Dojima Family"}
Dojima Family
echo $clan
clan=""
echo ${clan:-"Dojima Family"}
Dojima Family
echo ${clan-"Dojima Family"}
:= は変数にも代入するので、初期化に使えます。
unset logfile
echo ${logfile:=/var/log/clan_roster.log}
/var/log/clan_roster.log
echo $logfile
/var/log/clan_roster.log
:? はスクリプトの必須引数チェックに使えます。変数が未定義またはnullの場合、エラーメッセージを標準エラー出力に出してスクリプトを終了します(exit status 1)。
echo ${TARGET_DIR:?"TARGET_DIR が未設定です"}
bash: TARGET_DIR: TARGET_DIR が未設定です
サンプルコード
パラメータ展開を組み合わせてファイルを処理するスクリプトです。ファイル名から拡張子を除いたベース名の取得・拡張子の抽出・バックアップ先ディレクトリのデフォルト値設定を行います。
clan_roster_backup.sh
#!/bin/bash
# 必須変数のチェック(未設定ならエラー終了)
INPUT_FILE=${1:?"使い方: $0 <ファイル名>"}
BACKUP_DIR=${BACKUP_DIR:-/tmp/backup}
# ファイル名の各部分を分解
filename=${INPUT_FILE##*/} # ディレクトリ部分を除去
basename=${filename%.*} # 拡張子を除去
ext=${filename##*.} # 拡張子だけを取り出す
ext_lower=${ext,,} # 拡張子を小文字に統一
echo "ファイル名: $filename"
echo "ベース名: $basename"
echo "拡張子: $ext_lower"
echo "バックアップ先: $BACKUP_DIR"
# バックアップ先が存在しなければ作成
[ -d "$BACKUP_DIR" ] || mkdir -p "$BACKUP_DIR"
# タイムスタンプ付きでコピー
timestamp=$(date +%Y%m%d_%H%M%S)
cp "$INPUT_FILE" "$BACKUP_DIR/${basename}_${timestamp}.${ext_lower}"
echo "バックアップ完了: ${basename}_${timestamp}.${ext_lower}"
bash clan_roster_backup.sh /home/kiryu/clan_roster.txt ファイル名: clan_roster.txt ベース名: clan_roster 拡張子: txt バックアップ先: /tmp/backup バックアップ完了: clan_roster_20240115_093012.txt
ログファイルのエントリを正規化するスクリプトです。キャラクター名を大文字に変換し、不正な区切り文字を正規化します。
normalize_log.sh
#!/bin/bash
LOGFILE=${1:-majima_everywhere.log}
while IFS= read -r line; do
# タイムスタンププレフィックスを除去
content=${line#*] }
# カンマをパイプに統一
normalized=${content//,/|}
# 先頭を大文字に
normalized=${normalized^}
echo "$normalized"
done < "$LOGFILE"
概要
パラメータ展開はサブシェルや外部プロセスを起動しないため、sed や awk、cut といった外部コマンドより高速です。大量のファイルをループ処理するスクリプトでは特に差が出ます。
ただし、正規表現は使えません(グロブパターンのみ)。複雑なパターンマッチや後方参照が必要な場合は sed や awk を使います。また、大文字/小文字変換(^^・,,)は Bash 4.0 以降の機能です。macOS のデフォルトシェルは Zsh に変わっていますが、macOS に同梱されている /bin/bash は Bash 3.2(ライセンスの都合)のため、^^・,, は使えません。Homebrew でインストールした Bash 5.x か Zsh を使うことで回避できます。
よくあるミス
よくあるミス: # と % の方向を間違える
キーボード上で # は左側・% は右側にあります。これをそのまま覚えると「# = 左(先頭)から削除」「% = 右(末尾)から削除」と対応します。
先頭(左)からディレクトリ部分を削除してファイル名を取り出す例です。
path="/home/akiyama/real_estate_records.csv"
echo ${path##*/}
real_estate_records.csv
末尾(右)から拡張子を削除する場合は % を使います。# を誤って使うと意図しない結果になります。
path="/home/akiyama/real_estate_records.csv"
echo ${path%.csv}
/home/akiyama/real_estate_records
echo ${path#*.}
csv
NG: 末尾の拡張子を削除したいのに # を使ってしまった例です。
path="real_estate_records.csv"
echo ${path#*.csv}
real_estate_records.csv
${path#*.csv} は「先頭から *.csv に最短一致する部分を削除」しようとしますが、*.csv は先頭からのパターンなので、先頭が *.csv(任意文字列 + .csv)に一致するか判定されます。real_estate_records.csv はそのパターンに一致するため全体が削除されて空文字になります(Bash の挙動によっては元の文字列が返ることもあります)。末尾の拡張子を取り除くには ${path%.*} または ${path%.csv} を使います。
もう1つのよくあるミスは、## と % の組み合わせ(または # と %% の組み合わせ)の意味を混同することです。## は先頭からの最長一致で1回だけ削除します。//(置換の全置換)とは異なります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。