쉘(zsh/bash)에서의 확장 및 인용을 잘 이해하지 못합니다.

쉘(zsh/bash)에서의 확장 및 인용을 잘 이해하지 못합니다.

쉘에서 변수를 인용하는 것에 대한 답변을 여기에서 읽었습니다. 기본적으로 이것이 내가 실행하는 것입니다.

for f in "$(tmsu files)"; do echo "${f}"; done

이는 예상된 결과입니다: 파일 목록입니다. 하지만 나는 그것들이 하나의 문자열로 나타날 것이라고 확신합니다. 약간 읽은 후에 이것이 zsh-ism이라는 것을 깨달았습니다. 이는 대부분의 쉘과 다르게 분할을 처리합니다.

bash그래서 나는 같은 명령을 실행 하고 실행했습니다. 목록이 분할될 줄 알았는데 아쉽게도정확히같은 결과. 그래서 지금은 정말 혼란스럽습니다.

  1. Bash가 예상대로 실행되지 않는 이유는 무엇입니까?
  2. zsh 분할선을 만드는 방법은 무엇입니까?

나는 (zsh)를 시도했지만 성공 for f in "$(tmsu files)"; do echo "${=f}"; done 하지 for f in "$(=tmsu files)"; do echo "${f}"; done 못했기 때문에 분할 방법을 오해한 것 같습니다 zsh.

더 나쁜 것은 어느 시점에서 내가 원하는 대로 정확하게 작동하도록 만들었지만 어떻게 했는지 기억이 나지 않는다는 것입니다.

답변1

for f in "$(tmsu files)"; do echo "${f}"; done

이것은 zsh-ism보다 더 나쁜 습관입니다. 큰따옴표는 확장 결과를 단일 문자열로 유지하도록 쉘에 지시합니다. 대부분의 경우 이는 귀하가 원하는 것입니다. 예를 들어 다음과 같은 경우가 있습니다.

filename="foo bar"
ls "$filename"

두 인수 ls의 합이 아닌 단일 인수로 파일 이름을 가져오려고 합니다 . (이것들은 다른 파일 이름이 될 것입니다.) 이제 명령 대체를 사용하고 있다는 사실이 이것을 바꾸지 않습니다. 이 경우 인용이 문제를 해결하지 못한다는 것은 맞지만, 이 구성은 처음에는 어색합니다. (물론 POSIX 쉘에는 zsh에 자체 도구가 있습니다).foobar

다음과 같은 파일 목록을 고려하십시오.

hello.txt
some silly name with spaces.txt

를 사용하면 "$(output filenames)"두 개가 아닌 하나의 문자열이 제공됩니다.
을 사용하면 $(output filenames)2개가 아닌 6개의 문자열이 제공됩니다.

IFS후자가 개행 문자로 분할되어 두 개의 파일 이름을 제공하도록 개행 문자만 설정하여 이 문제를 해결할 수 있습니다 . 그러나 이는 번거롭고 전역적인 영향을 미치며 set -f파일 이름이 바뀌는 것을 방지하려면 여전히 와일드카드를 비활성화해야 합니다 funny * name.txt.

(지금까지는 변수 확장을 분할하지 않더라도 따옴표가 없는 명령 대체 결과를 분할한다는 점에서 기본적으로 zsh와 동일합니다. 그러나 기본적으로 결과를 와일드카드로 표시하지 않습니다.)

이제 차이점을 볼 수 없는 이유는 echo얻은 모든 인수를 단일 공백으로 연결하여 인쇄하기 때문입니다. 따라서 정확히 동일한 출력을 생성합니다 echo foo bar. echo "foo bar"대신 다음과 같은 것을 사용하면 printf "<%s>\n" ...매개변수 경계를 더 명확하게 볼 수 있습니다.

$ printf "<%s>\n" foo bar
<foo>
<bar>
$ printf "<%s>\n" "foo bar"
<foo bar>

또한보십시오:

답변2

하지만 나는 그것들이 하나의 문자열로 나타날 것이라고 확신합니다.

이것이 따옴표를 사용하면 얻을 수 있는 것입니다. zsh이 작업이 어떻게 수행되어야 하는지는 알 수 없지만 bash설명서에는 다음과 같이 나와 있습니다("확장" 아래의 "단어 분리" 섹션).

매개변수 확장, 명령 대체 및 산술 확장에 대한 쉘 스캔 결과큰따옴표 안에는 표시되지 않습니다.단어 분할에 사용됩니다.

(강조 추가). 작은 따옴표에서는 단어 분리가 발생하지 않지만 그 안에서도 대체 및 확장이 발생하지 않습니다.

명령 대체에 별도의 매개변수를 사용하려면 따옴표를 제거해야 합니다. 그러나 tmsu공백이 포함된 결과가 생성되면 원치 않는 분할이 생성되어(공백과 줄 바꿈 모두 단어 구분 기호로 간주되므로) 상황이 더 복잡해집니다. 에서는 행당 하나의 결과를 제공한다고 bash가정하면 다음을 수행할 수 있습니다.tmsu

tmsu files |
    while IFS= read -r f
    do
        printf "<%s>\n" "$f"
    done

내장 read함수는 입력의 전체 행을 읽습니다. 이름은 얼마든지 지정할 수 있습니다. 마지막 문자를 제외한 모든 문자는 단어 구분 기호(기본적으로 공백)를 기준으로 할당되고 마지막 문자는 줄의 나머지 부분에 할당되므로 bash확장 및 교체 시 공백 처리의 번거로움을 해결하는 좋은 아이디어입니다.

답변3

  1. zsh 분할선을 만드는 방법은 무엇입니까?

f인수 확장 플래그를 사용하십시오 (자세한 내용은 을 참조하십시오 man zshexpn. 다음과 같이 말합니다: Split the result of 확장 at newlines. 이것은 의 약어입니다 ps:\n:.)

for f in ${(f)"$( tmsu files )"}; do
  print -ru2 -- $f
done

파이프 데이터를 인쇄하기 위해 stdout을 떠나 출력을 stderr로 보냅니다(아마도 출력을 인쇄하는 것보다 더 많은 일이 있을 것입니다).

도구가 ASCII 널 문자(일명 NUL, \0)로 구분된 출력을 생성할 수 있는 경우 zsh는 해당 출력을 분할하는 대신 플래그 0(약식 )를 사용할 수 있습니다 .ps:\0:f

빈 레코드를 보존하려면 @플래그와 "따옴표(예 "${(@f)"$( command )"}": )를 추가하세요. 명령 대체의 특성으로 인해 후행 줄 바꿈을 검색하는 것은 불가능합니다.

read루프 에서 사용 하려면 whilezsh 또는 bash에서 다음과 같은 작업이 작동해야 합니다.

while IFS= read -ru3 -d '' x || [[ $x ]]; do
  {
    printf >&2 '%s\n' "$x"
    z=$x
  } 3<&-
done 3< <(printf 'x\0y\0z')
printf 'z: %s\n' "$z"

필드 분할이나 백슬래시 해석이 없고, 루프가 하위 쉘에서 실행되지 않고, stdin을 계속 사용할 수 있으며, 후행 구분 기호가 없어도 마지막 요소를 찾을 수 있습니다.

관련 정보