pikesaku’s blog

個人的な勉強メモです。記載内容について一切の責任は持ちません。

シェルの展開(EXPANSION)を学ぶ

man bashのEXPANTIONを勉強

EXPANSIONはコマンド実行前に処理されるコマンドラインの展開処理のこと。
7種類の動きがあり。

EXPANSIONの動きの確認の前に、shellで使われる括弧の英語/日本語表記について把握する。

括弧 - Wikipedia

表記 英語 日本語
( parenthesis, paren 丸括弧(小括弧)
{ brace 波括弧(中括弧)
[ bracket 角括弧(大括弧)

それでは7種類の動きを確認!!


①brace expansion

brace expansionとは中括弧{}による展開のこと。

man bashの"Brace Expansion"部分のGoogle翻訳によると、、、

ブレース拡張は、任意の文字列を生成するメカニズムです。このメカニズムはパス名拡張に似ていますが、生成されたファイル名は存在する必要はありません。中括拡張されるパターンはオプションのプリアンブルの形をとり、コンマで区切られた一連の文字列、または中括弧の間のシーケンス式の後にオプションの追記文字が続きます。プリアンブルは、中括弧内に含まれる各文字列の前に置かれ、ポストスクリプトは結果の各文字列に追加され、左から右に展開されます。

最近のGoogle翻訳すごい。。。

ひとまず以下だけ覚える。

シーケンス展開

# echo {a..c}
a b c
# echo {0..3}
0 1 2 3
# echo {0..3}a
0a 1a 2a 3a
# echo a{0..3}
a0 a1 a2 a3

カンマ区切り展開

# ls
a
# echo {a,b,1,2,*}                                                                                                                       
a b 1 2 a
# echo {a,b,1,2,*,?}
a b 1 2 a a
# echo {a,b,1,2,*,?,??}
a b 1 2 a a ??

注目すべきは*や?によるパス展開もされる点。
また*や?パス展開されない場合、そのまま文字として利用されてしまう点も注意。mkdirとかで使うと*や?のついたディレクトリが出来てしまう。


②tilde expansion

man bashの"Tilde Expansion"部分のGoogle翻訳によると、、、

単語が引用符で囲まれていないチルダ文字( `〜 ')で始まる場合、最初の引用符なしスラッシュ(または引用符なしのスラッシュがない場合はすべての文字)の前のすべての文字はチルダプレフィックスと見なされます。チルダプレフィックス内の文字のいずれも引用符で囲まれていない場合、チルダに続くチルダプレフィックス内の文字は、可能なログイン名として扱われます。このログイン名がヌル文字列の場合、チルダはシェルパラメータHOMEの値に置き換えられます。 HOMEが設定されていない場合は、シェルを実行しているユーザーのホームディレクトリが代わりに使用されます。それ以外の場合は、tilde-prefixが、指定されたログイン名に関連付けられたホームディレクトリに置き換えられます。

ひとまず以下だけ覚える。

ホームディレクトリに展開

# echo ~
/root
# echo ~/test
/root/test
# echo ~"/test"
~/test
# echo ~/"test"
/root/test
# echo a~
a~
# echo "a"~
a~

③parameter expansion

変数展開のこと。

man bashの"Parameter Expansion"部分のGoogle翻訳によると、、、

`$ '文字は、パラメータ展開、コマンド置換、または算術展開を導入します。展開されるパラメータ名またはシンボルは、省略可能な中括弧で囲むことができますが、名前の一部として解釈される直後の文字から拡張する変数を保護する役割を果たします。

色々記法・機能があるので、詳細は以下URLを参照。
シェルの変数展開 - Qiita
Bash Reference Manual: Shell Parameter Expansion

ひとまず以下だけ覚える。

{}(中括弧)を使った方がよい!(変数を明確に指定できるため)

# a="bbb"
# echo $a
bbb
# echo ${a}
bbb
# echo $ab

# echo ${a}b
bbbb

変数の文字数、配列の要素数の取り出し

${#parameter}
#を使う。

# a=1234
# echo ${#a}
4
# a=abcd
# echo ${#a}
4
# a=(a b c d)
# echo ${a}
a
# echo ${#a}
1
# echo ${a[@]}
a b c d
# echo ${#a[@]}
4
#

値の切り出し

パラメータ展開 内容
${parameter#word} 先頭から前方最短一致した位置まで取り除きます
${parameter##word} 先頭から前方最長一致した位置まで取り除きます
${parameter%word} 末尾から後方最短一致した位置まで取り除きます
${parameter%%word} 末尾から後方最長一致した位置まで取り除きます

前方一致の場合

# a=/var/log/messages
# echo ${a}
/var/log/messages
# echo ${a#/}
var/log/messages
# echo ${a##/}
var/log/messages
# echo ${a#*/}
var/log/messages
# echo ${a##*/}
messages
# echo ${a#log}
/var/log/messages
# echo ${a#*log}
/messages
# echo ${a##*log}
/messages

後方一致の場合

# a=/var/log/messages
# echo ${a%/}
/var/log/messages
# echo ${a%%/}
/var/log/messages
# echo ${a%/*}
/var/log
# echo ${a%%/*}

# echo ${a%log}
/var/log/messages
# echo ${a%%log}
/var/log/messages
# echo ${a%log*}
/var/
# echo ${a%%log*}
/var/

④command substitution

man bashの"Command Substitution"部分のGoogle翻訳によると、、、

コマンド置換は、コマンド名の代わりにコマンド出力を許可します。 2つの形式があります。
$(コマンド)
または
`コマンド`
Bashはコマンドを実行し、コマンド置換をコマンドの標準出力に置き換え、末尾に改行を削除して拡張を実行します。埋め込み改行は削除されませんが、単語分割中に削除される可能性があります。コマンド置換$(catファイル)は、同等ではあるが高速な$(<ファイル)で置き換えることができます。

ひとまず以下だけ覚える。

$(COMMAND)の形式の方が新しい使い方でよい!範囲指定が分かりやすく、ネストする時とか記述がシンプルになる。

# A=/etc/hosts
# echo $(cat $A)                                                                                                                             
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo `cat $A`
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo $(echo $(cat $A))
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo `echo `cat $A``
cat /etc/hosts
# echo `echo \`cat $A\``
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

⑤arithmetic expansion

man bashの"Command Substitution"部分のGoogle翻訳によると、、、

算術展開では、算術式の評価と結果の置換が可能です。
算術展開の形式は次のとおりです。
$((式))
式は二重引用符で囲まれているかのように扱われますが、かっこ内の二重引用符は特別に扱われません。式のすべてのトークンは、パラメータ拡張、文字列展開、コマンド置換、および見積りの削除を受けます。算術展開はネストすることができます。

"見積りの削除"は"quote removal"のこと。
Quoteはバックスラッシュとシングル・ダブルクオーテーションのこと。
"quote removal"は
After the preceding expansions, all unquoted occurrences of the characters \, ', and " that did not result from one of the above expansions are removed.
と書いてあるが、良く分からない。。。展開処理の最後に行われう処理のよう。ひとまず目をつむる。

ひとまず以下だけ覚える。

2重括弧の中に数式を書けば展開される!

# echo $((1+1))
2
# echo $((1*1))
1
# echo $((1/1))
1
# echo $((1%1))
0
# echo $((1**1))
1

数式以外を指定した時は、こんな動き

# a=/var/log/messages
# echo $((a))
-bash: /var/log/messages: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages")
# echo $(($a))
-bash: /var/log/messages: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages")
# echo $(($a+1))
-bash: /var/log/messages+1: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages+1")
# echo $(($a+a))
-bash: /var/log/messages+a: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages+a")
# unset a
# echo $((a))
0
# echo $(($a))
0

変数展開やコマンド置換等も動く

# echo 1 > ./b
# echo $(($(cat ./b)+1))
2

⑥word splitting

man bashの"Word Splitting"部分のGoogle翻訳によると、、、

シェルは、単語の二重引用符で囲まれていないパラメータ展開、コマンド置換、および算術展開の結果をスキャンします。
シェルは、IFSの各文字を区切り文字として扱い、他の展開の結果をこれらの文字の単語に分割します。 IFSが設定されていない場合、またはその値が正確に の場合、既定値、前回の展開結果の先頭と最後に、および無視され、先頭または末尾にないIFS文字のシーケンスは単語を区切る役割を果たします。 IFSがデフォルト以外の値を持つ場合、空白文字がIFS(IFS空白文字)の値である限り、空白文字のスペースとタブのシーケンスは単語の先頭と末尾で無視されます。隣接するIFS空白文字と一緒にIFS空白ではないIFS内の文字は、フィールドを区切ります。一連のIFS空白文字も区切り文字として扱われます。 IFSの値がNULLの場合、ワード分割は行われません。
明示的なヌル引数( ""または " ')は保持されます。値を持たないパラメータの拡張の結果として、引用符で囲まれていない暗黙のヌル引数は削除されます。値のないパラメータが二重引用符で囲まれている場合、null引数が返され、保持されます。
展開が行われない場合、分割は実行されないことに注意してください。

良く分からないが、
二重引用符で囲まれていないパラメータ展開、コマンド置換、および算術展開の結果をスキャンして、結果を単語に分割する処理のことだろう。
重要そうだけど、ひとまず目をつむる。


⑦pathname expansion

man bashの"Word Splitting"部分のGoogle翻訳によると、、、

単語の分割後、-fオプションが設定されていない限り、bashは各単語に*、?、および?の文字をスキャンします。これらの文字のいずれかが表示された場合、その単語はパターンとみなされ、パターンに一致するファイル名のアルファベット順ソートリストに置き換えられます。一致するファイル名が見つからず、シェルオプションnullglobが有効でない場合、その単語は変更されません。 nullglobオプションが設定されていて一致するものが見つからない場合、その単語は削除されます。 failglobシェルオプションが設定されていて一致するものが見つからない場合、エラーメッセージが出力され、コマンドは実行されません。シェルオプションnocaseglobを有効にすると、アルファベット文字の大文字と小文字を区別せずに一致が実行されます。パス名展開にパターンを使用する場合、シェルオプションのdotglobが設定されていない限り、名前の先頭またはスラッシュの直後の文字「。」は明示的に一致させる必要があります。パス名をマッチさせる場合、スラッシュ文字は常に明示的に一致させる必要があります。他の場合には、 ``。 ''文字は特別に扱われません。 nocaseglob、nullglob、failglob、およびdotglobシェルオプションについては、SHELL BUILTIN COMMANDSのshoptの説明を参照してください。

またまた難しい。ひとまず以下を覚える

Word splitingの後に動いて*、?、[]の展開をする。デフォルトでは、マッチするパス(ファイルorディレクトリetc)がない場合、パターンマッチ文字は、そのままで認識される。

と覚える

# ls
a  b
# ls * ?
a  a  b  b
# ls ??
ls: ?? にアクセスできません: そのようなファイルやディレクトリはありません
# ls [ab]
a  b
# ls [cd]
ls: [cd] にアクセスできません: そのようなファイルやディレクトリはありません
# 

おまけ

システムによっては、process substitutionもあり。

man bashの"Process Substitution"部分のGoogle翻訳によると、、、

プロセス置換は、名前付きパイプ(FIFO)をサポートするシステム、または開いているファイルに名前を付ける/ dev / fdメソッドでサポートされています。 <(list)または>(list)の形式をとります。プロセスリストは、その入力または出力がFIFOまたは/ dev / fdのあるファイルに接続されて実行されます。このファイルの名前は、展開の結果として現在のコマンドに引数として渡されます。 >(リスト)フォームが使用されている場合、ファイルへの書き込みはリストの入力を提供します。 <(リスト)形式を使用する場合、引数として渡されたファイルは、listの出力を得るために読み込まれなければなりません。
使用可能な場合、プロセス置換は、パラメーターおよび変数の拡張、コマンド置換、および算術拡張と同時に実行されます。