bash의 컷은 실패하는데 zsh는 실패하는 이유는 무엇입니까?

bash의 컷은 실패하는데 zsh는 실패하는 이유는 무엇입니까?

탭으로 구분된 필드가 있는 파일을 만듭니다.

echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input

다음 스크립트 이름이 있습니다.zsh.sh

#!/usr/bin/env zsh
while read line; do
    <<<$line cut -f 2
done < "$1"

테스트해보겠습니다.

$ ./zsh.sh input
bar
bar

이것은 훌륭하게 작동합니다. 그러나 첫 번째 줄을 변경하여 호출하면 bash실패합니다.

$ ./bash.sh input
foo bar baz
foo bar baz

이것이 실패 bash하고 작동하는 이유는 무엇입니까 zsh?

기타 문제 해결

  • 대신 shebang에서 직접 경로를 사용하면 env동일한 동작이 발생합니다.
  • echo여기서 문자열 대신 파이프를 사용하면 <<<$line동일한 동작이 생성됩니다. 즉 echo $line | cut -f 2.
  • awk대신 사용cut 일하다두 껍질 모두에 해당됩니다. 즉 <<<$line awk '{print $2}'.

답변1

이는 4.4 이전 버전에서는 <<< $line따옴표가 없을 때(와일드카드는 아니지만) 단어 분할이 수행되었고 결과 단어가 공백 문자로 연결되었기 때문입니다(그리고 임시 파일에 넣은 다음 줄 바꿈을 하고 다음으로 설정했습니다). 표준 입력).bash$linecut

$ a=a,b,,c bash-4.3 -c 'IFS=","; sed -n l <<< $a'
a b  c$

tab기본값은 다음과 같습니다 $IFS.

$ a=$'a\tb'  bash-4.3 -c 'sed -n l <<< $a'
a b$

해결책은 bash변수를 참조하는 것입니다.

$ a=$'a\tb' bash -c 'sed -n l <<< "$a"'
a\tb$

이 작업을 수행하는 유일한 쉘이라는 점에 유의하십시오. zsh( <<<Byron Rakitzis의 구현에서 영감을 얻은 출처 rc), ksh93또한 이를 수행하지 mksh않도록 yash지원합니다 <<<.

배열의 경우 , mksh, yash및 는 zsh의 첫 번째 문자에서 연결되고 공백에서 연결됩니다 .$IFSbashksh93

$ mksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ yash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ ksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ bash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$

비어 있으면 zsh/ yashmksh(적어도 R52 버전) 사이에 차이가 있습니다.$IFS

$ mksh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
12$

사용할 때 동작은 셸 전체에서 더 일관됩니다 ( "${a[*]}"비어 있을 때 mksh여전히 오류가 발생하는 경우 제외 $IFS).

그러나 에서는 Bourne과 유사한 모든 쉘 (및 관련 FAQ ) echo $line | ...의 일반적인 분할+glob 연산자입니다 .zshecho

답변2

bash무슨 일이 일어나는지는 탭 문자가 공백으로 대체된다는 것입니다 . "$line"반대의 말을 하거나 명시적으로 공백을 제거하면 이 문제를 피할 수 있습니다 .

답변3

문제는 참조가 없다는 것입니다 $line. 조사하려면 두 스크립트를 모두 변경하여 다음만 인쇄하십시오 $line.

#!/usr/bin/env bash
while read line; do
    echo $line
done < "$1"

그리고

#!/usr/bin/env zsh
while read line; do
    echo $line
done < "$1"

이제 출력을 비교해 보세요.

$ bash.sh input 
foo bar baz
foo bar baz
$ zsh.sh input 
foo    bar    baz
foo    bar    baz

보시 $line다시피 참조가 없기 때문에 bash는 탭을 올바르게 해석할 수 없습니다. Zsh는 이것을 더 잘 처리하는 것 같습니다. 이제 기본적으로 필드 구분 기호 cut로 사용됩니다 . \t따라서 bash스크립트가 탭을 차지하므로(split+glob 연산자로 인해) cut하나의 필드만 보고 작업할 수 있습니다. 실제로 실행 중인 작업은 다음과 같습니다.

$ echo "foo bar baz" | cut -f 2
foo bar baz

따라서 스크립트가 두 셸 모두에서 예상대로 작동하도록 하려면 변수를 인용하십시오.

while read line; do
    <<<"$line" cut -f 2
done < "$1"

그런 다음 둘 다 동일한 출력을 생성합니다.

$ bash.sh input 
bar
bar
$ zsh.sh input 
bar
bar

답변4

이미 답변했듯이 변수를 사용하는 보다 이식 가능한 방법은 변수를 참조하는 것입니다.

$ printf '%s\t%s\t%s\n' foo bar baz
foo    bar    baz
$ l="$(printf '%s\t%s\t%s\n' foo bar baz)"
$ <<<$l     sed -n l
foo bar baz$

$ <<<"$l"   sed -n l
foo\tbar\tbaz$

Bash의 구현은 다음과 같이 다릅니다.

l="$(printf '%s\t%s\t%s\n' foo bar baz)"; <<<$l  sed -n l

대부분의 쉘에 대한 결과는 다음과 같습니다.

/bin/sh         : foo bar baz$
/bin/b43sh      : foo bar baz$
/bin/bash       : foo bar baz$
/bin/b44sh      : foo\tbar\tbaz$
/bin/y2sh       : foo\tbar\tbaz$
/bin/ksh        : foo\tbar\tbaz$
/bin/ksh93      : foo\tbar\tbaz$
/bin/lksh       : foo\tbar\tbaz$
/bin/mksh       : foo\tbar\tbaz$
/bin/mksh-static: foo\tbar\tbaz$
/usr/bin/ksh    : foo\tbar\tbaz$
/bin/zsh        : foo\tbar\tbaz$
/bin/zsh4       : foo\tbar\tbaz$

bash만 인용되지 않은 경우 변수를 오른쪽으로 분할합니다 <<<.
그러나 이 문제는 bash 버전 4.4에서 수정되었습니다.
즉, 의 값이 의 $IFS결과에 영향을 미칩니다 <<<.


라인 포함:

l=(1 2 3); IFS=:; sed -n l <<<"${l[*]}"

모든 쉘은 IFS의 첫 번째 문자를 사용하여 값을 연결합니다.

/bin/y2sh       : 1:2:3$
/bin/sh         : 1:2:3$
/bin/b43sh      : 1:2:3$
/bin/b44sh      : 1:2:3$
/bin/bash       : 1:2:3$
/bin/ksh        : 1:2:3$
/bin/ksh93      : 1:2:3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

의 경우 "${l[@]}"다른 인수를 구분하기 위해 공백이 필요하지만 일부 쉘은 IFS의 값을 사용하도록 선택합니다(맞습니까?).

/bin/y2sh       : 1:2:3$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

빈 IFS의 경우 값은 다음 줄과 같이 연결되어야 합니다.

a=(1 2 3); IFS=''; sed -n l <<<"${a[*]}"

/bin/y2sh       : 123$
/bin/sh         : 123$
/bin/b43sh      : 123$
/bin/b44sh      : 123$
/bin/bash       : 123$
/bin/ksh        : 123$
/bin/ksh93      : 123$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

그러나 lksh나 mksh는 이를 수행할 수 없습니다.

매개변수 목록으로 변경하면:

l=(1 2 3); IFS=''; sed -n l <<<"${l[@]}"

/bin/y2sh       : 123$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

yash나 zsh 모두 매개변수를 분리할 수 없습니다. 이것은 버그입니까?

관련 정보