Предположим, в bash некоторая функция генерирует простой массив неизвестной длины. Массив будет преобразован и выведен на экран: каждая строка с маленьким индексным номером рядом с ней, чтобы выбрать из нее. Места мало, представление должно быть безупречным, набор инструментов... эм, без излишеств, я бы сказал.
Как я могу узнатьдлина строкиизиндексный номер сам по себе(для форматирования) что не является значением массива. Например, если массив содержит 334 345 элементов (последний элемент которого вы бы вывели с помощью echo ${hugearray[334344]}
), то это 6
было бы то, что я ищу.
ncurses
Мне приходится оставаться в bash, потому что в целевой системе нет tput или других "затейливых" форматирующих утилит. Только сам bash.
${#WORD[@]}
Дает мне общее количество предметов, из которого я составил простую конструкцию, чтобы получить его:
$〉pkglist+=($(brew list -1)) # ← I'm testing on a Mac.
$〉printf "%s\n" ${pkglist[@]}
automake
…
xquartz
$〉echo ${#pkglist[@]}
68
$〉indexlength=${#pkglist[@]}
$〉echo ${#indexlength}
2
Это работает, но не совсем элегантно.В свою защиту скажу, что я не разработчик.но я надеялся, что уже есть лучший, более простой способ сделать это, вложенные косвенные обращения или какое-то другое вуду bash. Нообычные вещикоторый действует на фактические данные, и так достаточно запутан, я подумал, что лучше спросить, прежде чем сожалеть о чем-то (целевая система имеет ТОЛЬКО учетную запись root). Есть?
Спасибо!
решение1
Если вы используете MacOS, у вас должен быть доступ к zsh, поэтому вам не придется использовать bash.
В zsh длина массива такая же, $#array
как и в csh, и вы можете вкладывать операторы раскрытия параметров, поэтому вы можете использовать ${#${#array}}
¹, чтобы получить длину десятичного представления длины массива.
$ a=(qwe asdasd qewqw)
$ () {printf "%${#${#a}}d %s\n" "${@:^a}"} {1..$#a}
1 qwe
2 asdasd
3 qweqe
$ a=( {a..m} )
$ () {printf "%${#${#a}}d %s\n" "${@:^a}}" {1..$#a}
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
10 j
11 k
12 l
13 m
Обратите внимание, что все array=(...)
, array+=(...)
, {x..y}
были скопированы bash из zsh (часто косвенно через ksh). {1..$expansion}
работает в zsh или ksh, но не в bash.
${a:^b}
Оператор сжатия массива и анонимные () {code} args
функции по-прежнему специфичны для zsh.
Если вам приходится использовать bash, то, скорее всего, лучше всего будет сделать это в два шага, как вы это делаете.
Это можно сделать в одном расширении с помощью $(((l=${#array[@]}),SHLVL[l=\${#l}],l))
, полагаясь на отложенную оценку индексов массива, но это не улучшит читаемость и может оказаться неэффективным в будущем.
Также обратите внимание, что в bash (как и в ksh, чью структуру массивов bash, к сожалению, скопировал) массивы на самом деле не являются массивами, а больше похожи на ассоциативные массивы с ключами, являющимися положительными целыми числами и начинающимися с 0 (а не 1 как в большинстве других оболочек)
bash-5.1$ a=( [5]=123 [123]=qwe [4567]=asd )
bash-5.1$ typeset -p a
declare -a a=([5]="123" [123]="qwe" [4567]="asd")
bash-5.1$ echo "${#a[@]}"
3
3
— это количество элементов в массиве, а не наибольшее значение ключа, для которого вам нужно:
bash-5.1$ keys=("${!a[@]}")
bash-5.1$ echo "${keys[@]: -1}"
4567
Итак, если вы хотите распечатать выровненные ключи и значения, вам нужно что-то вроде
printarray() {
local -n arr="$1"
local -a keys=("${!arr[@]}")
local highest_key="${keys[@]: -1}"
local key_width="${#highest_key}"
local key
for key in "${keys[@]}"; do
printf '%*d %s\n' "$key_width" "$key" "${arr[key]}"
done
}
bash-5.1$ printarray a
5 123
123 qwe
4567 asd
(не будет работать с древней версией bash, найденной на macOS; local -n
требуется bash 4.3 или новее)
¹ ${#$#array}
не работает, так как принимается за ${#${$#array}}
, длину $$
после того, как она была удалена array
с начала с помощью ${var#pattern}
оператора в стиле Корна.
решение2
Будет ли это своего рода BASH vudoo, как вы это назвали? - дополнительных переменных нет,
: $((${#pkglist[*]}-1)); echo ${#_};
но $_ может привести к проблемам отладки - длинные скрипты
решение3
РЕДАКТИРОВАТЬ:Прошу прощения; как любезно напомнил мне @ilkkachu, самый простой способ получить длину переменной — с помощью ${#variable}
, а не с помощью внешних инструментов. Пожалуйста, проигнорируйте этот ответ; я скоро его удалю. Позор мне!
Чтобы определить, сколько цифр в числе, можно использовать логарифм основания 10 числа плюс единица, а затем отбросить десятичную часть, но в двоичном калькуляторе bc
есть удобная функция для этого:
echo "length(334344)" | bc
по желанию выведет 6. Если аргумент length
не является целым числом, он подсчитает действительное количество цифр, заданных без десятичной запятой (т.е. length(3.4560)
выдаст 5).
Чтобы вставить переменную Bash, echo
используйте расширение переменной Bash внутри двойных кавычек:
x=334344
echo "length($x)" | bc
Для удобства вызова мы можем записать это как функцию:
function num_digits {
#determine hoy many digits has a number
#param number
local number="$1"
echo "length($number)" | bc
}
затем используйте его следующим образом:
lendigits=$(num_digits 334344)
или, с переменной
x=334344
lendigits=$(num_digits $x)
Примечание:Если bc
он еще не установлен в вашем дистрибутиве Linux, вы можете добавить его с помощью менеджера пакетов дистрибутива.
Заметка 2:Конечно, вы можете использовать выражение напрямую, без функции:
x=334344
lendigits=$(echo "length($x)" | bc)
но он менее читабелен, и я предпочитаю немного более длинный сценарий, в котором специальные операции идентифицируются и инкапсулируются в функцию, которую позже я могу скопировать для использования в других сценариях.