Bashは引用および/またはエスケープされた文字列変数を単語に拡張できますか?

Bashは引用および/またはエスケープされた文字列変数を単語に拡張できますか?

bash次の文字列を含むシェル変数があります。性格スペースで区切られます。文字列には、単語内のエスケープスペースなどのエスケープ文字を含めることができます。スペースを含む単語も引用できます。

$FOO代わりに、引用符なしで使用されたシェル変数は複数の単語になりますが、"$FOO"元の文字列の引用符とエスケープは効果がありません。

引用符とエスケープ文字を考慮して文字列を単語に分割する方法は?

背景

サーバーは、クライアントに提供されているコマンドラインに関係なく、スクリプトへのアクセスを制限するためにファイルにsshオプションForceCommandを提供します。sshd_configssh

スクリプトは、続行する前に変数(クライアントに提供されるコマンドラインをSSH_ORIGINAL_COMMAND含む文字列)を使用して引数リストを設定します。だからユーザーはsshssh

$ ssh some_server foo 'bar car' baz

スクリプトが実行されていることを確認し、スクリプトが実行されるとSSH_ORIGINAL_COMMAND4つのパラメータに設定されます。foo bar car baz

set -- ${SSH_ORIGINAL_COMMAND}

望む結果ではありません。したがって、ユーザーは再試行します。

$ ssh some_server foo bar\ car baz

同じ結果 - クライアントシェルがこれを表示するには、2番目のパラメータのバックスラッシュをエスケープする必要がありますssh。これらはどうですか?

$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz

どちらも有効です。printf "%q"参照ラッパークライアント参照を簡素化できます。

クライアントの引用を使用すると、適切に引用された文字列がサーバーに送信され、sshサーバーから受信したSSH_ORIGINAL_COMMANDバックスラッシュがそのまま残りますfoo bar\ car baz

setしかし、引用やエスケープを考慮していないため、まだ問題があります。解決策があります:

eval set -- ${SSH_ORIGINAL_COMMAND}

しかし、これは許容できません。考える

$ ssh some_server \; /bin/sh -i

非常に不満:eval入力制御が不足しているため使用できません。

eval実行部分のない文字列拡張機能が必要です。

答え1

使用read:

read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}"
set -- "${ssh_args[@]}"

これは単語をSSH_ORIGINAL_COMMAND配列に解析しssh_args\バックスラッシュ()をエスケープ文字として扱います。これにより、配列要素がパラメータとして提供されますsetssh次のように渡されたパラメータのリストで機能します。

$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz

printf "%q"はSSHラッパーを表します。以下は許可されています。

$ sshwrap some_server foo bar\ car baz
$ sshwrap some_server foo 'bar car' baz

以下は、これらのラッパーの例です。

#!/bin/bash
h=$1; shift
QUOTE_ARGS=''
for ARG in "$@"
do
  ARG=$(printf "%q" "$ARG")
  QUOTE_ARGS="${QUOTE_ARGS} $ARG"
done
ssh "$h" "${QUOTE_ARGS}"

答え2

文字列を引用するには:

${parameter@operator}セクションを参照してください。https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

  • 単一変数の場合:
"${var@Q}"

a-> 'a'a'b-> 'a'\''b'

  • 配列の場合
"@{array[@]@Q}"

配列の各要素は引用符で囲まれ、スペースを使用して単一の大きな文字列に連結されます。

  • プログラムパラメータの場合$@
"${@@Q}"

(Bash 4.4+が必要な場合があります。使用できない場合はprintf "%q"を使用できますが、配列内の各要素を参照する機能は失われます)

引用符付き文字列を再配列として逆参照する方法

@eel ghEEzが指摘した安全な方法は1つだけ見つけました。

declare -a array="($QUOTED_ARGS)"

2022/08/31 編集:コマンドの挿入を防ぐには、引用符付き引数を渡すことが重要です。

より正確にはそうするべきです。

JOINED_ARGMENTS_STRING="......"
declare -a array="(${JOINED_ARGMENTS_STRING@Q})"

サンプル:

cat <<'EOF' > show_args
#!/bin/bash
for arg in "$@"; do
  echo "ARG_$((++i))=$arg"
done
EOF
chmod +x show_args
cat <<'EOF' > test.sh
#!/bin/bash
QUOTED_ARGS=${@@Q}    # this is important!!!!!!
echo QUOTED_ARGS is "$QUOTED_ARGS"

echo de-quote QUOTED_ARGS
declare -a args="($QUOTED_ARGS)"
./show_args "${args[@]}"
EOF
chmod +x test.sh

テスト:

ARGS=("a a a" "b'b'b" 'c"c"c')
./show_args "${ARGS[@]}"

3つの要素で構成された配列であることを示すことができます。

ARG_1=a a a
ARG_2=b'b'b
ARG_3=c"c"c

引用符とバックティックがどのように機能するかを見てみましょう。

./test.sh "${ARGS[@]}"

または

./test.sh "a a a" "b'b'b" 'c"c"c'

明らかにする

QUOTED_ARGS is 'a a a' 'b'\''b'\''b' 'c"c"c'
de-quote QUOTED_ARGS
ARG_1=a a a
ARG_2=b'b'b
ARG_3=c"c"c

2022/08/31 編集:注入試行テストを追加:

./test.sh "\$(echo test >&2)"

結果:

QUOTED_ARGS is '$(echo test >&2)'
de-quote QUOTED_ARGS
ARG_1=$(echo test >&2)

「echo test」コマンドが呼び出されないことをお勧めします。

正常に修復されました。

関連情報