web-dev-qa-db-ja.com

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

自分のスクリプト用の構成ファイルを作成する必要があります。ここでは例を示します。

脚本:

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

の内容 /home/myuser/test/config

nam="Mark"
sur="Brown"

それはうまくいきました!

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

27
Pol Hallen

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

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

19
Mikkel

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

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

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

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

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

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

xmlコマンドは XMLStarlet であり、ほとんどのUnicesで使用できます。

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

JSONを好む場合は、シェルJSONパーサーを簡単に使用できる jq があります。

私の構成ファイルは、JSONでは次のようになります。

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

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

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

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

source /home/myuser/test/config

または

. /home/myuser/test/config

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

3
NFarrington

私はスクリプトでこれを使用します:

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を一切使用しないため、これは完全に安全です。

2
2xsaiko

これは簡潔で安全です:

# 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からのみ変数を取得することを保証します

更新:セキュリティのイラストは

env -i 'touch evil1 foo=omg boo=$(touch evil2)'

タッチされたファイルは生成されません。 Macでbashを使用して、つまりbsd envを使用してテストされています。

2
Marcin

私のシナリオでは、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
Iiridayn