web-dev-qa-db-ja.com

Bashで引用された配列の拡張

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

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

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

ここで、die fooError 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コード文字。


質問は終了しました:@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'
25
Alex Brown

あなたの問題は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}"

これは、引数の単一引用符を正しく処理しますが、以前のバージョンでは処理しませんでした。

29
camh

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'
16
Gordon Davisson

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

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保存されていることに注意してください。 " "は、内部にあるすべてのものを単一のフィールド/引数として扱うように(つまり、分割しないように)シェルに指示する特殊文字です。一方、リテラル引用符(この\"のように入力)は保持されます。

1
doubleDown

declare -p quotedarray

-編集-

実際、declare -p quotedarrayは目的を十分に満たします。結果の出力形式を主張する場合は、連想配列ではなく、インデックス付き配列に対してのみ、小さなトリックを実行します。

declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
1
weynhamz

別のアプローチ

# 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.
0
kyb