シェルスクリプトを再帰的に取得するには?

シェルスクリプトを再帰的に取得するには?

この質問は、重要であればBashの文脈で最も適していますが、言語間の解決策を期待しています。

私が経験している問題は、あるスクリプトを別のスクリプトに「インポート」した場合に発生しますが、これを再帰的に実行することです。問題はこのことを無限にするようだということだ。

具体的なケースは次のとおりです。同じディレクトリに別のbashスクリプトをロードするための単純なbashスクリプトを作成しました。私のプロジェクトでは、すべてのスクリプトが同じディレクトリにあるので、今動作します。

したがって、「スクリプトローダー」は次のようになります(_source_script.sh 次の機能を定義するファイル)。

_source_script()
{
    # Define a few colors for the possible error messages below.
    RED=$(tput setaf 1)
    NORMAL=$(tput sgr0)

    EXPECTED_DIR="scripts"

    if [ "$#" -ge  "1" ]
    then
        EXPECTED_DIR=$1
    fi

    # Based on: http://stackoverflow.com/a/1371283/3924118
    CURRENT_DIR=${PWD##*/}

    if [ "$CURRENT_DIR" = "$EXPECTED_DIR" ]
    then
        for (( arg = 2; arg <= $#; arg++ ))
        do
            # Based on:
            # - http://askubuntu.com/questions/306851/how-to-import-a-variable-from-a-script
            # - http://unix.stackexchange.com/questions/114300/whats-the-meaning-of-a-dot-before-a-command-in-shell
            # - http://stackoverflow.com/questions/20094271/bash-using-dot-or-source-calling-another-script-what-is-difference

            printf ". ./${!arg}.sh\n"
            # Try to load script ${!arg}.sh
            . ./${!arg}.sh

            # If it was not loaded successfully, exit with status 1.
            if [ $? -ne 0 ]
            then
                printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
                exit 1
            fi
        done
    else
        printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
        exit 1
    fi
}

この「スクリプトローダー」を使用するには、まず次のように他のスクリプトから「インポート」する必要があります。

. ./_source_script.sh

問題は、関数を介して別のスクリプトを含めようとしたときです_source_script。たとえば、スクリプトで以下を実行しようとすると、次のようになりますsome_script.sh

_source_script scripts colors asserts clean_environment

永久に実行され続けます。

内部にあるcolors.sh:

#!/usr/bin/env bash

# Colors used when printing.
export GREEN=$(tput setaf 2)
export RED=$(tput setaf 1)
export NORMAL=$(tput sgr0)
export YELLOW=$(tput setaf 3)

内部にあるasserts.sh:

...

. ./_source_script.sh
_source_script scripts colors

その中にclean_environment.sh私はまた次のものがあります:

. ./_source_script.sh
_source_script scripts colors

私の理解によると、これは何もロードしないか、ループを探すスクリプトが見つかるまで再帰的に実行する必要がありますが、そうではありません。

だから私の解決策は次のとおりですsome_script.sh

_source_script scripts colors 
_source_script scripts asserts
_source_script scripts clean_environment

つまり、個別に実行します。

それでは、ループで複数のスクリプトを「インポート」できないのはなぜですか?

答え1

コードをあまり詳しく見ることなく、一般的なアプローチにはCの「ヘッダガード」に似たものが含まれます。

#ifndef HEADER_H
#define HEADER_H

/* The contents of the header file, with typedefs etc. */

#endif

このヘッダに関連するCプリプロセッサマクロはどこにHEADER_Hありますか?ヘッダーがすでに含まれている場合は、ヘッダーを再度含めない場合にのみ使用されます。私は通常MCMC_H、ヘッダーファイル自体から名前が派生したマクロを作成しますmcmc.h

シェルスクリプトでは、次のように簡単にできます。

if [ -z "$script_source_guard" ]; then
script_source_guard=1

# ... do things (define functions etc.)

fi

変数名はscript_source_guardこのファイルにのみ適用する必要があります。同様に、名前はソースファイル名から任意に派生できます。名前付きファイルには、myfuncts.shlib名前付き保護変数があるか、別の名前を持つことができます。MYFUNCTS_MYFUNCTSguard_myfuncts

答え2

これは範囲の問題です。意図的に、関数は実行され、呼び出されると_source_script実際に自分自身を再帰的に呼び出します。ただし、シェルスクリプトの変数はデフォルトでグローバルです。したがって、(スタック内で)同時にアクティブにされた複数の関数実装は同じです。. ./${!arg}.sh_source_script_source_scriptarg

詳細:

いつasserts.sh、またはclean_environment.sh呼び出されますか?

_source_script scripts colors

関数は= 2_source_scriptになります$#。したがって、for (( arg = 2; arg <= $#; arg++ ))ループは$arg<= 2の間実行され、最終的に3になります。. asserts.sh呼び出されたら にある$argので既に 3 です。asserts$3

_source_script scripts colors asserts clean_environment

コマンドはsome_script.sh$arg置く 到着 3では、何もしないで元の状態にasserts.sh戻ります。$arg

しかし、. clean_environment.sh呼び出されたらにあるので$arg4です。clean_environment$4

_source_script scripts colors asserts clean_environment

注文する。したがって、in(呼び出し時に)が$arg3に設定されている場合、(in)の最上位レベルの実装はループ内の位置を失い、戻って再実行されます。何度も何度も…clean_environment.sh_source_script_source_scriptsome_script.shfor (( arg = 2; arg <= $#; arg++ )). clean_environment.sh

解決策

もちろん、解決策は単に  local arg関数に宣言を入れることです_source_script


スクリプトに関する追加の注:

  • 次のような状況があります。

    • さまざまなもの(定数、関数、エイリアスなど)を定義するファイルがあります。
    • 他のファイルがあります使用上で定義したものは. (ソース)定義を含むファイル
    • たとえば、一部のファイルは両方のグループに属し、呼び出し元にいくつかのリソースを提供できますが、そのファイルに定義されている値を取得するために読み取ることもできますasserts.sh 。おそらく階層があるでしょう。clean_environment.shcolors.sh

      • some_script.sh 着信電話 colors.sh
      • some_script.sh 着信電話 asserts.sh
        • asserts.sh 着信電話 colors.sh
      • some_script.sh 着信電話 clean_environment.sh
        • clean_environment.sh 着信電話 colors.sh

      など。

  • 一部のユーザーは、シェルコマンドファイルが何度も処理されるのを防ぐために「ヘッダガード」を使用することをお勧めします。このアドバイスはあなたにとって有用で関連性がありますが、状態 (上記のように)それはあなたと何が関係しているのか本当にわかりません。質問。

    - あなたがそれを使用する必要があることを除いて_source_script.shそれ自体

    上記の階層では、次の事実を無視しました。ファイルごと着信電話_source_script.sh。これは、次の_source_script機能を意味します。はい実行時にオーバーライド  これは非常に危険に聞こえるかもしれません。

  • あなたは言う

    if [ "$#" -ge "1" ]
    then
        EXPECTED_DIR=$1
    fi
    

    直接話すこともできます

    if [ "$#" -le  1  ]
    then
        exit 1                                  # or maybe      return 1
    fi
    EXPECTED_DIR=$1
    

    引数が 2 個未満であれば何もしないからです。

  • printf必須ではない場合は、型文字列(最初の引数)に変数を使用しない方が安全です。だから

    printf ". ./${!arg}.sh\n"
    printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
    printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
    

    しなければならない

    printf ". ./%s.sh\n" "${!arg}"
    printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \
                                                        "$RED" "${!arg}" "$NORMAL"
    printf "No script loaded: %s != %s.\n" "$CURRENT_DIR" "$EXPECTED_DIR"
    
  • 引用符をより頻繁に使用する必要があります。

    . ./${!arg}.sh
    

    しなければならない

    . "./${!arg}.sh"
    

    そして厳密に言えば、本当に編集証的な安全を $?維持するために"$?"

  • "$?"しかし、明示的にテストする代わりに

    if ! . "./${!arg}.sh"
    then
        printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \
                                                        "$RED" "${!arg}" "$NORMAL"
        exit 1
    fi
    

    参考にしてください.$?成功したコマンドは、ファイルの最後のコマンドの終了状態に設定されます。したがって、ヘッダーファイル(  .sh使用する他のファイルを定義するファイル)が正しい終了ステータスを返すことに注意してください。

答え3

親ファイルを介して購入をリンクしたい場合でも問題ありません。私は関数にソースステートメントを入れません。親ファイルのみをインポートする場合は、すべての子ファイルもインポートする必要があります。

内部コマンドの bash のマニュアルページからsource:

[It] ファイル名からコマンドを読み出して実行します。現在のシェル環境そして、filenameで実行された最後のコマンドの終了ステータスを返します。

現在、シェル環境が引き続き機能を実行していることを保証できない場合は、構成をより慎重に検討できます。

関連情報