Bashプログラムを作成するときは、通常、次のように呼び出しを作成します。
declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )
"${mycmd[@]}" || echo "Failed: foo"
ここで、die foo
はError foo
を出力して終了するbash関数です。
しかし、エラーの理由を明確にしたい場合は、失敗したコマンドを出力します。
"${mycmd[@]}" || echo "Failed: foo: ${mycmd[*]}"
したがって、ユーザーはデッドコマンドを実行してwhyを見つけることができます。ただし、このパスでは引用符が失われます-スペースまたはエスケープ文字を含むFailedメッセージの引数は、カットアンドペーストして実行できる方法では出力されません。
誰もがこの問題を解決するコンパクトな方法を提案していますか?
問題は、bashがコマンドの引数の解析を処理する方法と、(組み込みの)エコーが引数を処理する方法にあると思います。問題を説明する別の方法は次のとおりです。
次のbashの例(イミディエイトモードではなくスクリプトとして実行する必要があります)で、引数を引用符で囲んでprintするにはどうすればよいですか。
#!/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"
OR
1 2 3 4
"1" "2" "3 4"
いくつかの追加のbashコード文字。
更新されたスクリプト:
#!/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'
あなたの問題はecho
にあります。一部のパラメーターにスペースが含まれているため、正しい数のパラメーターを取得していますが、パラメーター間のスペースとパラメーター内のスペースの区別が失われています。
代わりに、printf(1)
を使用してパラメーターを出力し、常に引用符を含めることができます。フォーマット文字列にフォーマット指定子よりも多くのパラメーターがある場合、フォーマット文字列をパラメーターに続けて適用するprintfの機能を利用します。
echo "Failed: foo:" $(printf "'%s' " "${mycmd[@]}")
これは、必要がない場合でも、各引数を単一引用符で囲みます。
Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'
他のシェルメタキャラクターが誤って処理されないようにするために、一重引用符を使用しました。これは、単一引用符自体を除くすべての文字で機能します。つまり、単一引用符を含むパラメーターがある場合、上記のコマンドからの出力は正しくカットアンドペーストされません。これはおそらく、ごちゃごちゃにせずに取得できる最も近いものです。
編集:ほぼ5年後、私がこの質問に回答して以来、bash 4.4がリリースされました。これには"${var@Q}"
bashで解析できるように変数を引用する展開。
これにより、この回答が簡単になります。
echo "Failed: foo: " "${mycmd[@]@Q}"
これは、引数の単一引用符を正しく処理しますが、以前のバージョンでは処理しませんでした。
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'
面倒なメソッド(スペースを含む引数のみを引用する):
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
に関しては、引用符がnever保存されていることに注意してください。 " "
は、内部にあるすべてのものを単一のフィールド/引数として扱うように(つまり、分割しないように)シェルに指示する特殊文字です。一方、リテラル引用符(この\"
のように入力)は保持されます。
declare -p quotedarray
?
-編集-
実際、declare -p quotedarray
は目的を十分に満たします。結果の出力形式を主張する場合は、連想配列ではなく、インデックス付き配列に対してのみ、小さなトリックを実行します。
declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
# echo_array.sh
contains_space(){ [[ "$1" =~ " " ]]; return $?; }
maybe_quote_one(){ contains_space "$1" && echo \'"$1"\' || echo "$1"; }
maybe_quote(){ while test "$1"; do maybe_quote_one "$1"; shift; done; }
arridx(){ echo '${'$1'['$2']}'; }
arrindir(){ echo $(eval echo `arridx $1 $2`); }
arrsize(){ echo `eval echo '${'#$1'[@]}'`; }
echo_array()
{
echo -n "$1=( "
local i=0
for (( i=0; i < `arrsize a`; i++ )); do
echo -n $(maybe_quote "$(arrindir $1 $i)") ''
done
echo ")"
}
source echo_array.sh
a=( foo bar baz "It was hard" curious '67 - 00' 44 'my index is 7th' )
echo_array a
# a=( foo bar baz 'It was hard' curious '67 - 00' 44 'my index is 7th' )
arrindir a 7
# my index is 7th
arrindir a 0
# foo
arrsize a
# 8
$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.