В чем причина того, что $ array не расширяет весь массив в ksh и bash?

Если вы хотите сохранить порядок файла (не отсортированный), но все же удалить дубликаты, вы также можете сделать это

awk '!v[$1]++' /tmp/file

Например

d
d
a
a
b
b
c
c
c
c
c

Будет выведено

d
a
b
c
7
01.08.2017, 04:06
2 ответа

Судя по вашим комментариям к этому ответу, вы ожидаете, что в ответе будет сказано и признано, что парадигма zsh «лучше» и, следовательно, так должны работать все оболочки. "Лучше" - это просто мнение. Мнение не требует обсуждения.

  • Возможно, вы ожидаете узнать, почему $aможно также использовать для массива.

    Это то, что пытается сделать zsh, и даже если он проделал большую работу в этом направлении,
    в некоторых случаях он все еще терпит неудачу. Он еще не копирует массивы ().

    $ a=(1 2 3)
    $ b=$a
    $ printf '<%s>' $a ' ' $b; echo
    <1><2><3>< ><1 2 3>
    

  • Но этот вопрос на самом деле очень далек от этого. Это проблема языка.

В нашем языке мы используем имя для каждой идеи. Каждая новая идея, которая радикально отличается от предыдущих идей , должна иметь собственное имя. Для нашего языка естественно, что мы принимаем новые слова для новых идей. Подумайте о названии «Интернет», новом имени для новой идеи. Использование старых названий для новых понятий всегда приводит к путанице и недопониманию. Вот как мы, люди, устроены, и звучит разумно, если подумать.

  • В оболочке (и любом языке программирования )мы используем определенный синтаксис для каждой конкретной идеи.

С самого началапеременная в оболочке использовала имя (accepta)и ее значение было результатом расширения (процедуры оболочки )символов $a. Переменная могла содержать строку или число (, автоматически преобразованное ).

  • Введение нового содержимого для переменной (массива значений)должно использовать новый синтаксис.

Это именно то, что Perl сделал, используя @aдля обозначения списка , сохраняя при этом $aдля an scalar.
То есть:Вышеупомянутые два называются контекстом SCALAR и LIST.(В Perl).

Вот почему у нас есть a=(1 2)для назначения массива (есть и другие способы, но это наиболее распространенный ). Синтаксис, недопустимый в предыдущих оболочках.

В ш (в данном случае тире):

$ a=(12)
sh: 2: Syntax error: "(" unexpected

И расширение переменной было расширено с $aили эквивалентного ${a}на новый синтаксис (и недействительно в sh)`${a[@]}:

$ a=(aa bb cc)
$ printf '%s\n' "${a[@]}"
aa
bb
cc

В более простых оболочках (зола в данном случае):

$ a=Strin-Of-Text
$ printf '%s\n' "$a"
ash: syntax error: bad substitution

Очень жаль, что был выбран такой запутанный способ записи массива.

Если бы я предложил один новый синтаксис, я бы, вероятно, последовал примеру Perl и использовал что-то похожее на "@a" для списка значений (или, может быть, #aили%a). Это должно стать предметом некоторого обсуждения для достижения определенного консенсуса.

  • Но это не то, что было сделано (печально ), то, что было выбрано:${a[@]}.

Короче :Для обратной совместимости ожидается, что раскрытие простой переменной $aприводит только к одному значению, а не к списку значений, более того, к отдельным значениям.

Поскольку в POSIX не определены массивы, в -может быть целесообразно указать, что в POSIX определение Расширение параметра говорит:

The value, if any, of parameter shall be substituted.

И, ну,на самом деле, большинство оболочек печатают только скаляр с$a

bash(4.4)       : <1><====><1><2><3>
lksh            : <1><====><1><2><3>
mksh            : <1><====><1><2><3>
ksh93           : <1><====><1><2><3>
astsh           : <1><====><1><2><3>
zsh/ksh         : <1><====><1><2><3>
zsh             : <1><2><3><====><1><2><3>

Таким образом, для написания сценариев оболочки zsh [a] является нечетным.

Протестировано с этим скриптом:

a=(1 2 3)
printf '<%s>' $a '====';
printf '<%s>' "${a[@]}" ;
echo

[а] Тоже яш. csh-совместимые оболочки не тестировались. Скрипты с csh - известная проблема.

1
27.01.2020, 20:19

Я не могу дать ответ, но предлагаю несколько возможных объяснений.

Это правда, что кроме ksh и его клонов (pdksh и его производных и bash ), все остальные оболочки с массивами (csh, tcsh, rc, es, akanga, fish, zsh,yash)имеют $arrayрасширение на все элементы массива.

Но как в yash, так и в zsh(, когда в shэмуляции )две оболочки типа Bourne -в этом списке, это расширение по-прежнему подлежит разделению + подстановке (и по-прежнему пусто. удаление даже в zshдаже если не в shэмуляции ), поэтому вам все равно нужно использовать неуклюжий синтаксис "${array[@]}"(или "${(@)array}"или "$array[@]"в zsh, которые вряд ли легче введите¹ )для сохранения списка(cshи tcshимеют аналогичные проблемы ). Этот split+glob и пустое удаление являются наследием Борна (, в некоторой степени вызванным наследием оболочки Томпсона, где $1больше похоже на макрорасширение ).

rcи fish— два примера более поздних оболочек, которые не имеют багажа Борна и имеют более чистый подход. Они признают тот факт, что оболочка является интерпретатором командной строки, и в первую очередь они имеют дело со списками (списка аргументов команд ),таким образом, список/массив является первичным типом данных (, существует только один тип, и это списки в rc), и мы избавились от split+glob -после -ошибки расширения/неправильной функции оболочки Bourne (, которая больше не нужен, поскольку основным типом является массив ).

Тем не менее, это не объясняет, почему Дэвид Корн решил $arrayрасширить не все элементы, а элемент с индексом 0.

Теперь, за исключением csh/tcsh, все эти оболочки намного новее, чем ksh, разработанные в начале 80-х, всего через несколько лет после выпуска оболочки Bourne и Unix V7. Unix V7 также представил среду. В то время это была модная новинка. Среда аккуратная и полезная, но переменные среды не могут содержать массивы, если вы не используете какую-либо форму кодирования.

Это всего лишь предположение, но я подозреваю, что одна из причин, по которой Дэвид Корн выбрал этот подход, заключалась в том, что интерфейс со средой не был изменен.

В ksh88, как и в rc, все переменные были (разреженными массивами; немного похоже на ассоциативные массивы с ключами, ограниченными положительными целыми числами, что является еще одной странностью по сравнению с другими оболочками или языками программирования, и вы можете сказать, что это не было полностью продумано, поскольку, например, было невозможно получить список ключей ). В этом новом дизайне var=valueстало сокращением от var[0]=value. Вы по-прежнему можете экспортировать все свои переменные, но export varэкспортирует элемент индекса 0 массива в среду.

rcпомещает все свои переменные в среду, fishподдерживает экспорт массивов, но для массивов с более чем одним элементом, (по крайней мере, для порта Unix для rc, который исходит от plan9 ), им приходится прибегать к какой-то форме кодирования, понятной только им.

csh, tcsh, zshне поддерживают экспорт массивов (, хотя в настоящее время это может не показаться большим ограничением ). Вы можете экспортировать массивы в yash,но они экспортируются как переменная среды, которая представляет собой элементы массива, объединенные с :(, поэтому (a "" "" b)и (a : b)экспортируются с одним и тем же значением ), и при импорте нет преобразования обратно в массив.

Другим возможным оправданием может быть согласованность с $@/$*(Борна, но тогда почему индексы массива начинаются с 0 вместо 1 (еще одна странность по сравнению с другими оболочками/языками того времени )? ). kshне было свободным ПО, это было коммерческое предприятие, одним из требований была совместимость по Борну. kshдействительно удалил разделение полей, выполненное для каждого не -слова в кавычках в контексте списка (, поскольку это было явно бесполезно в оболочке Bourne ), но пришлось сохранить его для расширений (, так как скрипты использовали такие вещи, как var="file1 file2"; cmd $var, поскольку оболочка Bourne не имела массива, кроме"$@"). Сохранение этого в оболочке, которая в противном случае имеет массивы, не имеет особого смысла, но у Korn не было другого выбора, если Ksh все еще мог интерпретировать сценарии потребительской базы. Если бы $scalarподвергалось разделению + подстановке, $arrayдолжно было бы быть для согласованности, и поэтому "${array[@]}"как обобщение "$@"имело некоторый смысл. zshне имел подобного ограничения, поэтому мог свободно удалять split+glob при расширении одновременно с добавлением массивов (, но поплатился за нарушение обратной совместимости по Борну ).

Другим объяснением, предложенным @Arrow, могло быть то, что он не хотел перегружать существующие операторы, чтобы заставить их вести себя по-разному для разных типов переменных (например ${#var}против ${#array}через оболочку Bourne не было этого или ${var-value}, ${var#pattern}), что может вызвать путаницу у пользователей (в zshне всегда очевидно, как некоторые операторы работают с массивом против скаляра ).

Некоторая связанная литература:


Что касается случая a=$@в вашем редактировании, это на самом деле один случай, когда ksh нарушил совместимость с оболочкой Bourne.

В оболочке Борна $@и $*содержали объединение позиционных параметров с символами пробела. Только $@в кавычках был особенным, поскольку он расширялся до того же, что и "$*", но со вставленными пробелами, не заключенными в кавычки (с особыми случаями для пустого списка в более новых версиях, где к нему обращались, как в Solaris ). Вы заметите, что если вы удалите пробел из $IFS, "$@"расширяется до одного аргумента в контекстах списка (0 для пустого списка в исправленных версиях, упомянутых выше ). Если не цитировать, $*и $@ведут себя как любая другая переменная (, разделенная на символы $IFS, не обязательно на исходные позиционные параметры ). Например, в оболочке Борна:

'set' 'a:b'   'c'
IFS=:
printf '<%s>\n' $@
printf '[%s]\n' "$@"

Будет вывод:

<a>
<b c>
[a:b c]

Ksh88 изменил это так, что $@и $*были объединены с первым символом $IFS. "$@"в контексте списка разделяет позиционные параметры, за исключением случаев, когда $IFSпусто.

Когда $IFSпусто, $*соединяются через пробел, за исключением $*в кавычках, которые соединяются без разделителя.

Примеры:

$ set a b
$ IFS=:
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a:b>
<a:b>
<a:b>
<a:b>
<a>
<b>
<a>
<b>
<a>
<b>
<a:b>
$ IFS=
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a b>
<a b>
<a b>
<ab>
<a b>
<a b>
<a b>
<ab>

Вы увидите множество вариаций в различных оболочках типа Bourne/Korn -, включая ksh93 и ksh88. Есть также некоторые вариации в таких случаях, как:

set --
cmd ''"$@"
cmd $empty"$@"

Или когда $IFSсодержит многобайтовые -символы или байты, не образующие допустимых символов.


Однако

¹ в yash"$array"ведет себя как "${array[@]}", а zsh"$array"ведет себя как "${array[*]}".Менее уродливый/неуклюжий, хотя, может быть, более удивительный.

2
27.01.2020, 20:19

Теги

Похожие вопросы