다른 명령을 통해 간접적으로 전달된 배열을 올바르게 참조하는 방법

다른 명령을 통해 간접적으로 전달된 배열을 올바르게 참조하는 방법

파일 이름 배열을 명령에 전달하고 올바른 참조를 유지해야 합니다. 여태까지는 그런대로 잘됐다. 불행하게도 이 명령은 실제로 다른 명령에 의해 호출되는 하위 명령입니다. 구체적으로 명령은 다음과 같습니다.

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 및 이전 버전과 같은 일부 시스템에서는 여전히 고대 Bourne sh언어일 수 있습니다.

인용 구문에 관해서는 아무런 차이가 없습니다.

어떤 경우에도 $'...'ksh/bash/zsh와 같은 확장 참조 연산자는 사용할 수 없습니다. 이는 일부 경우에 사용되는 것처럼 GNU/bash/zsh/ksh printf %q또는 mksh/bash ${var@Q}연산자나 xtrace추적을 사용하여 참조를 생성 할 수 없음을 의미합니다 . $'...'또한 현지화에 안전하지 않은 특정 형식의 인용도 사용합니다(예 \: ).

사용할 수 있는 내장 인용 연산자 중 하나는 매개변수 확장 플래그입니다 zsh. qq작은따옴표를 사용하기 때문입니다.

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(또는 qqq-)을 사용하면 이스케이프 또는 인용이 적용됩니다. 이로 @인해 이 이스케이프가 배열의 모든 요소에 적용됩니다 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성장 속도가 가장 느리지만 네 번째 중첩 수준까지는 지연이 시작되지 않습니다.

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

우리가 만들고 있는 파일 목록은 "마법"이 일어나기 위해 필요한 곳입니다. 나머지는 모두 정적이죠? 이 스크립트를 작성하고 있으므로 필요하지 않습니다.필요세 줄로 나누어져 있어 단순화됩니다.

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'

관련 정보