왜 zsh가 아닌 bash로 cut이 실패합니까?


10

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

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합니까?

추가 문제 해결

  • 대신 세방에서 직접 경로를 사용 env하면 동일한 동작이 발생합니다.
  • echohere-string을 사용하는 대신 파이프를 사용 <<<$line하면 동일한 동작이 발생합니다. 즉 echo $line | cut -f 2.
  • 사용 awk대신 cut 작품 모두 쉘합니다. 즉 <<<$line awk '{print $2}'.

4
그건 그렇고, 당신은 다음 중 하나를 수행하여 더 간단하게 테스트 파일을 만들 수 있습니다 echo -e 'foo\tbar\tbaz\n...', echo $'foo\tbar\tbaz\n...'또는 printf 'foo\tbar\tbaz\n...\n'이들의 또는 변화. 각 탭이나 줄 바꿈을 개별적으로 감싸지 않아도됩니다.
추후 공지가있을 때까지 일시 중지되었습니다.

답변:


13

무슨 일 즉 bash대체합니다 공백 탭을. "$line"대신 말 하거나 공백 을 잘라서이 문제를 피할 수 있습니다 .


1
Bash가 a를보고 \t공백으로 바꾸는 이유가 있습니까?
user1717828 2016 년

@ user1717828 예, spit + glob operator 라고합니다 . bash 및 유사한 쉘에서 인용되지 않은 변수를 사용하면 발생합니다.
terdon

1
@terdon에서 <<< $line, bash분할을 수행하지만, glob에 없습니다. <<<한 단어 를 기대할 때 여기에서 나눌 이유가 없습니다 . 그것은 분할 후 거의 의미가 지원되는 다른 모든 쉘 구현 반대 경우에 조인 <<<하기 전이나 후에 bash. IMO 버그입니다.
Stéphane Chazelas 2016 년

@ StéphaneChazelas 공정한 정도로, 문제는 어쨌든 분할 부분에 있습니다.
terdon

2
StéphaneChazelas 없음 분할 (나 글로브는) bash는 4.4에서 발생 @

17

에 있기 때문의 <<< $line, bash단어 분할, (비록 글 로빙되지 않음) 수행 $line이가 인용 아니에요으로 다음 (의 표준 입력 것을 임시 파일에 개행 문자 뒤에 것을두고하게 발생하는 공백 문자와 단어를 결합 cut).

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

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

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

해결책 bash은 변수를 인용하는 것입니다.

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

그것을하는 유일한 쉘입니다. zsh( <<<의 유닉스 포트에서 영감을 얻은 rc) ksh93, mksh그리고 yash지원 <<<하지도 않습니다.

이 배열에 관해서, mksh, yashzsh의 첫 번째 문자에 참여 $IFS, bash그리고 ksh93공간.

$ 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가있는 경우 제외 ).

에서 echo $line | ..., 그것은 모든 Bourne과 같은 쉘에서 일반적인 split + glob 연산자이지만 zsh(와 관련된 일반적인 문제 echo)입니다.


1
훌륭한 답변! 감사합니다 (+1). 그래도 그들은 가장 어리석은 질문을 받아 들일 것입니다. 왜냐하면 그들은 내 어리 석음을 드러 낼만큼 충분히 질문에 대답했기 때문입니다.
Sparhawk

10

문제는 당신이 인용하지 않는다는 것 $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

보시다시피을 인용하지 않기 때문에 $linebash가 탭을 올바르게 해석하지 못합니다. Zsh가 더 잘 처리하는 것 같습니다. 이제 기본적으로 필드 구분 기호로 cut사용 \t됩니다. 따라서 bash스크립트가 탭을 먹고 있기 때문에 (분할 + 글로브 연산자로 인해) 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

훌륭한 답변! 감사합니다 (+1). 그래도 그들은 가장 어리석은 질문을 받아 들일 것입니다. 왜냐하면 그들은 내 어리 석음을 드러 낼만큼 충분히 질문에 대답했기 때문입니다.
Sparhawk

^ 실제로 정답을 포함하는 유일한 대답 인 투표bash.sh
lauir

1

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

$ 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는 인수를 구분하지 않습니다. 버그입니까?


소개 zsh/ yash"${l[@]}"디자인의 비리스트 문맥에서 "${l[@]}"리스트 문맥에서만 특별하다. 목록에없는 상황에서는 분리 할 수 없으므로 요소를 어떻게 든 결합해야합니다. $ IFS의 첫 번째 문자와 결합하는 것은 공백 문자 IMO와 결합하는 것보다 일관됩니다. dash뿐만 아니라 ( dash -c 'IFS=; a=$@; echo "$a"' x a b). 그러나 POSIX는 IIRC를 변경하려고합니다. 참조 이 (긴) 토론
스테판 Chazelas가


POSIX는 다시 한 번 살펴보면 자신에게 대답하지 않고 동작을 var=$@지정하지 않습니다.
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.