タブで区切られたフィールドを持つファイルを作成します。
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演算子のため)、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はどちらもパラメータを分離できません。これはバグですか?