탭으로 구분된 필드가 있는 파일을 만듭니다.
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
$line
cut
$ 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
의 첫 번째 문자에서 연결되고 공백에서 연결됩니다 .$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
/ yash
와 mksh
(적어도 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 연산자입니다 .zsh
echo
답변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 모두 매개변수를 분리할 수 없습니다. 이것은 버그입니까?