1. 概要

Bashスクリプトで複雑なロジックを作成する場合は、再利用可能な関数にグループ化するのが理にかなっています。

このクイックチュートリアルでは、Bash関数を定義して使用する方法を見ていきます。

2. 基本

Bash関数は次の2つの方法で定義できます。

name () compound-command [redirections] 

function name [()] compound-command [redirections]

関数キーワードは、括弧が存在する場合にのみ省略できます

または、関数keyword を使用する場合は、括弧を省略できます。

bodyは任意の複合コマンドにすることができますが、リダイレクトもオプションであり、関数の実行時に実行されます。

2.1. 関数の定義

関数を2つの方法で定義できることは前に述べました。 簡単な例を見てみましょう。

simple_function() {
    for ((i=0;i<5;++i)) do
        echo -n " "$i" ";
    done
}

simple_function

名前を呼び出すだけで関数を呼び出しますが、実行する前に関数を定義する必要があります。

この簡単な例は、いくつかの数値を出力するだけです。

0  1  2  3  4

最初に述べたように、 function キーワードを使用して、括弧を省略できます。

function simple_function {
    # same body as before
}

もちろん、結果は同じです。

関数にも同じ名前を使用していることに注意してください。 この場合、Bashはスクリプトで見つかった最後の関数定義を使用します。

本体は任意の複合コマンドである可能性があると述べたので、中括弧を省略できます。

function simple_for_loop()
    for ((i=0;i<5;++i)) do
        echo -n " "$i" ";
    done

この例では、出力は以前と同じです。

ただし、これはforループ内で命令を実行している場合にのみ機能します。 これは、ループ構造が複合コマンドとして機能するためです。

さらに、条件付き構文とコマンドグループを使用して関数の本体を定義できます。

2.2. 入力引数の受け渡し

関数への入力の受け渡しは、Bashスクリプトへの引数の受け渡しと同じです。

function simple_inputs() {
    echo "This is the first argument [$1]"
    echo "This is the second argument [$2]"
    echo "Calling function with $# arguments"
}

simple_inputs one 'two three'

この例を詳しく見てみましょう。 まず、位置パラメータから2つの入力を出力します。

その後、特別なパラメータを使用して引数の総数も出力します。

また、単語の分割を避けるために、2番目の入力を引用符でエスケープします。

出力を見てみましょう:

This is the first argument [one]
This is the second argument [two three]
Calling function with 2 arguments

2.3. 出力の取得

関数を実行すると、Bashはそれをコマンドに似ていると見なします。

これは、 returnステートメントが0〜255の値の数値終了ステータスのみを通知できることを意味します。

終了コードを返さない場合、Bashは関数の最後のコマンドの終了ステータスを返します。

2つの数値の合計を計算してみましょう。

sum=0
function simple_outputs() {
    sum=$(($1+$2)) 
}

simple_outputs 1 2
echo "Sum is $sum"

このスニペットでは、グローバル変数を使用して実際の結果を保存します。

このアプローチの代わりに、コマンド置換を利用できます。

function simple_outputs() {
    sum=$(($1+$2)) 
    echo $sum
}

sum=$(simple_outputs 1 2)
echo "Sum is $sum"

サブシェルで関数を実行していることに注意してください。 これについては後で詳しく説明します。

関数はコマンドに似ているため、標準出力をキャプチャして、実際の結果を得ることができます。

Sum is 3

2.4. 引数参照の使用

Bash 4.3以降では、入力引数を参照で渡し、関数内でその状態を変更できます。

function ref_outputs() {
    declare -n sum_ref=$3
    sum_ref=$(($1+$2)) 
}

この例を掘り下げて理解を深めましょう。

まず、3番目の引数の名前格納するnameref変数を宣言します。

2番目のステップとして、この変数を代入演算の左側のオペランドとして使用します。 この変数は3番目の引数への参照であるため、これを行うことができます。

最後に、入力と出力の両方を位置引数として指定することにより、関数を呼び出します。

ref_outputs 1 2 sum
echo "Sum is $sum"

そして、同じ結果が表示されます。

Sum is 3

3. 高度な概念

基本を理解したので、関数を使用したより高度な概念と使用シナリオを見てみましょう。

3.1. 変数とスコープ

前の例でグローバル変数を見ました。 ローカル変数を定義することもできます。

variable="baeldung"
function variable_scope() {
    local variable="lorem"
    echo "Variable inside function variable_scope : [$variable]"
}

variable_scope
echo "Variable outside function variable_scope : [$variable]"

ローカル変数は、呼び出し元のスコープから同じ名前の変数をシャドウします。

Variable inside function variable_scope : [lorem]
Variable outside function variable_scope : [baeldung]

さらに複雑にして、前の関数から別の関数を呼び出しましょう。

variable="baeldung"
function variable_scope2() {
    echo "Variable inside function variable_scope2 : [$variable]"
}
function variable_scope() {
    local variable="lorem"
    echo "Variable inside function variable_scope : [$variable]"
    variable_scope2
}

variable_scope

出力は少し驚くべきものです:

Variable inside function variable_scope : [lorem]
Variable inside function variable_scope2 : [lorem]

variable_scope 関数でローカル変数を宣言しましたが、それは2番目の関数でも表示されます。

これは動的スコープと呼ばれ、ネストされた子スコープで変数を表示する方法に影響します。

3.2. サブシェル

上記のサブシェルについて説明したことを忘れないでください。 サブシェルは特別なタイプのコマンドグループであり、現在のシェルから新しい実行環境を生成できます。

関数の本体はコマンドグループで区切ることができるため、サブシェルでロジックを直接実行できます。

sum=0
function simple_subshell() (
    sum_ref=$(($1+$2))
)

simple_subshell
echo "Sum is $sum"

中括弧の代わりに、 現在、関数本体を区切るために括弧を使用しています。

この例を実行すると、グローバル変数が変更されていないことがわかります。

Sum is 0

引数参照を試してみましょう。

sum=0
function simple_subshell() (
    declare -n sum_ref=$3
    sum_ref=$(($1+$2))
)

simple_subshell 1 2 sum
echo "Sum is $sum"

同じ結果が得られます。 これは、生成された実行環境が終了すると変数の割り当てが却下されるためです

ただし、前のスニペットで見たように、コマンド置換を使用してサブシェルの標準出力ストリームを取得できます。

一般に、サブシェルの目的は、タスクの並列処理を可能にすることです。

3.3. リダイレクション

最初に、関数定義構文でリダイレクトも許可されることを確認しました。

ファイルを1行ずつ読み取り、その内容を出力する簡単な例を考えてみましょう。

function redirection_in() {
    while read input;
        do
            echo "$input"
        done
} < infile

redirection_in

このスニペットでは、テストファイルの内容を関数の標準入力に直接リダイレクトします。

read コマンドは、標準入力から各行をフェッチします。

関数を実行すると、出力にはモデルと製造年とともに自動車メーカーのリストが含まれます。

Honda  Insight  2010
Honda  Element  2006
Chevrolet  Avalanche  2002

関数の標準出力をファイルにリダイレクトすることもできます。

function redirection_out() {
    declared -a output=("baeldung" "lorem" "ipsum")
    for element in "${output[@]}"
        do
            echo "$element"
        done
} > outfile

redirection_out

この場合、出力ファイル outfile には、3つの要素が別々の行に含まれています。

baeldung
lorem
ipsum

しかし、他のコマンドとの間のリダイレクトはどうですか? この目的のために、プロセス置換を使用できます。

function redirection_in_ps() {
    read
    while read -a input;
        do
            echo "${input[2]} ${input[8]}"
        done
} < <(ls -ll /)

redirection_in_ps

この例では、フォルダーとその所有者をルート( / )から読み取ります。 何が起こるかを詳しく見てみましょう。

root bin
root boot
root dev
root etc
# some more folders

ls コマンドの出力は、プロセス置換を介してファイルとして解釈されます。

次に、この出力は関数の標準入力にリダイレクトされ、関数はさらに処理します。

関数の標準出力をコマンドにリダイレクトする場合は、7行目でプロセス置換演算子を逆にします。

function redirection_out_ps() {
    declare -a output=("baeldung" "lorem" "ipsum" "caracg")
    for element in "${output[@]}"
        do
            echo "$element"
        done
} > >(grep "g")

redirection_out_ps

このようにして、 grep の標準入力をファイルと見なし、関数出力をそのファイルにリダイレクトできます。

このスニペットは、文字gを含む行のみを出力します。

baeldung
caracg

3.4. 再帰

Bash関数で再帰を使用することもできます。 n番目のフィボナッチ数の計算を見てみましょう。

function fibonnaci_recursion() {
    argument=$1
    if [[ "$argument" -eq 0 ]] || [[ "$argument" -eq 1 ]]; then
        echo $argument
    else
        first=$(fibonnaci_recursion $(($argument-1)))
        second=$(fibonnaci_recursion $(($argument-2)))
        echo $(( $first + $second ))
    fi 
}

それをよりよく理解するために詳しく見てみましょう。 関数の最初の部分は、1番目と2番目のフィボナッチ数を処理します。

他のすべての数値については、関数を再帰的に呼び出し、先行する2つの数値を計算します。

算術展開を使用して、入力引数から1と2を減算し、もう一度、コマンド置換を使用して結果を保持します。

これが7番目と15番目のフィボナッチ数を計算するためにどのように機能するかを見てみましょう。

echo $(fibonnaci_recursion 7)
echo $(fibonnaci_recursion 15)
13
610

最初の呼び出しでは物事はスムーズに実行されますが、2番目の呼び出しでは実行が非常に遅くなることがすぐにわかります。

Bash関数を使用すると再帰が可能ですが、通常は回避することをお勧めします。

FUNCNEST 組み込み変数を設定することにより、関数のネストされた呼び出しの数を制限することもできます。

FUNCNEST=5
echo $(fibonnaci_recursion 7)
echo $(fibonnaci_recursion 15)
fibonnaci_recursion: maximum function nesting level exceeded (5)
fibonnaci_recursion: maximum function nesting level exceeded (5)
# some more errors

4. 概要

この記事では、Bash関数を使用する実際的な側面について説明しました。 さまざまな構成を使用して関数の本体を定義し、出力を取得する複数の方法を使用できます。

ローカル変数を宣言することは可能ですが、動的スコープは関数が変数を表示する方法に影響を与えます。 また、関数内外でファイルやその他のコマンドをリダイレクトしたり、再帰を使用したりすることもできます。

全体として、Bash関数は優れた柔軟性を提供し、複雑なスクリプトを整理するための強力な方法を提供します。

いつものように、チュートリアルの完全なソースコードは、GitHubから入手できます。