🎉

[Bash]特殊変数とパラメータ展開

2021/12/17に公開

はじめに

いつも忘れるし、ググるのも大変だしメモ。
シェル芸の初歩として、特殊変数とパラメータ展開についてまとめました。

  • 試したバージョン
    $ bash --version
    GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
    Copyright (C) 2019 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    
    This is free software; you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    

特殊変数

一覧

変数 説明 備考
$0 シェルスクリプト名
$1~9 シェルスクリプトに渡される引数 10番目以降は${10}のように書く。
$* シェルスクリプトに渡される引数全て
$@ シェルスクリプトに渡される引数全て
$# シェルスクリプトに渡される引数の数
$? 最後に実行したコマンドの終了ステータス
$$ シェルスクリプトのPID
$! 最後に実行したバックグラウンドプロセスのPID

$*$@の違い

$*は1つの要素であるとみなされるのに対し、$@は要素が分かれています。
実際に以下を実行してみるとわかりやすいです。

  • ソースコード

    sample_test_args.sh
    #!/bin/bash
    
    echo -e "\n\$* test"
    for arg in "$*";do echo $arg; done;
    echo -e "\n\$@ test"
    for arg in "$@";do echo $arg; done;
    
  • 実行結果

    $ ./sample_test_args.sh a b c
    
    $* test
    a b c
    
    $@ test
    a
    b
    c
    

プロセスについて

$$はシェルスクリプトのPIDを表示しますが、Bash4から$BASHPIDという変数が登場しました。
基本的には一緒のPIDを表示しますが、サブシェルの時に挙動が変わります。

  • $$: シェルスクリプトのPID

  • $BASHPID: シェルスクリプトのPID(サブシェルの時はサブシェル自身のPID)

  • ソースコード

    sample_test_pid.sh
    #!/bin/bash
    ( 
    echo "\$\$ is $$"
    echo "\$BASHPID is ${BASHPID}"
    sleep 3 
    ) &
    ps -ef --forest | grep -i 'bash' | grep -v grep
    
  • 実行結果

    $ ./sample_test_pid.sh
    
    $$ is 11440
    $BASHPID is 11443
    *****     7484 10856  0 09:50 pts/6    00:00:00                      |   \_ /bin/bash
    *****    11440  7484  0 10:13 pts/6    00:00:00                      |   |   \_ /bin/bash
    *****    11443 11440  0 10:13 pts/6    00:00:00                      |   |       \_ /bin/bash
    

パラメータ展開

bashにはパラメータ展開という機能があります。
個人的にはデフォルト値を入れたりとか、文字列の一部を取り出したいときによく使用します。

一覧

一部を抜粋して一覧化しています。

説明 備考
${parameter:-word} parameterが未設定であればwordを使用。parameterへの代入はなし
${parameter:=word} parameterが未設定であればwordを使用。parameterへの代入あり
${parameter:+word} parameterが設定されていればwordを使用。未設定であれば空文字。parameterへの代入あり
${parameter+word} parameterが未設定であればwordを使用。未設定であれば空文字。parameterへの代入なし
${parameter:offset} parameterからoffset(0始まり)以降の文字列を抜き出す
${parameter:offset:length} parameterからoffset(0始まり)以降の文字列をlength分抜き出す
${#parameter} parameterの文字数をカウント
${parameter/pattern/string} parameterでpatternにマッチした部分をstringに置換 (1回のみ)
${parameter//pattern/string} parameterでpatternにマッチした部分をstringに置換 (全て) 正規表現のgオプションに相当

デフォルト値

${parameter:-word}${parameter:=word}はparameterが未設定であればデフォルト値wordが入ります。
変数parameterにwordを代入するか否かで両者に違いがあります。

  • ソースコード
    sample_test_default.sh
    #!/bin/bash
    
    arg1="$1"
    arg2="$2"
    
    echo -e "\n\${parameter:-word} test"
    
    echo ${arg1:-"word"}
    echo ${arg2:-"word"}
    
    echo -e "\nIs \${arg2} set default value?"
    echo ${arg2}
    
    echo -e "\n\${parameter:=word} test"
    echo ${arg1:="word"}
    echo ${arg2:="word"}
    
    echo -e "\nIs \${arg2} set default value?"
    echo ${arg2}
    
  • 実行結果
    $ ./sample_test_default.sh foo
    
    ${parameter:-word} test
    foo
    word
    
    Is ${arg2} set default value?
    
    
    ${parameter:=word} test
    foo
    word
    
    Is ${arg2} set default value?
    word
    

文字列抜き出し

offsetとlengthの指定でparameterの任意の位置の文字を抜き出すことができます。
末尾から抜き出すこともできるので便利ですね。
これでcutを使わなくてもいけます。

  • ソースコード
    sample_test_substring.sh
    #!/bin/bash
    
    arg1="$1"
    arg2="$2"
    
    echo -e "\n\${parameter:offset(3)} test"
    
    echo ${arg1:3}
    # 文字列の長さよりも大きい値をoffsetで指定してもエラーにならない
    echo ${arg2:3}
    
    echo -e "\n\${parameter:offset(-3)} test"
    # offsetにマイナスの値を入れると、末尾から数えてoffset以降の文字を抜き出す
    # ※コロンの間に半角スペースを入れることに注意
    echo ${arg1: -3}
    
    echo -e "\n\${parameter:offset(3):length(2)} test"
    
    echo ${arg1:3:2}
    # 文字列の長さよりも大きい値をoffset, lengthで指定してもエラーにならない
    echo ${arg2:3:2}
    
    echo -e "\n\${parameter:offset(3):length(-2)} test"
    # lengthにもマイナスの値を入れられる。length分を引いた文字を抜き出す
    echo ${arg1:3:-2}
    
  • 実行結果
    
    $ ./sample_test_substring.sh foobarbaz
    
    ${parameter:offset(3)} test
    barbaz
    
    
    ${parameter:offset(-3)} test
    baz
    
    ${parameter:offset(3):length(2)} test
    ba
    
    
    ${parameter:offset(3):length(-2)} test
    barb
    

おわりに

スマートかつ簡潔に書けるのは良いと思う反面、わからないとドツボにはまりそうですね。。。
まだまだ未熟ですが、これから精進します。押忍!

参考にした記事

以下参考にさせていただきました。ありがとうございます。

Discussion