Help us understand the problem. What is going on with this article?

POSIX shの変数展開による文字列置換の限界を探る

More than 3 years have passed since last update.

シェルスクリプト中で文字列置換をしたくなったら普通はsedやawkを呼び出すのが正解でしょう。ただ、世の中にはプロセスのforkが異常に遅い環境というのがあるので(CygwinとかMSYSとか)、シェルスクリプト内で頑張りたくなることがあります。

そんなときに使えるのがシェルの変数展開の機能です。特に下記の4つの書き方はPOSIX shがサポートしているのでポータビリティの観点でも安心して使うことができます。

表現 説明
${parameter%パターン} $parameterの末尾からパターンにマッチする文字列を取り除いた値を返す
(最短一致)
${parameter%%パターン} $parameterの末尾からパターンにマッチする文字列を取り除いた値を返す
(最長一致)
${parameter#パターン} $parameterの先頭からパターンにマッチする文字列を取り除いた値を返す
(最短一致)
${parameter##パターン} $parameterの先頭からパターンにマッチする文字列を取り除いた値を返す
(最長一致)

これらを使ってどこまで頑張れるのかについて調べてみました。

パターンの書き方

上記のパターンの部分はいわゆるワイルドカードマッチで、次のような表現が使えます。

表現 説明
? 任意の1文字にマッチ
* 任意の0文字以上にマッチ
[abc] 文字a,b,cいずれか1文字にマッチ
[!abc] 文字a,b,c以外の1文字にマッチ
[a-z] aからzまでの1文字にマッチ
[!a-z] aからz以外の1文字にマッチ

上の表を見てわかる通り、パターンマッチの能力はかなり貧弱です。特に、直前の表現のn回繰り返し(正規表現で言う*)が使えないのは非常に痛いですね。

POSIX shでできること一覧

わかりやすさのため、変数展開で出来ることを正規表現と並べて記述してみました。

先頭または末尾の固定文字列の削除

正規表現 POSIX shで対応する変数展開 README.mdに対して適用した結果
s/\.md$// ${str%.md} README
s/^README// ${str#README} .md

元の文字列と比較することでマッチングの成功失敗も判定できますから、次のようにifで分岐すれば文字列置換を行うことも可能です(もちろん、こんな書き方をするくらいならsedなどを使うべきだと思いますが)。

tmp=${path%.gif}
if [ "$path" != "$tmp" ]; then
  tmp="${tmp}.jpg"
fi

先頭から特定の文字列までの削除・特定の文字列から末尾までの削除

abc=123=foo=barから=より前(abc)や=より後(bar)を取り出したいような場合も簡単に記述可能です。

正規表現 POSIX shで対応する変数展開 abc=123=foo=barに対して適用した結果
s/=.*$// ${str%%=*} abc
s/=[^=]*$// ${str%=*} abc=123=foo
s/^.*=// ${str##*=} bar
s/^[^=]*=// ${str#*=} 123=foo=bar

これと同じ書き方で、basenameコマンドを${path##*/}で、dirnameコマンドを${path%/*}で代用する、などというのが定番の使い方だと言えます。

文字列先頭から特定の文字群が連続する限り取り出す

Abc123-foo:barから、先頭や最後でアルファベットが連続している部分(Abcbar)を取り出したいような場合もPOSIX shで記述可能です。

正規表現 POSIX shで対応する変数展開 Abc123-foo:barに対して適用した結果
s/^([A-Za-z]*).*$/$1/ ${str%%[!A-Za-z]*} Abc
s/^.*?([A-Za-z]*)$/$1/ ${str##*[!A-Za-z]} bar

逆にアルファベット連続だけを削除する方法はありません。不便ですね。

まとめ

今回調べてみて、POSIX shでもそれなりに文字列操作ができるんだな、という感想を持ちました。一方で、このような記法は保守性が非常に低いと個人的には感じます。実際に利用する際は補足コメントを書いておいた方が良いでしょう。

これより複雑な処理を書きたい場合はsedなどの外部コマンドを呼び出すか、ポータビリティはあきらめてbashやzshに依存したスクリプトを書くのが良さそうです。bash/zshならPOSIX正規表現(zshならPCREも使える)によるマッチングおよびキャプチャができるので、もう少し頑張れるはずです。

参考URL

hnw
境界値バグが大好物。自分の日記で書くには小ネタすぎるネタをQiitaに書いています。
https://hnw.hatenablog.com/
klab
モバイルオンラインゲーム、その他スマートフォン関連サービス、及びサーバーインフラ開発・運用
http://www.klab.com/jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした