シェルスクリプトに構成ファイルを使用する


26

私は自分のスクリプトの設定ファイルを作成する必要があります:ここに例があります:

スクリプト:

#!/bin/bash
source /home/myuser/test/config
echo "Name=$nam" >&2
echo "Surname=$sur" >&2

の内容/home/myuser/test/config

nam="Mark"
sur="Brown"

うまくいきました!

私の質問:これはこれを行う正しい方法ですか、または他の方法がありますか?


変数は一番上になければなりません。私はそれがうまくいくことに驚いています。とにかく、なぜ設定ファイルが必要なのですか?これらの変数を別の場所で使用する予定ですか?
ファヒムミタ14

Faheem、スクリプトには多くのオプションがあるため、変数が必要です。設定ファイルを使用するとスクリプトが簡略化されます。ありがとう
ポルハレン14

5
私見その罰金。私はこのようにします。
ティンティ14

abcdeまた、この方法でそれを行い、それは非常に大きなプログラムです(シェルスクリプト用)こちらご覧になれます
ルーカス14

回答:


19

source任意のコードを実行するため、安全ではありません。これは懸念事項ではないかもしれませんが、ファイルのアクセス許可が正しくない場合、ファイルシステムアクセス権を持つ攻撃者が、セキュリティで保護されたスクリプトなどによって読み込まれた構成ファイルにコードを挿入することにより、特権ユーザーとしてコードを実行する可能性がありますinitスクリプト。

これまでのところ、私が特定できた最良の解決策は、不器用な車輪を再発明する解決策です。

myscript.conf

password=bar
echo rm -rf /
PROMPT_COMMAND='echo "Sending your last command $(history 1) to my email"'
hostname=localhost; echo rm -rf /

を使用するとsource、これはecho rm -rf /2回実行され、実行中のユーザーのが変更されます$PROMPT_COMMAND。代わりに、これを行います:

myscript.sh(Bash 4)

#!/bin/bash
typeset -A config # init array
config=( # set default values in config array
    [username]="root"
    [password]=""
    [hostname]="localhost"
)

while read line
do
    if echo $line | grep -F = &>/dev/null
    then
        varname=$(echo "$line" | cut -d '=' -f 1)
        config[$varname]=$(echo "$line" | cut -d '=' -f 2-)
    fi
done < myscript.conf

echo ${config[username]} # should be loaded from defaults
echo ${config[password]} # should be loaded from config file
echo ${config[hostname]} # includes the "injected" code, but it's fine here
echo ${config[PROMPT_COMMAND]} # also respects variables that you may not have
               # been looking for, but they're sandboxed inside the $config array

myscript.sh(Mac / Bash 3互換)

#!/bin/bash
config() {
    val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d '=' -f 2-)

    if [[ $val == __DEFAULT__ ]]
    then
        case $1 in
            username)
                echo -n "root"
                ;;
            password)
                echo -n ""
                ;;
            hostname)
                echo -n "localhost"
                ;;
        esac
    else
        echo -n $val
    fi
}

echo $(config username) # should be loaded from defaults
echo $(config password) # should be loaded from config file
echo $(config hostname) # includes the "injected" code, but it's fine here
echo $(config PROMPT_COMMAND) # also respects variables that you may not have
               # been looking for, but they're sandboxed inside the $config array

私のコードにセキュリティ上の悪用を見つけたら返信してください。


1
FYIこれは悲しげの対象となるバッシュバージョン4.0ソリューションで非常識なライセンスの問題、アップルによって課さやMacで、デフォルトでは使用できません
スキマスイッチ

@すきま良い点。Bash 3と互換性のあるバージョンを追加しました。その弱点は*、入力を適切に処理できないことですが、Bashではその文字を適切に処理できますか?
ミッケル

パスワードにバックスラッシュが含まれている場合、最初のスクリプトは失敗します。
クサラナナンダ

@Kusalanandaバックスラッシュがエスケープされるとどうなりますか?my\\password
ミッケル

10

MacとLinuxの両方で、Bash 3以降と互換性のあるクリーンでポータブルなバージョンを次に示します。

すべてのシェルスクリプトで巨大な、散らかった、重複した「デフォルト」設定関数の必要性を避けるために、すべてのデフォルトを個別のファイルで指定します。また、デフォルトのフォールバックを使用するか、使用しないかを選択できます。

config.cfg

myvar=Hello World

config.cfg.defaults

myvar=Default Value
othervar=Another Variable

config.shlib(これはライブラリであるため、シェバンラインはありません):

config_read_file() {
    (grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}

config_get() {
    val="$(config_read_file config.cfg "${1}")";
    if [ "${val}" = "__UNDEFINED__" ]; then
        val="$(config_read_file config.cfg.defaults "${1}")";
    fi
    printf -- "%s" "${val}";
}

test.sh(または構成値を読み取るスクリプト)

#!/usr/bin/env bash
source config.shlib; # load the config library functions
echo "$(config_get myvar)"; # will be found in user-cfg
printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing!
myvar="$(config_get myvar)"; # how to just read a value without echoing
echo "$(config_get othervar)"; # will fall back to defaults
echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn't set anywhere

テストスクリプトの説明:

  • test.shでのconfig_getの使用はすべて二重引用符で囲まれていることに注意してください。すべてのconfig_getを二重引用符で囲むことにより、変数値のテキストがフラグとして誤って解釈されないようにします。また、config値の複数のスペースなど、空白が適切に保持されるようにします。
  • そして、そのprintf行は何ですか?まあ、それはあなたが知っておくべきことです:echoあなたが制御できないテキストを印刷するための悪いコマンドです。二重引用符を使用しても、フラグは解釈されます。myvarconfig.cfg)に設定してみてください。-e空行が表示echoされます。これはフラグであると考えられるためです。しかしprintf、その問題はありません。printf --「これを印刷し、フラグとして何かを解釈しません」と言うと、"%s\n"「最後の改行を含む文字列として出力をフォーマットし、最後に最後のパラメータは、フォーマットへのprintf関数の値であると言います。
  • 値を画面にエコーしない場合は、のように通常どおりに値を割り当てるだけmyvar="$(config_get myvar)";です。それらを画面に印刷する場合は、printfを使用して、ユーザー構成にある可能性のあるエコー非互換の文字列に対して完全に安全であることをお勧めします。ただし、ユーザーが指定した変数がエコーする文字列の最初の文字でない場合、エコーは問題ありません。「フラグ」が解釈できる唯一の状況であるecho "foo: $(config_get myvar)";ため、「foo」はダッシュで始まるため、文字列の残りの部分もフラグではないことをエコーに伝えます。:-)

@ user2993656私の元のコードには正しいものではなく、まだプライベートな構成ファイル名(environment.cfg)が含まれていることに気付いてくれてありがとう。「echo -n」編集に関しては、使用するシェルに依存します。Mac / Linux Bashでは、「echo -n」は「改行のないエコー」を意味しますが、これは改行の末尾を避けるために行いました。しかし、それなしでもまったく同じように動作するようですので、編集してくれてありがとう!
gw0 19

実際、エコーの代わりにprintfを使用するように書き直しました。これにより、エコーが設定値の「フラグ」を誤って解釈するリスクを取り除くことができます。
gw0

私はこのバージョンが本当に好きです。config.cfg.defaults電話をかける際にそれらを定義する代わりに落としました$(config_get var_name "default_value")tritarget.org/static/...
スキマスイッチ

7

構成ファイルを解析し、実行しないでください。

私は現在、非常に単純なXML構成を使用する職場でのアプリケーションを作成しています。

<config>
    <username>username-or-email</username>
    <password>the-password</password>
</config>

シェルスクリプト(「アプリケーション」)で、これはユーザー名を取得するために行います(多かれ少なかれ、シェル関数に入れました)。

username="$( xml sel -t -v '/config/username' "$config_file" )"

xmlコマンドがあるXMLStarletほとんどのUnixのために利用可能です。

アプリケーションの他の部分もXMLファイルでエンコードされたデータを処理するため、私はXMLを使用しています。

JSONを好む場合jq、使いやすいシェルJSONパーサーがあります。

私の設定ファイルはJSONで次のようになります。

{                                 
  "username": "username-or-email",
  "password": "the-password"      
}                

そして、スクリプトでユーザー名を取得します。

username="$( jq -r '.username' "$config_file" )"

スクリプトの実行には、多くの長所と短所があります。主な欠点はセキュリティです。誰かが設定ファイルを変更できると、コードを実行できるようになり、バカな証拠にするのが難しくなります。利点は速度です。簡単なテストでは、pqを実行するよりも構成ファイルをソースする方が10,000倍以上速く、柔軟性があります。モンキーパッチングPythonが好きな人なら誰でもこれに感謝します。
イカロス

@icarus通常、どのくらいの大きさの構成ファイルに遭遇しますか。また、1回のセッションでそれらを解析する頻度はどれくらいですか?また、XMLまたはJSONから一度に複数の値が取得される可能性があることにも注意してください。
クサラナナンダ

通常、少数(1〜3)の値のみ。eval複数の値を設定するために使用している場合、構成ファイルの選択した部分を実行しています:-)。
イカロス

1
@icarus私は配列を考えていました... eval何もする必要はありません。既存のパーサーで標準形式を使用することによるパフォーマンスヒットは(外部ユーティリティであっても)、堅牢性、コード量、使いやすさ、および保守性と比較して無視できます。
クサラナナンダ

1
「構成ファイルを解析し、実行しない」ための+1
Iiridayn

4

最も一般的で、効率的で正しい方法は、を使用するsource.、省略形として使用することです。例えば:

source /home/myuser/test/config

または

. /home/myuser/test/config

ただし、考慮すべき点は、追加のコードを挿入できる場合、追加の外部ソース構成ファイルを使用すると発生する可能性があるセキュリティの問題です。この問題を検出して解決する方法など、詳細については、http: //wiki.bash-hackers.org/howto/conffile#secure_itの「Secure it」セクションをご覧になることをお勧めします。


5
私はその記事に大きな期待を持っていました(検索結果にも出てきました)が、悪意のあるコードを除外するために正規表現を使用しようとする著者の提案は無益です。
ミッケル

ドットを使用する手順では、絶対パスが必要ですか?それが動作しない相対的なものに
ダヴィデ

2

スクリプトでこれを使用します。

sed_escape() {
  sed -e 's/[]\/$*.^[]/\\&/g'
}

cfg_write() { # path, key, value
  cfg_delete "$1" "$2"
  echo "$2=$3" >> "$1"
}

cfg_read() { # path, key -> value
  test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1
}

cfg_delete() { # path, key
  test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1"
}

cfg_haskey() { # path, key
  test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null
}

すべての文字の組み合わせをサポートする必要がありますが、キーは=それらに含めることができませんが、それはセパレータですから。それ以外は機能します。

% cfg_write test.conf mykey myvalue
% cfg_read test.conf mykey
myvalue
% cfg_delete test.conf mykey
% cfg_haskey test.conf mykey || echo "It's not here anymore"
It's not here anymore

また、これはsource/を一切使用しないため、完全に安全です。eval


0

私のシナリオでは、sourceまたは.大丈夫でしたが、FOO=bar myscript.sh構成された変数よりも優先されるローカル環境変数(つまり)をサポートしたかったのです。また、構成ファイルをユーザーが編集可能で、構成ファイルのソースに慣れている人にとって使いやすいものにし、非常に小さなスクリプトの主な推力から邪魔されないように、できるだけ小さく/シンプルに保つことを望んでいました。

これは私が思いついたものです:

CONF=${XDG_CONFIG_HOME:-~/config}/myscript.sh
if [ ! -f $CONF ]; then
    cat > $CONF << CONF
VAR1="default value"
CONF
fi
. <(sed 's/^\([^=]\+\) *= *\(.*\)$/\1=${\1:-\2}/' < $CONF)

本質的に-変数の定義をチェックし(空白について非常に柔軟ではない)、それらの行を書き換えて、値がその変数のデフォルトに変換され、見つかった場合、変数はXDG_CONFIG_HOME上記の変数のように変更されません。構成ファイルのこの変更されたバージョンを入手し、続行します。

将来の作業により、sedスクリプトがより堅牢になり、奇妙に見える行や定義ではない行などが行末コメントで途切れることがなくなりますが、今のところこれで十分です。


0

これは簡潔で安全です:

# Read common vars from common.vars
# the incantation here ensures (by env) that only key=value pairs are present
# then declare-ing the result puts those vars in our environment 
declare $(env -i `cat common.vars`)

-i性を保証は、あなただけの変数を取得しますcommon.vars


-2

あなたはそれをすることができます:

#!/bin/bash
name="mohsen"
age=35
cat > /home/myuser/test/config << EOF
Name=$name
Age=$age
EOF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.