読者です 読者をやめる 読者になる 読者になる

変数は入れ子 nest できるみたい

tech shellscript zsh
Grumpy Little Fella
Grumpy Little Fella / backpackphotography

変数は、入れ子 nest することができるらしく。

これを知ったのが、投げやりに走らせた中での事だった、と言うのも相まって、朝の通勤電車の中で思わず「をっ...」なんて声にしてしまい。
結構恥ずかしい思いをしました。

例えば、こんな感じです。;

dirname=${${url##*/}%.*}

ただしコレ、zsh に限るらしい。

zsh をメインに使ってるもんにとっては、"なるほど" な嬉しい発見だったりします。

が。
それ。ちょっと困ります。
なぜならば、os*1 が真っ新な状態って時のシェルと言うのは bash だったりするからです。

うーん、...じゃあ bash だと?

と言う中で知った幾つかに関するメモ、と言うのが今回のお話です。

今回の作業で見えてくると思われること。 ;

背景

github clone の url パス情報から、clone 時にデフォルトとなるディレクトリ名を獲りたい、と思ったのです。*2

具体的には、HTTPS clone URL が https://github.com/woowee/radiko.git だったとしたら、
ここで獲りたいのは、radiko と言う文字列。

パラメータ展開 Parameter Expansion

パスや URL からファイル名を獲る方法は、
"パラメータ展開 Parameter Expansion" を採っています。
この方法を採った理由と言うのは、特別技術的なものではなく、経験上以前使ったことあるから、と言う程度のもの。

この場合、2 つのステップを踏むことになります。;

  1. まず、url からこの最右端部分、上述例だと radiko.git を取得。*3
  2. 次に、1 で取得した文字列から更に今度は、radiko のみを獲る。*4

この方法の詳細については後ほど触れるとして。

実際のスクリプトも同じような感じで、素直に各ステップずつ 1 行を充てて書いていました。以下がイメージです。 ;

% $url=https://github.com/woowee/radiko.git
% $file=${url##*/}          # .. step 1) パスから最右端を獲る。この場合 "radiko.git"。
% $filename=${file%.*}      # .. step 2) 1 で獲ったファイル名から、拡張子なしの名前のみを取得。この場合 "radiko"。

なお。 パラメータ展開とは、についてはこちらが宜しいかと。;

ついでに、man のリンクも挙げておきます。;

変数はネストできる

そんな中、
「なんかなぁ」と思いつつ、突然ぼんやりと「コマンド置換で出来るんだから、変数でもできていーんじゃないだろーか。」なノリで、変数もネストして使ってみたら、;

% $url=https://github.com/woowee/radiko.git
% $filename=${${url##*/}%.*}

何か出来るみたいなのです。

ただし zsh のみ

ちょっと嬉しい驚きに胸躍らせて調べてみると、どうやらこの書き方、zsh のみらしいとのこと。
自身、普段使いのシェルは zsh で、だから上手く行った、と言う事らしい。

zsh では以下のように変数展開を一度に複数回行うことができます。 (bash では複数回の変数展開は行えません。2回に分けて行う必要があります)

残念なことにこれでは今回の要件を満たしてくれません。
なぜならば、ここで取り上げているスクリプトの利用シーンと言うのが、os が、インストール直後の、真っ新な状態で走らせることを想定しているからです。

os がデフォルトで提供しているシェルは、bash だったりします。;

Change the default login shell to bash

シェルが bash であることを前提に考えなくてはなりません。
変数をネストして使う事は出来ません。

bash を想定するのならば、やはり「2 回に分けて」なければならないのでしょうか。
...まさかあ、これは何時もの「自分が知らないだけ」のもので、実は何処かにきちんとスマートな方法がきっとある筈、と思いながら改めて探してみる事に。

bash は、やはり「2 回に分けて」

結論から申しますと、
やっぱりそう言うことみたいです。残念ながら。

こんなエントリーにヒットしました。;

What I have is this:

progname=${0%.} progname=${progname##/}

Can this be nested (or not) into one line, i.e. a single expression? Basically I'm trying to strip the path and extension off of a script name so that only the base name is left. The above two lines work fine. My 'C' nature is simply driving me to obfuscate these even more.

まさにドンぴしゃり。
同じような事を考えているようです。

で、これに対するリアクションはどれも... ;

Then no, you can't nest ${var} expressions. The bash syntax expander won't understand it.

とか。

Or, use zsh/ksh which can do the pattern nesting thing. :)

とか。

This nesting does not appear to be possible in bash, but it works in zsh:

progname=${${0%.}##/}

で、極めつけは、;

Actually it is possible to create nested variables in bash, using two steps. quotes

では bash では少なくとも 2 ステップ必要、と言うのは分かったとして bash で書くとしたら?
スレッド中にあった、幾つかのサジェスチョンに、"なるほど" 思ったので取り上げておきたいと思います。

方法 その1; ${!prefix*} 変数間接展開 indirect expansion

prefix で始まる全ての変数の名前に展開してくれるものとのこと。

文字が感嘆符ならば、... 残りの parameter からなる変数の値を変数の名前と見なします。

こういう使い方があるよ、ってサンプルコードを置いて行ってくれてるのですが、;

個人的には、この設問に対して、なぜこの答えなのか全く納得できなかったりするのですが、
どうなのでしょう。

こうなるんでしょうか?

url='https://github.com/woowee/radiko.git'
file=${url##*/}
parameter='file'
filename=${!perameter%.*}

どう考えてもかえって面倒くなっているように思えてならない。
あと、この 変数間接展開 と言うのが、どのようなシーンで使われるのかが想像できないというのもある。

きちんと理解しきれるだけの力がない自分が今、とっても面倒くさい。(笑

方法 その2; eval()

eval [arg ...]
arg を読み込み、1 つのコマンドに結合し、 このコマンドを読み込んで実行します。 その終了ステータスが eval の値として返されます。 args がない場合や空の引き数しかない場合には eval は 0 を返します。

と言う事らしいです。

url='https://github.com/woowee/radiko.git'
file=${url##*/}
expression='file'
eval filename=\${$expression%.*}

でいいのでしょうか...

ところで evaluation の "eval" なのですね。初めて知りました。orz

方法 その3; basename()

これだと bash でも 1 行で書ける。

ただし、eval とかと違って 基本コマンド builtin commands ではない、と言う点は気にしておいた方がよいかもしれませんね。
違うコマンドですが、"which よか type とか hash 使った方いいよ、なぜなら builtin comamnds だから" な論争は興味深い話でした。

と言う事で、こうなるのでしょう。;

url='https://github.com/woowee/radiko.git'
filename=$(basename ${url} '.git')

やっぱこう言うのが好きですけどね。

で「なるほど」と思ったのがあったので、ここに落としておきます。;

var=$( basename ${var%%.*} )

おまけ

ついでに、こういうのあったら良いのにな、と思っていたページやっと出遭えたので、話題とはズレますがメモしておきます。

"New Mac Checklist (and Mavericks Upgrade Notes) - mactips - Helpful configuration tips for OS X users"

こういうのがあると何気に便利だったりします。

ということで、おしまい。 です。

*1:勿論、10.9 mavericks での話です。

*2:どうしてそんなことやるんwww と言う話は置いといて(笑

*3:正しくは、url から 'https://github.com/woowee/' 部分を削除する、となるでしょうか。(最短後置パターンの削除 Remove Largest Prefix Pattern)

*4:こちらも同様に、'radiko.git' から '.git' の部分を削除する、となるでしょう。(最長前置パターンの削除 Remove Smallest Suffix Pattern)