なぜ bash スクリプト全体を関数で記述するのでしょうか?

なぜ bash スクリプト全体を関数で記述するのでしょうか?

仕事では、bash スクリプトを頻繁に書いています。上司は、次の例のように、スクリプト全体を関数に分割することを提案しました。

#!/bin/bash

# Configure variables
declare_variables() {
    noun=geese
    count=three
}

# Announce something
i_am_foo() {
    echo "I am foo"
    sleep 0.5
    echo "hear me roar!"
}

# Tell a joke
walk_into_bar() {
    echo "So these ${count} ${noun} walk into a bar..."
}

# Emulate a pendulum clock for a bit
do_baz() {
    for i in {1..6}; do
        expr $i % 2 >/dev/null && echo "tick" || echo "tock"
        sleep 1
    done
}

# Establish run order
main() {
    declare_variables
    i_am_foo
    walk_into_bar
    do_baz
}

main

「読みやすさ」以外にこれを行う理由はあるでしょうか。これは、コメントをもう少し追加し、行間を少し空けることで同様に実現できると思います。

スクリプトの実行効率が向上するのでしょうか (実際は、どちらかと言えばその逆を期待しています)、または前述の可読性を超えてコードを変更するのが容易になるのでしょうか。それとも、単にスタイル上の好みの問題なのでしょうか。

スクリプトではうまく説明されていませんが、実際のスクリプト内の関数の「実行順序」は非常に線形になる傾向があり、 が行ったwalk_into_bar処理に依存し、が設定した処理に基づいて動作するので、実行順序を任意に入れ替えることは通常は行わないことに注意してください。たとえば、の後に突然 を配置することはないでしょう。そうすると、状況が悪くなります。i_am_foodo_bazwalk_into_bardeclare_variableswalk_into_bar

上記のスクリプトの書き方の例は次のようになります。

#!/bin/bash

# Configure variables
noun=geese
count=three

# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"

# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."

# Emulate a pendulum clock for a bit
for i in {1..6}; do
    expr $i % 2 >/dev/null && echo "tick" || echo "tock"
    sleep 1
done

答え1

読みやすさ一つだけ。しかし、それだけではない。モジュール化これだけではない。半モジュール化関数の場合はより正確かもしれません。

関数では、いくつかの変数をローカルに保持することができ、信頼性物事が混乱する可能性を減らします。

関数のもう一つの利点は再利用性一度関数をコーディングすると、スクリプト内で複数回適用できます。また、別のスクリプトに移植することもできます。

今のコードは線形かもしれませんが、将来的にはマルチスレッド、 またはマルチプロセスBash の世界では、関数内で処理する方法を学べば、並列処理へのステップに十分対応できるようになります。

もう 1 つ付け加えておきたいことがあります。Etsitpab Nioliv が以下のコメントで指摘しているように、関数から一貫したエンティティとしてリダイレクトするのは簡単です。しかし、関数によるリダイレクトにはもう 1 つの側面があります。つまり、リダイレクトは関数定義に沿って設定できるということです。例:

f () { echo something; } > log

これで、関数呼び出しによる明示的なリダイレクトは不要になりました。

$ f

これにより、多くの繰り返しが省かれ、信頼性が向上し、物事を整理するのに役立ちます。

参照

答え2

私はこれと同じスタイルのbashプログラミングを使い始めました。Kfir Lavi のブログ投稿「防御的な Bash プログラミング」彼はいくつかの良い理由を挙げていますが、個人的には次の理由が最も重要だと思います。

  • 手順は説明的になります。コードの特定の部分が何をすべきかがずっと簡単にわかります。コードの壁の代わりに、「ああ、関数はfind_log_errorsエラーのためにログ ファイルを読み込みます」とわかります。長いスクリプトの途中で、神のみぞ知る種類の正規表現を使用している大量の awk/grep/sed 行を見つけるのと比べてみてください。コメントがない限り、そこで何が行われているのかまったくわかりません。

  • set -x関数をとで囲むことでデバッグできますset +x。コードの残りの部分が正常に動作することがわかったら、このトリックを使用して、特定の関数のデバッグのみに集中できます。もちろん、スクリプトの一部を囲むこともできますが、長い部分の場合はどうでしょうか。次のようにする方が簡単です。

       set -x
       parse_process_list
       set +x
    
  • 使用法を印刷しますcat <<- EOF . . . EOF。私はコードをよりプロフェッショナルにするためにこれを何度も使用しました。さらに、関数parse_args()with はgetopts非常に便利です。繰り返しますが、これはすべてを巨大なテキストの壁としてスクリプトに押し込む代わりに、読みやすさに役立ちます。これらを再利用するのも便利です。

そして、明らかに、これは C や Java、Vala を知っているが、bash の経験が限られている人にとっては、はるかに読みやすいです。効率性に関して言えば、できることは多くありません。bash 自体は最も効率的な言語ではなく、速度と効率性に関しては、人々は perl や python を好みます。ただし、関数はnice次のようにできます。

nice -10 resource_hungry_function

コードの各行で nice を呼び出す場合と比べて、入力が大幅に減り、スクリプトの一部だけを低い優先度で実行したい場合に便利に使用できます。

私の意見では、関数をバックグラウンドで実行すると、大量のステートメントをバックグラウンドで実行したい場合にも役立ちます。

このスタイルを使用した例をいくつか挙げます。

答え3

私のコメントでは、関数の 3 つの利点について言及しました。

  1. テストや正確性の検証が容易になります。

  2. 関数は将来のスクリプトで簡単に再利用(ソース化)できます

  3. あなたの上司はそれを気に入っています。

そして、3番目の重要性を決して過小評価しないでください。

もう1つの問題について触れたいと思います。

... したがって、実行順序を任意に入れ替えることは、通常は行いません。たとえば、declare_variablesの後に突然 を配置することはないでしょう。walk_into_barそうすると、状況が悪くなります。

コードを関数に分割する利点を得るには、関数をできるだけ独立させるようにする必要があります。 がwalk_into_bar他の場所で使用されていない変数を必要とする場合、その変数は で定義され、 に対してローカルにする必要がありますwalk_into_bar。 コードを関数に分割し、それらの相互依存性を最小限に抑えるプロセスにより、コードはより明確でシンプルになります。

理想的には、関数は個別にテストしやすい必要があります。相互作用のためにテストが容易でない場合は、リファクタリングによってメリットが得られる可能性があることを示しています。

答え4

コードを関数に分割する理由は、C/C++、Python、Perl、Ruby などのプログラミング言語のコードの場合と同じです。より深い理由は抽象化です。つまり、低レベルのタスクを高レベルのプリミティブ (関数) にカプセル化することで、処理方法について悩む必要がなくなります。同時に、コードはより読みやすく (保守しやすく)、プログラム ロジックはより明確になります。

しかし、あなたのコードを見ると、変数を宣言する関数があるのは非常に奇妙だと思います。これには本当に眉をひそめてしまいます。

関連情報