web-dev-qa-db-ja.com

bash関数パラメーターで引用符を保持する

私がやりたいのは、関数への入力として、引用符(シングルまたはダブル)を含む可能性のある行を取得し、その行を関数に提供されたとおりにエコーすることです。例えば:

function doit {
   printf "%s " ${@} 
   eval "${@}"
   printf " # [%3d]\n" ${?}
}

これは、次の入力が与えられた場合

doit VAR=42
doit echo 'single quote $VAR'
doit echo "double quote $VAR"

以下を生成します。

VAR=42  # [  0]
echo single quote $VAR  # [  0]
echo double quote 42  # [  0]

したがって、変数展開のセマンティクスは期待どおりに保持されますが、関数に提供された行の正確な形式を取得できません。私が欲しいのは、doit echo 'single quote $VAR'の結果をecho 'single quote $VAR'にすることです。

これは、引数が関数に渡される前に引数をbashで処理することと関係があると確信しています。私はそれを回避する方法を探しています(可能であれば)。

編集

したがって、私が意図したのは、各ステップの終了ステータスを含む診断ツールとして使用できる実行の正確なレプリカを提供しながら、スクリプトの実行をシャドウイングすることでした。

私がcanのように何かをすることによって、上記の望ましい振る舞いを得る間

while read line ; do 
   doit ${line}
done < ${INPUT}

このアプローチは、制御構造(つまり、ifwhileなど)に直面すると失敗します。 set -xを使用することを考えましたが、それにも制限があります。"'になり、失敗したコマンドの終了ステータスは表示されません。

23
ezpz

これが発生する理由は、bashがあなたが思ったように引数を解釈するためです。関数を呼び出すときに引用符が存在しないため、これは不可能です。プログラムがコマンドライン自体を解釈できるため、DOSで機能しましたが、それが役立つわけではありません。

6
Peter Westlake

私は、既存のコマンドをラップアラウンドし、引用符を保持する引数を渡すスクリプトが必要だったという点で、あなたと同じような立場にありました。

入力したとおりにコマンドラインを保持しないが、引数を正しく渡し、それらが何であるかを示すものを思いついた。

シャドウlsに設定されたスクリプトは次のとおりです。

CMD=ls
PARAMS=""

for PARAM in "$@"
do
  PARAMS="${PARAMS} \"${PARAM}\""
done

echo Running: ${CMD} ${PARAMS}
bash -c "${CMD} ${PARAMS}"
echo Exit Code: $?

そして、これはいくつかのサンプル出力です:

$ ./shadow.sh missing-file "not a file"
Running: ls "missing-file" "not a file"
ls: missing-file: No such file or directory
ls: not a file: No such file or directory
Exit Code: 1

ご覧のとおり、元々は存在しなかった引用符が追加されますが、必要なスペースを含む引数は保持されます。

11
Dave Webb
doit echo "'single quote $VAR'"
doit echo '"double quote $VAR"'

どちらも機能します。

bashは、関数に入るときに引用符の外側のセットのみを削除します。

4
Peter

@ Peter Westlake 's answer は正しく、保存する引用符はありませんが、必要に応じて引用符が元々渡されたかどうかを推測することができます。個人的には、コマンドが正しい引用符で実行されたことをログに証明する必要があるときに、このrequote関数を使用しました。

function requote() {
    local res=""
    for x in "${@}" ; do
        # try to figure out if quoting was required for the $x:
        grep -q "[[:space:]]" <<< "$x" && res="${res} '${x}'" || res="${res} ${x}"
    done
    # remove first space and print:
    sed -e 's/^ //' <<< "${res}"
}

そして、これが私がそれをどのように使うかです:

CMD=$(requote "${@}")
# ...
echo "${CMD}"
4
Chen Levy

コマンドライン引数として引用符を含む文字列を渡すと、Bashは引用符を削除します。文字列がスクリプトに渡されると、引用符はもう存在しません。一重引用符または二重引用符があることを知る方法はありません。

あなたがおそらくできることはこのようなsthです:

doit VAR=42
doit echo \'single quote $VAR\'
doit echo \"double quote $VAR\"

スクリプトでは、

echo 'single quote $VAR'
echo "double quote $VAR"

またはこれを行う

doit VAR=42
doit echo 'single quote $VAR'
doit echo '"double quote $VAR"'

スクリプトでは、

echo single quote $VAR
echo "double quote $VAR"
2
ttchong

この:

ponerApostrofes1 () 
{
    for (( i=1; i<=$#; i++ ));
    do
        eval VAR="\${$i}"; 
        echo \'"${VAR}"\';
    done; 
    return; 
}

例として、パラメータにアポストロフィがある場合に問題があります。

この関数:

ponerApostrofes2 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n \'${PARAM//\'/\'\\\'\'}\'' ';
    done;
    return
}

上記の問題を解決し、「ポーキーズ」のように内部にアポストロフィを含むパラメータを使用でき、各パラメータが引用されると、明らかに(?)同じパラメータ文字列を返します。そうでない場合は、引用します。驚いたことに、再帰的に使用すると同じリストが返されないのに、各パラメーターが再度引用される理由がわかりません。ただし、それぞれをエコーすると、元のパラメーターが復元されます。

例:

$ ponerApostrofes2 'aa aaa' 'bbbb b' 'c' 
'aa aaa' 'bbbb b' 'c'

$ ponerApostrofes2 $(ponerApostrofes2 'aa aaa' 'bbbb b' 'c' )
''\''aa' 'aaa'\''' ''\''bbbb' 'b'\''' ''\''c'\''' 

そして:

$ echo ''\''bbbb' 'b'\'''
'bbbb b'
$ echo ''\''aa' 'aaa'\'''
'aa aaa'
$ echo ''\''c'\''' 
'c'

そしてこれ:

ponerApostrofes3 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n ${PARAM//\'/\'\\\'\'} ' ';
    done;
    return
}

1レベル少ない見積もりを返すことも機能せず、両方を再帰的に交互にすることもありません。

1
Robert Vila

シェルがパターン置換をサポートしていない場合、つまり${param/pattern/string}の場合、次のsed式を使用して、任意の文字列を安全に引用し、evalを単一のパラメーターに再び変換できます。

sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"

これをprintfと組み合わせると、ファイル名の展開または"$@"によって生成された文字列のリストを取得し、それをevalに安全に渡して、パラメーターの分離を安全に維持しながら別のコマンドの引数に展開できるものに変換する小さな関数を作成できます。 。

# Usage: quotedlist=$(Shell_quote args...)
#
# e.g.:  quotedlist=$(Shell_quote *.pdf)    # filenames with spaces
#
# or:    quotedlist=$(Shell_quote "$@")
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
#
#   eval "set -- $quotedlist"
#   for str in "$@"; do
#       # fiddle "${str}"
#   done
#
# or like this:
#
#   eval "\$a_command $quotedlist \$another_parameter"
#
Shell_quote()
{
    local result=''
    local arg
    for arg in "$@" ; do

        # Append a space to our result, if necessary
        #
        result=${result}${result:+ }

        # Convert each embedded ' to \' , then insert ' at the
        # beginning of the line, and append ' at the end of
        # the line.
        #
        result=${result}$(printf "%s\n" "$arg" | \
            sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/")
    done

    # use printf(1) instead of echo to avoid weird "echo"
    # implementations.
    #
    printf "%s\n" "$result"
}

状況によっては、フィールド区切り文字として「不可能な」文字を使用し、evalを使用して値の展開を再度制御する方が簡単な場合があります(おそらく安全です。つまり、IFSを避けます)。

0
Greg A. Woods

シェルは、引用符と$を解釈してから、関数に渡します。 42がハードコーディングされているかどうか、または変数からのものであるかどうかを(二重引用符で囲む例では)知る方法がないため、関数が特殊文字を取り戻すためにできることは多くありません。特殊文字を自分の機能に到達するのに十分長く存続させたい場合は、特殊文字をエスケープする必要があります。

0
bta