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およびyashzshの最初の文字で連結され、スペースで連結されます。$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演算子のため)、1つの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はどちらもパラメータを分離できません。これはバグですか?

関連情報