bashスクリプトへのddスタイルのパラメーター


19

paramsをddスタイルのbashスクリプトに渡したいです。基本的に、私は欲しい

./script a=1 b=43

と同じ効果を持つ

a=1 b=43 ./script

私はこれを達成できると思った:

for arg in "$@"; do
   eval "$arg";
done

evalが安全であること、つまり"$arg"静的(コード実行なし)の変数割り当てと一致することを保証する良い方法は何ですか?

または、これを行うためのより良い方法はありますか?(これをシンプルに保ちたい)。


これにはbashのタグが付いています。Posix準拠のソリューションが必要ですか、それともbashソリューションを受け入れますか?
リチ

タグの言うことは、私が言っていることです:)
PSkocik

さて、あなたはそれを=セパレーターでパターンとして解析し、より注意深く構築されたevalで割り当てを行うことができます。安全のために、私的な使用のために、あなたがやったように私はそれにしたいと思います。
オリオン

回答:


16

evalなしで(および人為的なエスケープなしで)bashでこれを行うことができます:

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done

編集: StéphaneChazelasのコメントに基づいて、割り当てられた変数が既に配列または整数変数として宣言されることを避けるために、宣言にフラグを追加しました。これdeclareにより、key=val引数の値部分を評価する多くのケースを回避できます。(+aたとえば、設定する変数が既に配列変数として宣言されている場合、エラーが発生します。)これらの脆弱性はすべて、この構文を使用して既存の(配列または整数)変数を再割り当てすることに関連しています。シェル変数。

実際、これは、インジェクション攻撃のクラスの単なるインスタンスであり、evalベースのソリューションに等しく影響します。コマンドラインに存在する変数を盲目的に設定するよりも、既知の引数名のみを許可することは本当にはるかに良いでしょう。(PATHたとえば、コマンドラインがを設定した場合に何が起こるかを検討してください。またはPS1、次のプロンプト表示で発生する評価を含めるようにリセットします。)

bash変数を使用するよりも、名前付き引数の連想配列を使用することをお勧めします。これは、設定がより簡単で、はるかに安全です。または、実際のbash変数を設定できますが、その名前が正当な引数の連想配列にある場合のみです。

後者のアプローチの例として:

# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
  # Make sure that it is an assignment.
  # -v is not an option for many bash versions
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
        ${options[${arg%%=*}]+ok} == ok ]]; then
    declare "$arg"
    # or, to put it into the options array
    # options[${arg%%=*}]=${arg#*=}
  fi
done

1
正規表現の括弧が間違っているようです。おそらく代わりにこれを使用してください:^[[:alpha:]_][[:alnum:]_]*=
-lcd047

1
@ lcd047:foo=fooを空の文字列に設定する唯一の方法であるため、許可する必要があります(IMHO)。ブラケットを固定しました、ありがとう。
リチ

3
declareと同じくらい危険ですeval(危険であることが明らかでないため、さらに悪いことを言うかもしれません)。たとえば'DIRSTACK=($(echo rm -rf ~))'、引数としてそれを呼び出すようにしてください。
ステファンシャゼル

1
@PSkocik:+xは「not -x」です。 -a=インデックス付き配列、-A=連想配列、-i=整数変数。したがって、インデックス付き配列ではなく、連想配列でも整数でもありません。
lcd047

1
の次のバージョンではbash+c複合変数+Fを無効にするか、浮動変数を無効にするために追加する必要がある場合があることに注意してください。私はまだevalあなたが立っている場所を知っているところを使用します
ステファンシャゼル

9

POSIXのもの(/ ... などの特殊変数の問題を回避する$<prefix>var代わりに設定):$varIFSPATH

prefix=my_prefix_
for var do
  case $var in
    (*=*)
       case ${var%%=*} in
         "" | *[!abcdefghijiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*) ;;
         (*) eval "$prefix${var%%=*}="'${var#*=}'
       esac
  esac
done

と呼ばれるmyscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2、それは割り当てます:

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2

6

ハードコーディングされたDD_OPT_プレフィックスでリファクタリングされたlcd047のソリューション:

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done

frostschutzは、ほとんどのリファクタリングに値します。

私はこれをグローバル変数としてソースファイルに入れます:

DD_OPTS_PARSE=$(cat <<'EOF'
  while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
    eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
  done
EOF
)

eval "$DD_OPTS_PARSE" すべての魔法を行います。

関数のバージョンは次のとおりです。

DD_OPTS_PARSE_LOCAL="${PARSE_AND_REMOVE_DD_OPTS/DD_OPT_/local DD_OPT_}"

使用中で:

eval "$DD_OPTS_PARSE_LOCAL"

テストとREADME.mdで完全なリポジトリを作成しました。次に、これを作成中のGithub API CLIラッパーで使用し、同じラッパーを使用して、上記のレポの githubクローンをセットアップしました(ブートストラップは楽しいです)。

1行でbashスクリプトの安全なパラメーターを渡す。楽しい。:)


1
ただし*=*、=がない場合は、key / valを削除して、置換を停止できます。(リファクタリングしていたため):P
frostschutz

1
実際、あなたはforループとifを取り除き、代わりにwhile $ 1を使用することができます。これは、あなたがシフトしているからです
...-frostschutz

1
へえ、ブレインストーミングが機能するという証拠。:)
lcd047

1
朝のアイデアを収集する:とを削除しkeyval、を書くこともできますeval "${1%%=*}"=\${1#*=}。しかしeval "$1"、@ rici declare "$arg"が機能しないことは明らかなので、それはほとんどの場合に限ります。PATHまたはなどの設定にも注意してくださいPS1
lcd047

1
ありがとう-私はそれが評価された変数だと思った。私の忍耐に感謝します-それは非常に明白なことです。とにかく、いや-想像されたもの以外、それはよさそうだ。これは、を使用して任意のシェルで動作するように拡張できますが、知っていますcase。おそらく問題ではありませんが、
念のために...-mikeserv

5

Classic Bourneシェルがサポートされ、BashおよびKornシェルは-kオプションを引き続きサポートします。有効になっている場合dd、コマンドライン上の任意の ' -like'コマンドオプションは、コマンドに渡される環境変数に自動的に変換されます。

$ set -k
$ echo a=1 b=2 c=3
$ 

それらが環境変数であると確信するのは少し難しいです。これを実行すると私のために働く:

$ set -k
$ env | grep '^[a-z]='   # No environment a, b, c
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: 
a=1
b=2
c=3
$ set +k
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: b=2 c=3
$

最初のenv | grep例では、単一の小文字で環境変数が示されていません。最初の例bashは、を介して実行されるスクリプトに引数が渡されておらず-c、環境には3つの1文字の変数が含まれていることを示しています。はをset +kキャンセル-kし、同じコマンドに引数が渡されたことを示します。(a=1として扱われた$0スクリプトのました。適切なエコーを使用して、それを証明することもできます。)

これは、質問が求めるものを実現します—タイピング./script.sh a=1 b=2は、タイピングと同じでなければなりませんa=1 b=2 ./script.sh

スクリプト内で次のようなトリックを試みると、問題が発生することに注意してください。

if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

"$@"逐語的に処理されます。(との両方bashksh)割り当てスタイルの変数を見つけるために再分析されません。私は試した:

#!/bin/bash

echo "BEFORE"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='
if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

echo "AFTER"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='

unset already_invoked_with_minus_k

already_invoked_with_minus_k環境変数のみがexec'dスクリプトます。


とてもいい答えです!これはPATHを変更しないのは興味深いですが、HOMEは変更可能であるため、この方法で設定するには危険すぎる環境変数のブラックリスト(少なくともPATHを含む)のようなものが必要です。私はこれが非常に短く、質問に答える方法が大好きですが、それはまだ安全であり、それによってより普遍的に使用できるため、サニタイズ+評価+プレフィックスソリューションに行きます(ユーザーが環境を台無しにしたくない環境で)。ありがとう、+ 1。
PSkocik

2

私の試み:

#! /usr/bin/env bash
name='^[a-zA-Z][a-zA-Z0-9_]*$'
count=0
for arg in "$@"; do
    case "$arg" in
        *=*)
            key=${arg%%=*}
            val=${arg#*=}

            [[ "$key" =~ $name ]] && { let count++; eval "$key"=\$val; } || break

            # show time
            if [[ "$key" =~ $name ]]; then
                eval "out=\${$key}"
                printf '|%s| <-- |%s|\n' "$key" "$out"
            fi
            ;;
        *)
            break
            ;;
    esac
done
shift $count

# show time again   
printf 'arg: |%s|\n' "$@"

RHS上の(ほぼ)任意のゴミで動作します:

$ ./assign.sh Foo_Bar33='1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0' '1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33'
|Foo_Bar33| <-- |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0|
arg: |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33|

$ ./assign.sh a=1 b=2 c d=4
|a| <-- |1|
|b| <-- |2|
arg: |c|
arg: |d=4|

最初の非x = yパラメーターでループを中断しないと、shiftは間違ったものを殺します
-frostschutz

@frostschutz良い点、編集済み。
lcd047

それを一般化することで素晴らしい仕事。少し簡略化できると思います。
PSkocik

私の編集を見る機会がありましたか?
PSkocik

私の編集を見てください。それが好きな方法です(+のshift代わりにshift 1)。そうでなければありがとう!
PSkocik

0

しばらく前、私aliasはこの種の仕事に落ち着きました。私の別の答えのいくつかはここにあります:


ただし、このようなステートメントの評価と実行を分離することもできます。たとえばalias、コマンドを事前評価するために使用できます。次の例では、変数定義は、$var評価する変数にASCII英数字または_に一致しないバイトが含まれていない場合にのみ正常に宣言できるエイリアスに保存されます。

LC_OLD=$LC_ALL LC_ALL=C
for var do    val=${var#*=} var=${var%%=*}
    alias  "${var##*[!_A-Z0-9a-z]*}=_$var=\$val" &&
    eval   "${var##[0-9]*}" && unalias "$var"
done;       LC_ALL=$LC_OLD

eval ここでは、新しい aliasでは、割り当てのためにではなく、引用符で囲まれたvarnameコンテキストからをされます。そしてeval、以前のalias定義が成功した場合にのみ呼び出され、多くの異なる実装がエイリアス名のさまざまな種類の値を受け入れることを知っていますが、完全に空のものを受け入れるシェルはまだ見つかりません。

エイリアス内の定義は _$varただし、であり、これは重要な環境値が上書きされないようにするためです。_で始まる注目すべき環境値はわかりませんが、通常はセミプライベート宣言の安全な方法です。

とにかく、エイリアスの定義が成功すると、$varの値に指定されたエイリアスを宣言します。また、数字で始まらない場合にevalのみ呼び出します。aliasそれ以外の場合evalは、null引数のみを取得します。したがって、両方の条件が満たされた場合、をeval呼び出しalias、変数定義をaliasが作成され、その後、新しいエイリアスがハッシュテーブルから即座に削除されます。


またalias、このコンテキストで便利なのは、作業内容を印刷できることです。求められた場合alias、二重引用符で囲まれたシェル実行の安全性に関するステートメントを出力します。

sh -c "IFS=\'
    alias q=\"\$*\" q" -- \
    some args which alias \
    will print back at us

出力

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.