Чому зріз провалюється з bash, а не zsh?


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 виробляє таку саму поведінку.
  • Трубопровід з echoзамість використання тут-рядка <<<$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
Чи є якась причина, коли Баш бачить a \tі замінює його пробілом?
користувач1717828

@ user1717828 так, це називається оператором spit + glob . Це те, що відбувається, коли ви використовуєте змінну без котирування в bash та подібних оболонках.
тердон

1
@terdon, в <<< $line, bashрозщеплюється, але не глобально. Тут немає ніяких причин, щоб це розкололося, як <<<очікує одного слова. Він розбивається, а потім приєднується до цього випадку, що мало сенсу і проти всіх інших реалізацій оболонок, які підтримуються <<<до чи після bash. ІМО, це помилка.
Стефан Шазелас

@ StéphaneChazelas досить справедливо, питання все одно з розділеною частиною.
terdon

2
@ StéphaneChazelas На баш 4.4 не трапляється спліт (ні глобус)

17

Це тому, що в <<< $line, bashрозбиття слів (хоч і не глобусне), $lineоскільки воно не цитується там, а потім з'єднує отримані слова з символом пробілу (і ставить це у тимчасовий файл, за яким слідує символ нового рядка і робить це stdin of 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(звідки <<<походить, натхненний портом Unix 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вона порожня).

В echo $line | ..., це звичайний оператор спліт + Glob у всіх Bourne-подібних оболонок , але 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

Як бачите, оскільки ви не цитуєте $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

Відмінна відповідь! Дякую (+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 розділяє змінну праворуч від <<<котирування.
Однак це було виправлено у версії 4.4 bash.
Це означає, що значення $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. Дивіться цю (довгу) дискусію
Стефан Шазелас


Відповідаючи собі, ні, маючи другий погляд, POSIX залишить поведінку var=$@не визначеною.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.