44

bash プログラムを作成するときは、通常、次のような呼び出しを作成します。

declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )

"${mycmd[@]}" || echo "Failed: foo"

die foo出力して終了する bash 関数はどこにありますかError foo

しかし、エラーの理由を明確にしたい場合は、失敗したコマンドを出力します。

"${mycmd[@]}" || echo "Failed: foo: ${mycmd[*]}"

したがって、ユーザーはデッド コマンドを実行して、その理由を突き止めることができます。ただし、このパスでは引用符が失われます。スペースまたはエスケープ文字を含む Failed メッセージ引数は、カットアンドペーストして実行できる方法で出力されません。

この問題を解決するためのコンパクトな方法についての提案はありますか?


問題は、bash がコマンドの引数解析を処理する方法と、(組み込みの) echo が引数を処理する方法だと思います。問題を述べる別の方法は次のとおりです。

次の bash の例 (即時モードではなく、スクリプトとして実行する必要があります) で、スペースを含む引数の周りに引用符を出力するにはどうすればよいですか?

#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"

実結果:

1  2  3 4
1 2 3 4

望ましい結果

1  2  3 4
1 2 "3 4"

また

1  2  3 4
"1" "2" "3 4"

いくつかの追加の bash コード文字で。


質問は閉じられました: @camh は見事に答えました:

更新されたスクリプト:

#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"
echo $(printf "'%s' " "${myargs[@]}")

出力:

1  2  3 4
1 2 3 4
'1' '2' '3 4'
4

6 に答える 6

52

あなたの問題はにありechoます。正しい数のパラメーターを取得しており、一部のパラメーターにはスペースが含まれていますが、その出力では、パラメーター間のスペースとパラメーター内のスペースの区別が失われます。

代わりに、を使用printf(1)してパラメーターを出力し、常に引用符を含めることができます。フォーマット文字列にフォーマット指定子よりも多くのパラメーターがある場合に、フォーマット文字列をパラメーターに連続して適用するprintfの機能を利用します。

echo "Failed: foo:" $(printf "'%s' " "${mycmd[@]}")

これにより、必要がない場合でも、各引数を一重引用符で囲むことができます。

Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'

他のシェルメタ文字が誤って処理されないように、一重引用符を使用しました。これは、一重引用符自体を除くすべての文字で機能します。つまり、一重引用符を含むパラメーターがある場合、上記のコマンドからの出力は正しくカットアンドペーストされません。これは、乱雑になることなく取得できる最も近いものである可能性があります。

編集:ほぼ5年後、私がこの質問に答えてから、bash4.4がリリースされました。これには、"${var@Q}"bashで解析できるように変数を引用する拡張機能があります。

これにより、この回答が次のように簡略化されます。

echo "Failed: foo: " "${mycmd[@]@Q}"

これにより、引数内の一重引用符が正しく処理されますが、以前のバージョンでは処理されませんでした。

于 2012-10-20T04:48:59.367 に答える
21

bash の printf コマンドには、文字列が出力されるときに適切な引用符を文字列に追加する %q 形式があります。

echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

何かを引用する「最良の」方法のアイデアは、常に私のものと同じとは限りません。たとえば、文字列を引用符で囲むのではなく、面白​​い文字をエスケープすることを好む傾向があります。例えば:

crlf=$'\r\n'
declare -a mycmd=( command.ext "arg1 with space 'n apostrophe" "arg2 with control${crlf} characters" )
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

版画:

Failed: foo: command.ext arg1\ with\ space\ \'n\ apostrophe $'arg2 with control\r\n characters'
于 2012-10-20T05:24:20.873 に答える
2

どうdeclare -p quotedarrayですか?

- 編集 -

実際、declare -p quotedarrayあなたの目的を十分に満足させます。結果の出力形式を主張する場合は、小さなトリックで作業を行うことができますが、連想配列ではなくインデックス付き配列のためだけです。

declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
于 2012-10-20T04:12:31.950 に答える
1

面倒なメソッド(スペースを含む引数のみを引用する):

declare -a myargs=(1 2 "3 4")
for arg in "${myargs[@]}"; do
    # testing if the argument contains space(s)
    if [[ $arg =~ \  ]]; then
      # enclose in double quotes if it does 
      arg=\"$arg\"
    fi 
    echo -n "$arg "
done

出力:

1 2 "3 4"

ちなみに、に関してはquoting is lost on this pass、引用符は保存されないことに注意してください。" "は、内部にあるものをすべて単一のフィールド/引数として処理する(つまり分割しない)ようにシェルに指示する特殊文字です。一方、リテラル引用符(このように入力\")は保持されます。

于 2012-10-20T05:40:08.113 に答える
-1

再利用と文書化が容易になるように、コードを関数に入れるのが好きです。

function myjoin
{
   local list=("${@}")
   echo $(printf "'%s', " "${list[@]}")
}

declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')

echo "[$(myjoin ${colorlist[@]})]"

bash スクリプトを使用してコードを生成しているため、ソリューションにコンマを追加した方法に注意してください。

[編集: EM0 が ['blue',] を返していると指摘した問題を修正]

于 2015-06-27T02:40:14.477 に答える