他のコマンドを介して間接的に渡された配列を正しく参照する方法

他のコマンドを介して間接的に渡された配列を正しく参照する方法

ファイル名の配列をコマンドに渡し、正しい参照を維持する必要があります。今まではそんなに良くなった。残念ながら、このコマンドは実際には他のコマンドによって呼び出されるサブコマンドです。具体的には、コマンドは次のようになります。

git filter-branch --index-filter \
    'git rm -rf --cached ‹file1› ‹file2›…' \
    HEAD

簡単にするために、以下は同じ問題を示すより簡単なコマンドに置き換えます。

printf '%s\n' 'cmd file1 file2…'

これで配列ができましたfiles=('a b' c)。私が望む結果は、上記のコマンドが印刷されることです連続して、必要に応じて、次の各トークンを個別に引用しますcmd(たとえば、空白がある場合)。

ファイル名を手動で拡張して引用すると機能します。

$ printf '%s\n' 'cmd '\''a b'\'' c'
→ cmd 'a b' c

(または一重引用符と二重引用符を混合して同じ結果を得ることもできます。)

しかし、配列を渡そうとするともう機能しません。

  1. $ (set -x; printf '%s\n' "cmd '${files[@]}'")
    + printf '%s\n' 'cmd '\''a b' 'c'\'''
    → cmd 'a b
    c'
    
  2. $ (set -x; printf '%s\n' 'cmd '\'"${files[@]}"\')
    + printf '%s\n' 'cmd '\''a b' 'c'\'''
    → cmd 'a b
    c'
    
  3. $ (set -x; printf '%s\n' 'cmd '"${files[@]}")
    + printf '%s\n' 'cmd a b' c
    → cmd a b
    c
    

私は(3)が動作しないという事実には驚きません(完全さのために含まれています)。出力によると、set -xシェルは(1)と(2)の個々の配列要素を正しく引用し、内容全体の周りにエスケープされた引用符を追加します。ただし、個別に参照された項目を分類します。これを防ぐ方法はありますか?


しかし、Shellcheck(SC2145)は、上記のセクションを上記のセクション[@]に置き換えることを提案します。[*]これは明らかに空白のあるファイル名を壊します。

答え1

  1. 配列の代わりに引数のset -- file1 file2 ...リストを次に埋めます。bash パラメータ変換そしてQウテオペレーター:

    set -- 'a "b' c "d 'e" "f 'g "'"h' ; (set -x; printf 'cmd %s\n' "${*@Q}")
    

    出力:

    + printf 'cmd %s\n' ''\''a "b'\'' '\''c'\'' '\''d '\''\'\'''\''e'\'' '\''f '\''\'\'''\''g "h'\'''
    cmd 'a "b' 'c' 'd '\''e' 'f '\''g "h'
    

    または、その部分を削除すると、set -x; 出力は次のようになります。

    cmd 'a "b' 'c' 'd '\''e' 'f '\''g "h'
    
  2. さんのコメントLL3以下を要求しないより良いアプローチを提案してください set -- ...

    export x; n=(a "b 'c"); x="${n[@]@Q}"
    ( n=($x); printf 'cmd %s\n' "${n[*]}"; )
    

    簡単なバージョン:

    n=(a "b 'c"); echo "cmd ${n[@]@Q}"
    

    出力:

    cmd 'a' 'b '\''c'
    
  3. 別の方法はbash パラメータ変換そしてA移動する演算子(必須eval):

    export x;n=(a b 'c d');x="${n[@]@A}"; (eval "$x";printf '%s\n' "${n[@]}")
    

    出力にはprintf表示される内容が表示されます。

    a
    b
    c d
    

答え2

git filter-branch/bin/sh /usr/lib/git-core/git-filter-branchこのスクリプトを実行してください--index-filter使用された引数の評価 eval

したがって、このパラメータはコードとして評価されます/bin/sh

ほとんどのシステムでは、これは/bin/shPOSIX言語のインタプリタですが、shSolaris 10以前のような一部のシステムではまだ古代のBournesh言語である可能性があります。

引用構文に関しては何の違いもありません。

いずれにせよ、$'...'ksh/bash/zsh などの拡張参照演算子は使用できません。これは、場合によって使用されるように、GNU / bash / zsh / kshまたはprintf %q演算子mksh/bash ${var@Q}またはxtraceトレースを使用して参照を生成できないことを意味します。$'...'また、ローカライズに安全でない特定の形式の引用も使用します(例\:)。

使用できる組み込み引用演算子の1つは、パラメータ拡張フラグですzshqq一重引用符を使用するためです。

files=(foo 'a b c' $'a\nb\nc' --foo-- "a'b")
git filter-branch --index-filter "git rm -rf --cached -- ${${(@qq)files}}" HEAD

これを引用する方法を見るにはzsh

$ printf '<%s>\n' "${${(@qq)files}}"
<'foo' 'a b c' 'a
b
c' '--foo--' 'a'\''b'>

bash/ksh/yash/zsh を使用すると、次の機能を使用して同じ参照を実行できます。

shquote() {
  LC_ALL=C awk -v q=\' '
    BEGIN{
      for (i=1; i<ARGC; i++) {
        gsub(q, q "\\" q q, ARGV[i])
        printf "%s ", q ARGV[i] q
      }
      print ""
    }' "$@"
}

それから:

git filter-branch --index-filter "git rm -rf --cached -- $(shquote "${files[@]}")" HEAD

答え3

Zshにはさまざまな引用オプションがあります。最良の方法は、(q+)または(q-)で文書化された拡張フラグですzshall(1)。不要な文字が少なくなります。

$ cmd=(ssh localhost "echo hi > t")

$ newcmd=(sh -c "${${(q@)cmd}}"); echo "${${(q@)newcmd}}"
sh -c ssh\ localhost\ echo\\ hi\\ \\>\\ t

$ newcmd=(sh -c "${${(qq@)cmd}}"); echo "${${(qq@)newcmd}}"
'sh' '-c' ''\''ssh'\'' '\''localhost'\'' '\''echo hi > t'\'''

$ newcmd=(sh -c "${${(q-@)cmd}}"); echo "${${(q-@)newcmd}}"
sh -c 'ssh localhost '\''echo hi > t'\'

$ newcmd=(sh -c "${${(qqqq@)cmd}}"); echo "${${(qqqq@)newcmd}}"
$'sh' $'-c' $'$\'ssh\' $\'localhost\' $\'echo hi > t\''

構文の場合"${${(q@)cmd}}"q(またはqqなどq-)を使用すると、エスケープまたは引用が適用されます。これ@により、このエスケープが配列のすべての要素に適用されますcmd。外部は${...}同等のようです${(j: :)...}。つまり、スペースに関連付けられます。結果が再分割されないように、二重引用符が必要です。

残念ながら、ZshとBashのすべての引用メカニズムは、いくつかの入力に対して指数関数的に深いです。

次の例は、さまざまな見積拡張演算子(下のコード)の増加率を示しています。

q: (1) 6; (2) 14; (3) 24; (4) 42; (5) 76; (6) 142; (7) 272; (8) 530; 
qq: (1) 5; (2) 15; (3) 43; (4) 125; (5) 369; (6) 1099; (7) 3287; (8) 9849; 
qqq: (1) 5; (2) 13; (3) 25; (4) 45; (5) 81; (6) 149; (7) 281; (8) 541; 
qqqq: (1) 6; (2) 14; (3) 24; (4) 39; (5) 64; (6) 109; (7) 194; (8) 359; 
q-: (1) 5; (2) 15; (3) 39; (4) 97; (5) 237; (6) 575; (7) 1391; (8) 3361; 
q+: (1) 6; (2) 16; (3) 40; (4) 98; (5) 238; (6) 576; (7) 1392; (8) 3362; 

奇妙なことに、qqqq成長速度は最も遅いですが、4番目のネストレベルまでは遅延は始まりません。

Tclは、線形成長属性を持つネストされた参照演算子を持つ優れた言語です(以下の項目6を参照man tcl)。

以下は実験のコードです。との長さが異なる$'\t'ため、初期文字列として使用します。q+q-

f (){
  flag=$1
  echo -n "$flag: "
  str=$'\t'
  for i in $(seq 1 10); do
    eval 'str=\"${${('$flag'@)str}}\"'
    N=$(echo -n $str | wc -c)
    echo -n "($i) $N; "
  done
  echo
}
f q
f qq
f qqq
f qqqq
f q-
f q+

答え4

$ foo=(1 2 '3 4' 4 5)
$ printf "'%s'\n" "${foo[@]}"
'1'
'2'
'3 4'
'4'
'5'
$ subcommand() { printf "'%s'\n" "$@"; }
$ subcommand "${foo[@]}"
'1'
'2'
'3 4'
'4'
'5'

したがって、これを特定のユースケースに合わせて調整してみましょう。

git filter-branch --index-filter \
    'git rm -rf --cached file1 file2 […]' \
    HEAD

あなたの場合は、より創造的に作業をより小さな部分に分割する必要があります。

git filter-branch --index-filter  \
    'git rm -rf --cached [MAGIC]' \
    HEAD

私たちが作っているファイルのリストは、「魔法」が起こるために必要な場所です。残りはすべて静的ですか?このスクリプトを書いているので必要ありません。必要3行に分かれており、単純化されます。

git filter-branch --index-filter 'git rm -rf --cached [MAGIC]' HEAD

だから:

prefix="git filter-branch --index-filter 'git rm -rf --cached "
postfix="' HEAD"
magic="$(printf '"%s" ' "${file[@]}"'

その後、次のように実行します。

${prefix}${magic}${postfix}

したがって、コマンドはすでにsにあるので、ファイル名をsの"代わりにsで囲んでコマンドを組み合わせました。'filter-branch'

関連情報