Если вы используете «стандартную» оболочку *NIX-системы, возможно, вы не знакомы с такой полезной особенностью bash как массивы. Хотя массивы в bash не так круты, как в P-языках (Perl, Python и PHP) и других языках программирования, они часто бывают полезны.
Bash-массивы имеют только численные индексы, но они не обязательны к использованию, то есть вы не должны определять значения всех индексов в явном виде. Массив целиком может быть определен путем заключения записей в круглые скобки:
arr=(Hello World)
Отдельные записи могут быть определены с помощью знакомого всем синтаксиса (от Бейсика (да простит меня Дейкстра — прим. переводчика) до Фортрана):
arr[0]=Hello
arr[1]=World
Правда, обратное выглядит сравнительно более уродливо. Если нужно обратиться к определенной записи, тогда:
echo ${arr[0]} ${arr[1]}
Из страницы man:
"Фигурные скобки нужны для предотвращения конфликтов при разворачивании полных путей к файлам."
Кроме того, доступны следующие странноватые конструкции:
${arr[*]} # Все записи в массиве
${!arr[*]}# Все индексы в массиве
${#arr[*]}# Количество записей в массиве
${#arr[0]}# Длина первой записи (нумерация с нуля)
${!arr[*]} — сравнительно новое дополнение в bash и не является частью оригинальной реализации. Следующая конструкция демонстрирует пример простого использования массива. Обратите внимание на "[index]=value", это позволяет назначить конкретное значение конкретному номеру записи.
#!/bin/bash
array=(one two three four [5]=five)
echo "Array size: ${#array[*]}" # Выводим размер массива
echo "Array items:" # Выводим записи массива
for item in ${array[*]}
do
printf " %s\n" $item
done
echo "Array indexes:" # Выводим индексы массива
for index in ${!array[*]}
do
printf " %d\n" $index
done
echo "Array items and indexes:" # Выводим записи массива с их индексами
for index in ${!array[*]}
do
printf "%4d: %s\n" $index ${array[$index]}
done
Запуск скрипта породит следующий вывод:
Array size: 5
Array items:
one
two
three
four
five
Array indexes:
0
1
2
3
5
Array items and indexes:
0: one
1: two
2: three
3: four
5: five
Обратите внимание, что символ "@" может быть использован вместо "*" в конструкциях типа {arr[*]}, результат будет одинаковым за исключением разворачивания записи в кавычках. "$*" и "$@" выведут записи в кавычках, "${arr[*]}" вернет каждую запись как одно слово, "${arr[@]}" вернет каждую запись в виде отдельных слов.
Следующий пример покажет, как кавычки и конструкции без кавычек возвращают строки (особенно важно, когда в этих строках есть пробелы):
#!/bin/bash
array=("first item" "second item" "third" "item")
echo "Number of items in original array: ${#array[*]}"
for ix in ${!array[*]}
do
printf " %s\n" "${array[$ix]}"
done
echo
arr=(${array[*]})
echo "After unquoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
printf " %s\n" "${arr[$ix]}"
done
echo
arr=("${array[*]}")
echo "After * quoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
printf " %s\n" "${arr[$ix]}"
done
echo
arr=("${array[@]}")
echo "After @ quoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
printf " %s\n" "${arr[$ix]}"
done
Вывод при запуске:
Number of items in original array: 4
first item
second item
third
item
After unquoted expansion: 6
first
item
second
item
third
item
After * quoted expansion: 1
first item second item third item
After @ quoted expansion: 4
first item
second item
third
item
edo1h
одни люди борются с башизмами, другие пишут о них статьи
deseven
Ну да, давайте скрипты писать под стандарты 30-летней давности, с кучей лишних действий и абсолютно нечитаемыми конструкциями, чтобы ни в коем случае не нарушить какую-то мифическую совместимость непонятно с чем.
edo1h
из 6 систем с linux у меня на столе bash есть на 2 (на 3 из 7, если считать android за linux-систему).
за freebsd не слежу, там уже появился bash в дефолтной установке?
можно подумать, что массивы bash — верх читаемости.
и что, кроме классического bourne shell и bash больше скриптовых языков нет?
ИМХО если потребовались массивы, то это уже повод задуматься о другом языке программирования.
P.?S. я не говорю, что башизмы — вселенское зло и их никогда нельзя использовать; это как goto и глобальные переменные — иногда делают жизнь лучше, но в целом нужно стремиться избегать.
deseven
Замените башизмы на zsh-измы, fish-измы или что вам больше нравится.
Аргументируйте.
Сравнивать массивы с goto это, конечно, мощно. Ну, можете избегать массивов и дальше, удачи вам с обработкой сотни тысяч строк в каком-нибудь однострочнике, который надо написать за пару минут.
McDuk
Например? Где необходим массив, там будет его заполнение и какая-то обработка (как минимум 2 конструкции, похожие на цикл). Однострочник такого размера выглядит достаточно неприятно (для меня), чтобы решать эту задачу другим способом.
deseven
Как по мне, так подавляющее большинство однострочников выглядит отвратительно :) Но они и не для красоты обычно пишутся, а чтобы решить какую-то конкретную проблему. Я не совсем корректно выразился, под «однострочниками» я имел в виду все, что пишется в терминале (даже если с переносами) как быстрый метод что-то сделать.
Пример, возьмем стандартную итерацию по куче строк (тут не особо важно из файла или stdin, но я для простоты беру миллион строк из файла):
Это на моей машине выполняется за 44 секунды.
Теперь используем массивы из баша:
Больше писать, но выполнение за 10 сек, выигрыш в 4 раза.
Понятно, что такой пример это сферический конь в вакууме, который меняется на банальный sed, но в реальных задачах логика бывает куда сложнее и этим уже не обойтись, массивы сильно улучшают как удобство работы с кучей данных, так и скорость. То же самое справедливо и для других башизмов.
Serge3leo
Общее время: 0m17.949s против 1m46.314s, 6 раз. Кстати, хотя общее время этого варианта такое же, как у башизнутого варианта, но по процессорному времени, POSIX вариант в 1,24 раза быстрее башизнутого.
deseven
Во-первых, эти варианты при практическом использовании не равнозначны, перенаправление всего stdout в файл ведет не только к неудобствам с диагностическими сообщениями, которые теперь придется перенаправлять вручную, но и к ненулевой возможности поиметь среди данных вывод какой-нибудь тулзы, использованной при процессинге. Я лично сталкивался с ситуацией, когда curl, используемый чтобы пнуть нужный вебхук, после истечения токена вместо пустой строки при коде 200 начинал отдавать сообщение об ошибке с другой стороны (что вполне логично, но не было предусмотрено автором скрипта). Думаю про тонны всевозможного стороннего софта, который зачастую даже не знает, что ошибки надо выводить в stderr, и упоминать не нужно.
Во-вторых, массивы это не просто красивая обертка для чтения кучи строк, они также позволяют достаточно гибко с ними работать — ссылаться на конкретный элемент по индексу или ключу, добавлять или удалять элементы в любых местах, мгновенно узнавать их количество.
P.S. Не смог повторить ситуацию, когда баш вариант сожрал больше процессора, в обоих случаях плюс-минус одно и то же, POSIX вариант незначительно медленнее.
McDuk
Равнозначны. Медленный вариант 1 000 000 раз открывает/закрывает файловый дескриптор — вот и весь секрет медленности.
Не возражаю. Я и раньше знал, что массивы в bash есть, но зачем они там нужны — неизвестно. Просто как только у меня возникает задача, в которой нужно что-то делать с массивами — желание сделать это в bash сразу пропадает;) На десктопе — есть oocalc и python, там всё это значительно легче.
deseven
Serge3leo
Который в 2...3 раза короче и шустрее вашего, поскольку не имеет хотя бы цикла.
А заодно, и подумать над вопросом: «Почему нормальные реализации массивов в POSIX awk, а так же в perl или python, входящие в стандарт Linux, выполняют эту же задачу на два порядка быстрее?»
deseven
Я же написал что пример это сферический конь в вакууме, а вы мне что пытаетесь показать, что я не додумался обойтись без цикла? Любому идиоту понятно, что для задачи «дописать в каждую строку файла текст» не нужны никакие массивы. Но вы можете продолжать и дальше спорить сами с собой.
Serge3leo
Извиняйте, но странный код глаз режет. Да ещё ж и со странным примечанием: «Больше писать, но выполнение за 10 сек, выигрыш в 4 раза.»
Впрочем, задача «дописать в каждую строку файла текст» средствами только shell имеет свой интерес. И похоже башизмы для неё совсем не полезны.
Serge3leo
P.S.
Да, забытый POSIX вариант, вероятной самый шустрый из всех чисто shell/bash вариантов: