내 사용 사례:
composer.json
파일에 나열된 모든 개발 패키지를 제거해야 합니다 . projectx/package-nice
와 이라는 두 개의 패키지가 있다고 가정해 보겠습니다 projecty/package-good
. 삭제하려면 다음을 실행해야 합니다.
$ composer remove --dev projectx/package-nice projecty/package-good
그래서 패키지 목록을 추출하기 위해 다음 명령을 작성합니다.
echo $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
그러면 아래와 같이 패키지 목록이 반환됩니다.projectx/package-nice projecty/package-good
그래서 bash가 반환값을 따옴표로 묶인 단일 문자열로 해석하기 때문에 성공하지 못한 채 다음 명령을 실행해 보았습니다.
$ composer remove --dev $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
이는 다음과 같습니다:
$ composer remove --dev "projectx/package-nice projecty/package-good"
그럼 내가 뭘 잘못하고 있는 걸까요?
편집하다:
문제는 구문 분석에 있는 것이 아닙니다. $()
공백으로 구분된 예상 값을 반환합니다 . 문제는 bash가 이 반환값을 고유한 값으로 해석하는 이유입니다.
@MarcusMüller가 지적했듯이 이 문제는 발생해서는 안 됩니다. 나는 안으로 달리고 있다 /etc
:
$ ls $(ls | head -n 2)
그리고 실행된 명령은 ls file1 file2
대신 이므로 ls "file1 file2"
왜 이런 일이 발생하는지 이해할 수 없습니다. 아마도 작곡가는 PHP로 실행되는 스크립트일 뿐이고 이것이 뭔가를 방해하고 있기 때문일까요?
감사해요.
답변1
composer remove --dev $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
같지 않음:
composer remove --dev "projectx/package-nice projecty/package-good"
그 $(cmd)
부분은 참조되지 않고 목록 컨텍스트에서 분할+glob의 영향을 받기 때문입니다(여기서는 간단한 명령의 인수에서).
수정하지 않는 한, $IFS
(나뉘다부분)에 공백 문자가 포함되어 있으므로 cmd
출력 인 경우 projectx/package-nice projecty/package-good\n
합계로 분할되어 projectx/package-nice
별도 projecty/package-good
의 인수로 에 전달됩니다 composer
.
그런데 개행 문자도 기본값이므로 $IFS
귀하의 것 xargs
(개행 문자를 공백으로 변환하려는 의도인 것 같습니다)은 의미가 없습니다.
셸을 사용하는 경우 분할+glob을 사용하는 대신 파일의 줄을 배열의 개별 요소로 읽는 bash
것이 더 합리적입니다.readarray
readarray -t packages < <(
composer show -s | grep -Po '^\p{Ll}+/[\p{Ll}\d_-]\H*'
)
(( ${#packages[@]} == 0 )) ||
composer remove --dev "${packages[@]}"
Split+glob을 사용하는 것도 옵션이지만 항상 그렇듯이 사용 시 특정 요구 사항에 맞게 조정하는 것이 가장 좋습니다.
IFS=$'\n' # split on newline only
set -o noglob # disable the glob part which we don't want
packages=( $(cmd...) ) # split+glob, result assigned to an array
귀하의 경우 출력에는 cmd
공백이나 탭이 포함되어서는 안 되며, 다른 두 문자는 $IFS
기본값 in 이므로 그대로 bash
둘 수 있습니다 $IFS
.
그러나 글로브가 포함될 수 있습니다. 예를 들어, composer show -s
출력 인 경우 etc/p* blah blah
파이프라인은 출력되고 etc/p*
, 내부에서 실행되지 /
않으면 파이프라인은 , , ... 로 확장됩니다 set -o noglob
.etc/p*
etc/pam.conf
etc/passwd
etc/profile
분할+glob을 방지하고 출력 cmd
(명령 대체에 의해 제거된 후행 개행 제외)을 명령에 대한 단 하나의 인수로 전달하려면 큰따옴표를 사용하십시오.
composer remove --dev "$(cmd)"
cmd
(패키지가 하나만 출력되는 경우에만 의미가 있습니다.)
Linux에서는 다음을 사용하여 명령에 인수가 전달되는 것을 볼 수 있습니다.
strace -s999999 -qqfe execve the-command and its args
(또는 strace
쉘에서 명령을 실행하여 쉘 execve()
에서 수행된 모든 시스템 호출이나 생성된 모든 프로세스를 추적합니다.)
예를 들어:
분할+글로브, 기본값은 IFS입니다.
bash-5.0$ strace -s999999 -qqfe execve true $(echo foo; echo foo bar)
execve("/usr/bin/true", ["true", "foo", "foo", "bar"], 0x7ffe1374cc50 /* 66 vars */) = 0
줄 바꿈에서만 분할:
bash-5.0$ (IFS=$'\n'; strace -s999999 -qqfe execve true $(echo foo; echo; echo foo bar))
execve("/usr/bin/true", ["true", "foo", "foo bar"], 0x7ffd16c547f8 /* 66 vars */) = 0
(빈 줄은 제거되었습니다.)
glob 부분의 역할:
bash-5.0$ (strace -s999999 -qqfe execve true $(echo 'etc/p*'))
execve("/usr/bin/true", ["true", "etc/pam.conf", "etc/pam.d", "etc/papersize", "etc/parallel", "etc/passwd", "etc/passwd-", "etc/pcmcia", "etc/perl", "etc/php", "etc/pki", "etc/pm", "etc/pnm2ppa.conf", "etc/polkit-1", "etc/popularity-contest.conf", "etc/ppp", "etc/printcap", "etc/profile", "etc/profile.d", "etc/protocols", "etc/pulse", "etc/python2.7", "etc/python3", "etc/python3.8"], 0x7ffdc911f8a0 /* 66 vars */) = 0
다음으로 수정됨 set -o noglob
:
bash-5.0$ (set -o noglob; strace -s999999 -qqfe execve true $(echo 'etc/p*'))
execve("/usr/bin/true", ["true", "etc/p*"], 0x7ffe9c278a50 /* 66 vars */) = 0
참조로 분할+글로브를 비활성화합니다.
bash-5.0$ (IFS=$'\n'; strace -s999999 -qqfe execve true "$(echo foo; echo; echo foo bar; echo 'etc/p*')")
execve("/usr/bin/true", ["true", "foo\n\nfoo bar\netc/p*"], 0x7ffcf0e70d20 /* 66 vars */) = 0
에서는 zsh
glob 부분이 아닌 따옴표가 없는 명령 대체 시 IFS 분할 부분만 수행됩니다(ksh는 분할+glob 외에 중괄호 확장도 수행함). 에서는 zsh
매개변수 확장 플래그를 사용하여 명령 대체 위에 명시적 분할을 적용할 수도 있습니다 . 예를 들어 출력을 줄 바꿈으로 분할하므로 여기서는 다음을 수행합니다.s
f
0
${(f)"$(cmd)"}
cmd
packages=( ${(f)"$(composer show -s | grep -Po '^\p{Ll}+/[\p{Ll}\d_-]\H*')"} )
(( $#packages == 0 )) || composer remove --dev $packages
$IFS
와일드카드를 전체적으로 수정하거나 비활성화할 필요가 없습니다 .
¹ 임의의 입력으로 인해 실패하고 비효율적이 되는 경우가 많기 때문에 이는 잘못된 방법입니다.
답변2
당신은 해결책을 가지고 있습니다. 명령 은 echo
공백으로 구분된 2개의 문자열을 반환합니다. 이것을 명령 대체로 감싸면 $()
(예, 중첩될 수 있음) 원하는 것을 얻을 수 있습니다.
composer remove --dev $(echo $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs))