bashのパラメータ展開で文字列処理を完結させる方法|${var%}・${var//}でsed・awkの外部呼び出しを減らす設計
「ファイル名から拡張子だけ取り除きたいのに、毎回sedを書くのが面倒で...」
「ループの中で変数の一部を置換したいだけなのに、awkまで呼び出してしまっている」
こういった状況に覚えがあるなら、bashのパラメータ展開を使いこなすことでスクリプトをシンプルかつ高速にできます。bashには変数の中身を直接操作する組み込みの文字列処理機能が揃っており、外部コマンドを呼ばずにほとんどの変数内文字列操作を完結できます。
この記事では、bashパラメータ展開の基本構文から実践的な設計パターンまでを体系的に解説します。sed・awk・cut・trといった外部コマンドとの使い分けの指針も示します。動作確認環境はbash 5.2.x(RHEL 9.4 / Ubuntu 24.04 LTS)です。
この記事のポイント
・${var%pattern}と${var#pattern}で拡張子・ディレクトリを分離できる
・${var//pattern/replacement}でsed不要の変数内一括置換が可能
・${var^^}/${var,,}でtr不要の大文字・小文字変換ができる
・ループ内の外部コマンド呼び出しを排除しスクリプトを高速化できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
bashパラメータ展開とは何か(外部コマンドとの違い)
bashの「パラメータ展開」とは、変数の値を参照する際に、同時に文字列の切り出し・削除・置換などの操作を行う機能です。${}の中に特定の演算子を組み合わせることで動作します。
外部コマンドとの最大の違いはプロセスを起動しない点にあります。sedやawkは呼び出すたびに子プロセスが生成され、パイプを通じてデータが転送されます。ループの中で1万回呼んだとすれば、1万プロセスが起動することになります。パラメータ展開はbashの組み込み機能なのでプロセス起動のオーバーヘッドがなく、実行速度が大幅に異なります。
# 外部コマンド(sed)でファイル名から .log を除去する例 for f in /var/log/*.log; do name=$(echo "$f" | sed 's/\.log$//') echo "$name" done # パラメータ展開で同じことをする(プロセス起動なし) for f in /var/log/*.log; do name="${f%.log}" echo "$name" done # 実行結果(どちらも同一の出力) # /var/log/messages # /var/log/secure # /var/log/cron
後者はbashの内部処理だけで完結するため、ループ回数が増えるほど差が大きくなります。スクリプトのボトルネックがループ内の外部コマンド呼び出しにある場合、パラメータ展開への置き換えは即効性のある最適化手段です。
基本の文字列切り出し: ${var:offset:length}
最もシンプルな文字列操作は部分文字列の切り出しです。Pythonのスライス記法に近い感覚で使えます。開始位置は0から数えます。
# 基本構文: ${変数名:開始位置:文字数} str="LinuxMaster" # 0文字目から5文字を取り出す echo "${str:0:5}" # Linux # 5文字目以降を全部取り出す echo "${str:5}" # Master # 末尾から数えるにはマイナスを使う(コロンとマイナスの間にスペースが必要) echo "${str: -6}" # Master # 動作確認: bash 5.2.15 (RHEL 9.4)
重要な注意点: マイナス(負のオフセット)を指定する場合、コロンとマイナスの間に半角スペースを1つ置かないと、デフォルト値展開(${var:-default})として解釈されてしまいます。これは初心者が必ずハマるポイントです。スペースを入れ忘れた場合、構文エラーにはならず意図しないデフォルト値展開が起きるため、気づきにくいバグになります。
前方・後方一致削除: ${var#}・${var%}の4種類を使いこなす
パラメータ展開で最もよく使うのが、パターンに一致する部分を削除する演算子です。「#(シャープ)」が前方一致、「%(パーセント)」が後方一致です。それぞれ1個が最短一致、2個が最長一致になります。
path="/var/log/nginx/access.log.2024" # ${var#pattern} : 前方から最短一致で削除 echo "${path#*/}" # var/log/nginx/access.log.2024 # ${var##pattern} : 前方から最長一致で削除(basename相当) echo "${path##*/}" # access.log.2024 # ${var%pattern} : 後方から最短一致で削除(最後の拡張子だけ除去) echo "${path%.*}" # /var/log/nginx/access.log # ${var%%pattern} : 後方から最長一致で削除(最初のドット以降を全除去) echo "${path%%.*}" # /var/log/nginx/access # 動作確認: bash 5.2.15 (RHEL 9.4)
この4種類を覚えるだけで、ファイルパス操作の大半が外部コマンドなしで実装できます。特に重要なのは以下の2つの組み合わせです。
・${var##*/} は basename コマンドの代替(ディレクトリパスを除去)
・${var%/*} は dirname コマンドの代替(ファイル名部分を除去)
# tarファイルを処理するスクリプト例 archive="/backup/data_20240712.tar.gz" # basename相当(ディレクトリ部分を除去) filename="${archive##*/}" # data_20240712.tar.gz # .tar.gz を除去(最長一致で .tar.gz まで削除) basename="${filename%%.tar.*}" # data_20240712 # .gz だけ除去して .tar が残るパターン notar="${filename%.gz}" # data_20240712.tar echo "ファイル名: $filename" echo "ベース名: $basename" echo "tar残り: $notar" # 実行結果(RHEL 9.4 bash 5.2.15 で確認済み) # ファイル名: data_20240712.tar.gz # ベース名: data_20240712 # tar残り: data_20240712.tar
tarアーカイブの圧縮・展開についてはtar コマンドの実用例も参照してください。tarコマンドによるアーカイブ処理と組み合わせることで、ファイル名を自動生成・加工する自動化スクリプトを効率的に書けるようになります。
文字列置換: ${var/}・${var//}でsedの外部呼び出しをなくす
変数内の文字列を置換するにはスラッシュ演算子を使います。sedのs/pattern/replacement/に相当しますが、外部プロセスを起動しません。
str="hello world hello" # ${var/pattern/replacement} : 最初の1箇所だけ置換 echo "${str/hello/bye}" # bye world hello # ${var//pattern/replacement} : 全箇所を置換(グローバル置換) echo "${str//hello/bye}" # bye world bye # 置換先を省略すると削除になる echo "${str//hello/}" # world (スペースが残る) # スペースを全部除去する echo "${str// /}" # helloworldhello # 前方一致のみ置換(先頭から) echo "${str/#hello/bye}" # bye world hello # 後方一致のみ置換(末尾から) echo "${str/%hello/bye}" # hello world bye
パターンにはグロブ(ワイルドカード)が使えますが、正規表現は使えません。.*ではなく*です。「任意の文字列」は*、「任意の1文字」は?です。これがsedとの最大の違いで、正規表現が必要な複雑なパターンマッチはsedを使うべき場面です。
# 実用例1: ファイル名のスペースをアンダースコアに変換 filename="my report 2024 final.txt" safe_name="${filename// /_}" # my_report_2024_final.txt echo "$safe_name" # 実用例2: URLのhttpをhttpsに変換(最初の1箇所のみ) url="http://www.example.com/path" secure_url="${url/http:/https:}" echo "$secure_url" # https://www.example.com/path # 実用例3: カンマ区切りをパイプ区切りに変換 csv="apple,banana,cherry" psv="${csv//,/|}" # apple|banana|cherry echo "$psv" # 動作確認: bash 5.2.15 / RHEL 9.4
シェルスクリプト講座を見る >>
大文字・小文字変換: ${var^^}・${var,,}でtrをなくす
bash 4.0以降(RHEL 7・Ubuntu 14.04以降に標準搭載)では、大文字・小文字変換の演算子も使えます。従来はtr '[:lower:]' '[:upper:]'のような外部コマンドが必要でしたが、これも外部プロセスを起動せずに完結します。
str="Hello World" # ${var^^} : 全文字を大文字に変換 echo "${str^^}" # HELLO WORLD # ${var,,} : 全文字を小文字に変換 echo "${str,,}" # hello world # ${var^} : 先頭1文字だけ大文字に変換 str2="hello world" echo "${str2^}" # Hello world # ${var,} : 先頭1文字だけ小文字に変換 echo "${str,}" # hello World # 特定の文字だけ変換するパターン指定も可能 echo "${str^^[aeiouAEIOU]}" # hEllO wOrld (母音だけ大文字化) # trコマンドとの比較(外部プロセスが起動する方法) echo "Hello World" | tr '[:lower:]' '[:upper:]' # HELLO WORLD # 動作確認: bash 5.2.15 / RHEL 9.4
ホスト名やユーザー名の正規化処理で入力値を小文字に統一する用途でよく使います。ループ内で大量の変数を変換する場合、外部コマンドとの速度差が顕著に出ます。
実践例: ファイル名操作・拡張子処理をパラメータ展開だけで実装
実際のシェルスクリプトでパラメータ展開を活用するシナリオを見ていきます。よくあるユースケースは「ディレクトリ内の全ファイルを一括処理する」というものです。
1. 拡張子の大文字・小文字を統一するリネームスクリプト
#!/bin/bash # /data/images 配下の .JPG を .jpg にリネーム # (Windowsからコピーした場合など拡張子が大文字になっているケース) src_dir="/data/images" for f in "${src_dir}"/*.JPG; do # グロブが展開されなかった場合の除外 [ -f "$f" ] || continue # ファイル名のみ取り出す(##で最長前方一致削除) filename="${f##*/}" # .JPG を .jpg に付け替え(%で後方最短一致削除して再連結) newname="${filename%.JPG}.jpg" mv "${src_dir}/${filename}" "${src_dir}/${newname}" echo "Renamed: ${filename} -> ${newname}" done # 実行結果例(検証サーバー RHEL 9.4 で確認) # Renamed: IMG_0001.JPG -> IMG_0001.jpg # Renamed: IMG_0002.JPG -> IMG_0002.jpg
2. ログファイルのアーカイブスクリプト
#!/bin/bash # アプリログを日付付きファイル名でアーカイブする logfile="/var/log/app/app.log" datestamp=$(date +%Y%m%d) # ディレクトリ部分とファイル名を分離 basedir="${logfile%/*}" # /var/log/app logname="${logfile##*/}" # app.log # 拡張子の手前でファイル名を分割して日付を挿入 stem="${logname%.*}" # app ext="${logname##*.}" # log archived="${basedir}/${stem}_${datestamp}.${ext}" mv "$logfile" "$archived" echo "Archived: $archived" # 実行結果例 # Archived: /var/log/app/app_20240712.log
ファイルの一覧確認にはls コマンドの基本オプションが役立ちます。ls -ltでタイムスタンプ順に並べて確認すると、アーカイブ処理の結果を素早く把握できます。
sed・awkの外部呼び出しを減らす設計パターン
「外部コマンドを減らす」という設計方針を実践するには、置き換えられる場面と置き換えられない場面を正確に把握することが大切です。代表的なパターンを整理します。
1. ループ内の外部コマンドを排除する
最も効果的なのはforループやwhileループ内の外部コマンドを排除することです。ループの外で1回呼ぶ外部コマンドはそれほど問題になりませんが、ループ内で毎回呼ぶ場合は大きなボトルネックになります。
# 改善前: ループ内でsedを毎回呼ぶ for item in "${list[@]}"; do clean=$(echo "$item" | sed 's/^[[:space:]]*//') # 先頭空白除去 process "$clean" done # 改善後: パラメータ展開で置き換え(プロセス起動なし) for item in "${list[@]}"; do clean="${item#"${item%%[! ]*}"}" # 先頭空白除去 process "$clean" done # 汎用trim関数として定義する場合 trim() { local s="$1" s="${s#"${s%%[! ]*}"}" # 先頭の空白とタブを除去 s="${s%"${s##*[! ]}"}" # 末尾の空白とタブを除去 printf '%s' "$s" }
2. 単純な変数置換はパラメータ展開で完結させる
# 改善前: 単純な置換にsedを使う result=$(echo "$url" | sed 's/http:/https:/') # 改善後: パラメータ展開で十分 result="${url/http:/https:}" # 改善前: 拡張子除去にcutを使う noext=$(echo "$file" | cut -d. -f1) # 改善後: パラメータ展開でシンプルに noext="${file%.*}" # 改善前: 大文字変換にtrを使う upper=$(echo "$name" | tr '[:lower:]' '[:upper:]') # 改善後: パラメータ展開(bash 4.0以降) upper="${name^^}"
3. 配列全要素への一括適用
配列の各要素にパラメータ展開を適用する場合、${array[@]//pattern/replacement}という構文で全要素を一度に変換できます。ループを書かずに済みます。
# 配列全要素の文字列置換(bash 4.x以降) files=("report 2024.txt" "data file.csv" "log 01.log") # 全要素のスペースをアンダースコアに変換 safe_files=("${files[@]// /_}") # 結果: ("report_2024.txt" "data_file.csv" "log_01.log") # 全要素を大文字に変換 upper_files=("${files[@]^^}") # 結果: ("REPORT 2024.TXT" "DATA FILE.CSV" "LOG 01.LOG") printf '%s ' "${safe_files[@]}" # 実行結果(RHEL 9.4 bash 5.2.15 で確認) # report_2024.txt # data_file.csv # log_01.log
パラメータ展開の限界と使い分けの指針
パラメータ展開はすべての文字列処理を置き換えられるわけではありません。以下の場合は外部コマンドを使うべきです。
1. 正規表現が必要な場合はsedか[[ =~ ]]を使う
パラメータ展開のパターンはグロブ(ワイルドカード)です。[0-9]+のような数量子(+や{n,m})は使えません。「1桁以上の連続した数字」のような複雑なパターンマッチには、bashの条件式[[ =~ ]]またはsedが必要です。
str="order_12345_done" # グロブパターンで数字列を削除(パターンは [0-9]*) echo "${str//_[0-9]*/}" # order # 正確な数字列の抽出には [[ =~ ]] + BASH_REMATCH if [[ "$str" =~ _([0-9]+)_ ]]; then echo "${BASH_REMATCH[1]}" # 12345 fi # または sed(完全な正規表現) echo "$str" | sed 's/.*_\([0-9]*\)_.*//' # 12345
2. ファイル全体の行処理はsed/awkが適切
パラメータ展開は変数内の文字列を処理するものです。テキストファイルの各行を変換したり、複数行にまたがるパターンを処理する場合は、sedやawkの出番です。パラメータ展開とsed/awkは競合するものではなく、用途が異なります。
| やりたいこと | 推奨手段 |
|---|---|
| 変数内の前後一致削除 | ${var#p} / ${var%p} |
| 変数内の文字列置換(グロブ) | ${var//p/r} |
| 変数内の大文字・小文字変換 | ${var^^} / ${var,,} |
| 変数内の部分文字列切り出し | ${var:offset:len} |
| 変数内の正規表現マッチと抽出 | [[ =~ ]] + BASH_REMATCH |
| ファイル全体の正規表現置換 | sed |
| ファイルの列操作・集計 | awk |
| 1文字単位の変換・削除 | ${var//c/r} または tr |
本記事のまとめ
bashのパラメータ展開は、変数内の文字列処理を外部コマンドなしで完結させる強力な機能です。特にループ内での使用では、sedやawkを呼ぶ場合と比べてプロセス起動コストがゼロになるため、スクリプトの高速化に直結します。
パラメータ展開でできる主な操作を最後に整理します。
| 操作 | 構文 | 代替コマンド |
|---|---|---|
| 部分文字列切り出し | ${var:offset:length} |
cut, awk |
| 前方一致削除(最短) | ${var#pattern} |
sed, cut |
| 前方一致削除(最長) | ${var##pattern} |
basename |
| 後方一致削除(最短) | ${var%pattern} |
sed |
| 後方一致削除(最長) | ${var%%pattern} |
sed |
| 最初の1箇所を置換 | ${var/pattern/replacement} |
sed |
| 全箇所を置換 | ${var//pattern/replacement} |
sed -g |
| 全文字を大文字に | ${var^^} |
tr |
| 全文字を小文字に | ${var,,} |
tr |
一方で、正規表現が必要な複雑なパターンや、ファイル全体の行処理はsed/awkが適しています。「変数内の処理はパラメータ展開、ファイル全体の処理はsed/awk」という使い分けを基本として押さえておくと、スクリプト設計の判断が速くなります。
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:shellcheckでシェルスクリプトを静的解析する方法|インストールからCI組み込み・頻出警告の直し方まで
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます