Строка ZSH 5.05 разделена на ноль

Как отмечено в этом вопросе SO, ZSH сжимает соседние разделители в строке разделен по умолчанию. Теперь в ZSH 5.05 прилагаемое исправление не работает.

Hexagon% string="1::3"
Hexagon% setopt interactive_comments
Hexagon% a=("${(s/:/)string}") # notice the quotes
Hexagon% 
Hexagon% echo $a[1] # 1, good
1
Hexagon% echo $a[2] # nothing, good
3
Hexagon% echo $a[3] # 3, good

Как видите, мой результат не совпадает с записанным на предыдущем ZSH. Как я могу эмулировать эту функциональность, и, что еще лучше, есть ли портативный способ сделать это?


person PythonNut    schedule 17.07.2014    source источник


Ответы (2)


Попробуйте следующее в zsh:

IFS=: read -r -A a <<<"$string"
  • -A считывает входные данные в массив, в данном случае с именем a.
  • IFS=: (локализовано для команды) определяет : как разделитель для разделения входных данных на поля — несколько смежных разделителей считаются заключающими пустые поля, поэтому $a[2] в итоге оказывается пустым.

Сделать это переносимо непросто:

Вот решение, которое работает на zsh, bash и ksh, однако оно НЕ совместимо с POSIX (см. ниже):

# Determine the shell-specific option character for `read`;
# [-]A for ksh and zsh, [-]a for bash:
readIntoArrayOptChar='A' && [[ -n $BASH_VERSION ]] && readIntoArrayOptChar='a'

# Read the input string into array `a`, splitting into elements by ':'
IFS=: read -r -$readIntoArrayOptChar a <<<"$string"

# Determine the shell-specific start and end indices for printing
# the array elements in a loop:
# bash and ksh are 0-based, zsh is 1-based (by default).
startNdx=0 endNdx=${#a[@]}
[[ -n $ZSH_VERSION ]] && (( ++startNdx, ++endNdx ))

# Print array elements individually.
# Note how the element references are enclosed in {...} for cross-shell 
# compatibility.
for (( i = startNdx; i < endNdx; i++ )); do
  echo "el. $i: ["${a[i]}"]" # -> e.g., in zsh, 1st iteration: 'el. 1: [1]'
done

Примечание:

  • subprocess.call() Python с параметрами shell = True и os.system() вызывает sh по умолчанию, где вы можете полагаться только на функции POSIX (и, если платформа не похожа на Unix, даже на это).
  • Вышеприведенное НЕ совместимо с POSIX, в первую очередь потому, что переменные массива не являются частью спецификации языка оболочки POSIX, как указывает @rici.
  • @rici также указывает на заметное отсутствие в zsh конструкции bash/ksh ${!a[@]} для получения списка индексов массива (a).
person mklement0    schedule 17.07.2014
comment
Большое спасибо! Я полагаю, у меня есть еще одна деталь, read является встроенной оболочкой, и поэтому процесс не запускается, верно? Этот код используется для некоторых довольно тяжелых вычислений, и указание процесса при каждом разделении может замедлить его на порядок. - person PythonNut; 17.07.2014
comment
@PythonNut: Да, read является встроенным во всех 3-х оболочках (bash, ksh, zsh), поэтому никакой дополнительный процесс не запускается, но, конечно, сама оболочка это процесс. Как вы вызываете оболочку? Обратите внимание, что модуль Python subprocess с параметром shell = True и модуль os с system() вызывают sh по умолчанию, где вы можете полагаться только на функции POSIX (и, если платформа не похожа на Unix, даже на эту ). - person mklement0; 17.07.2014
comment
@mklement0: printf %s\\n "${a[@]}" работает с {ba,k,z}sh, но включить индекс элемента не так просто. paste <(printf %s\\n "${!a[@]}") <(printf %s\\n "${a[@]}") работает только на {ba,k}sh; zsh "${(k)a[@]}" работает, только если a является ассоциативным. Но я не вижу ничего плохого в использовании флага (s/:/) для разделения. - person rici; 17.07.2014
comment
@rici: в версиях zsh 5.0.2 и 5.0.5 a=("${(s/:/)string}") создает только элементы массива 2, потому что прогоны смежных экземпляров :, по-видимому, рассматриваются как одиночный разделитель, чего не делает OP. не хочу. (ОП предполагает, что предыдущие версии zsh вели себя по-другому - я не изучал это.) - person mklement0; 17.07.2014
comment
@rici: Спасибо, что указали на различия в получении индексов массива. Мой ответ демонстрирует использование явных числовых индексов путем корректировки базового индекса в соответствии с оболочкой. Эту разницу можно сгладить с помощью emulate sh в zsh (очевидно, с другими побочными эффектами), однако это не решает проблему "${!a[@]}". Опять же, я даже не понимаю, каковы требования к переносимости. Если требуется строгое соответствие POSIX, мой ответ все равно не сработает. - person mklement0; 17.07.2014
comment
@ mklement0: массив слов исключает строгое соответствие POSIX. Я прочитал вопрос как относящийся к zsh, так как это слово встречается в вопросе четыре раза, включая заголовок. (Не знаю, зачем здесь тег bash, хотя иначе я бы не увидел вопроса.) Конечно, всегда интересно сравнить решения в других оболочках. - person rici; 17.07.2014
comment
@rici: Ну, есть еще is there a portable way to do so? :) Хорошо, что переменные массива не являются частью спецификации оболочки POSIX. Знаете ли вы zsh, эквивалентный "${!a[@]}? - person mklement0; 17.07.2014
comment
@mklement0: единственный известный мне эквивалент - это флаг (k), но, как я уже сказал, он работает только с ассоциативными массивами, поэтому здесь он не очень полезен. Вы можете использовать что-то вроде cat -n - person rici; 17.07.2014
comment
@rici: Спасибо; любопытно, что (k) возвращает значения с обычными массивами. cat -n удобен для создания списков индексов для непрерывных массивов, начинающихся с 1, но не для разреженных. Фактически, я даже не смог вручную воспроизвести поведение ${!a[@]} (который для разреженных массивов в bash/ksh возвращает только индексы определенных элементов): в то время как zsh по крайней мере позволяет вам нужно найти максимальный индекс с помощью $#a (обратите внимание, как это отличается от bash/ksh), он не реализует настоящие разреженные массивы: все элементы определены неявно, поэтому тест вроде [[ -z ${a[i]+defined} ]] не работает. - person mklement0; 17.07.2014
comment
@ mklement0: насколько я знаю, это правильно. Если вам нужен разреженный массив в zsh, вам нужно использовать ассоциативный массив. Однако, опять же, не существует встроенного способа заполнения ассоциативного массива из разделения. Так что, вероятно, это все, что нужно для более конкретного вопроса с тегом zsh. (Не все элементы определены: только те, которые находятся в индексе диапазона.) - person rici; 17.07.2014
comment
@rici Да, я повел нас по касательной; Спасибо, что пришли. Спасибо за пояснение относительно всех элементов - иначе говоря: все элементы между 1 (при условии, что базовый индекс по умолчанию равен 1) и $#a (т.е. самый высокий индекс, когда-либо присвоенный) - определены неявно. Тангент уволен. :) - person mklement0; 17.07.2014
comment
Спасибо вам обоим. Это был очень поучительный разговор. - person PythonNut; 18.07.2014
comment
@PythonNut: с удовольствием; теперь мы знаем, что по крайней мере 3 человек нашли это интересным. :) - person mklement0; 18.07.2014

В zsh версии 5.0.2 (и предположительно выше) вы можете сохранить пустые строки в разделении, добавив флаг @:

a=("${(@s/:/)string}")
person rici    schedule 17.07.2014
comment
+1; полезно знать - определенно лучшее решение zsh (хотя и не переносимое). - person mklement0; 17.07.2014