Преобразование массива Bash в строку с разделителями
Я хотел бы знать следующее;
- Почему данный нерабочий пример не работает.
- Если есть какие-либо другие более чистые методы, чем те, которые приведены в рабочем примере.
Нерабочий пример
> ids=(1 2 3 4);echo ${ids[*]// /|}
1 2 3 4
> ids=(1 2 3 4);echo ${${ids[*]}// /|}
-bash: ${${ids[*]}// /|}: bad substitution
> ids=(1 2 3 4);echo ${"${ids[*]}"// /|}
-bash: ${"${ids[*]}"// /|}: bad substitution
Рабочий пример
> ids=(1 2 3 4);id="${ids[@]}";echo ${id// /|}
1|2|3|4
> ids=(1 2 3 4); lst=$( IFS='|'; echo "${ids[*]}" ); echo $lst
1|2|3|4
В контексте строка с разделителями, которая будет использоваться в команде sed для дальнейшего анализа.
3 ответа
# REVISION: 2017-03-14
# Use of read and other bash specific features (bashisms)
Поскольку круглые скобки используются для разделения массива, а не строки:
ids="1 2 3 4";echo ${ids// /|}
1|2|3|4
Некоторые образцы: заселение $ids
с двумя строками: a b
а также c d
ids=("a b" "c d")
echo ${ids[*]// /|}
a|b c|d
IFS='|';echo "${ids[*]}";IFS=$' \t\n'
a b|c d
... и наконец:
IFS='|';echo "${ids[*]// /|}";IFS=$' \t\n'
a|b|c|d
Где собран массив, разделенный 1-м символом $IFS
, но с заменой пробела на |
в каждом элементе массива.
Когда вы делаете:
id="${ids[@]}"
вы переносите строку сборки из слияния массива ids
пробелом в новую переменную типа string.
Примечание: когда "${ids[@]}"
дать строку через пробел, "${ids[*]}"
(со звездой *
вместо знака @
) отобразит строку, разделенную первым символом $IFS
,
какие man bash
говорит:
man -Len -Pcol\ -b bash | sed -ne '/^ *IFS /{N;N;p;q}'
IFS The Internal Field Separator that is used for word splitting
after expansion and to split lines into words with the read
builtin command. The default value is ``<space><tab><newline>''.
Играть с $IFS
:
set | grep ^IFS=
IFS=$' \t\n'
declare -p IFS
declare -- IFS="
"
printf "%q\n" "$IFS"
$' \t\n'
Буквально space
, tabulation
и (имеется в виду или) line-feed
, Итак, пока первый символ - это пробел. использование *
будет делать так же, как @
,
Но:
{
# OIFS="$IFS"
# IFS=$': \t\n'
# unset array
# declare -a array=($(echo root:x:0:0:root:/root:/bin/bash))
IFS=: read -a array < <(echo root:x:0:0:root:/root:/bin/bash)
echo 1 "${array[@]}"
echo 2 "${array[*]}"
OIFS="$IFS" IFS=:
echo 3 "${array[@]}"
echo 4 "${array[*]}"
IFS="$OIFS"
}
1 root x 0 0 root /root /bin/bash
2 root x 0 0 root /root /bin/bash
3 root x 0 0 root /root /bin/bash
4 root:x:0:0:root:/root:/bin/bash
Примечание: линия IFS=: read -a array < <(...)
буду использовать :
в качестве разделителя, без настройки $IFS
постоянно. Это потому что выходная строка #2
представить пространства в качестве разделителей.
Ты можешь использовать printf
Кроме того, без каких-либо внешних команд или необходимости манипулировать IFS:
ids=(1 2 3 4) # create array
printf -v ids_d '|%s' "${ids[@]}" # yields "|1|2|3|4"
ids_d=${ids_d:1} # remove the leading '|'
Ваш первый вопрос уже рассматривается в ответе Ф. Хаури. Вот канонический способ объединения элементов массива:
ids=( 1 2 3 4 )
IFS=\| eval 'lst="${ids[*]}"'
Некоторые люди будут кричать, что eval
это зло, но это совершенно безопасно здесь, благодаря одинарным кавычкам. Это имеет только преимущества: нет подоболочек, IFS
не изменяется глобально, он не обрезает завершающие символы новой строки, и это очень просто.
Служебная функция для объединения массива аргументов в строку с разделителями:
#!/usr/bin/env bash
# Join arguments with delimiter
# @Params
# $1: The delimiter string
# ${@:2}: The arguments to join
# @Output
# >&1: The arguments separated by the delimiter string
array::join() {
(($#)) || return 1 # At least delimiter required
local -- delim="$1" str IFS=
shift
str="${*/#/$delim}" # Expand arguments with prefixed delimiter (Empty IFS)
echo "${str:${#delim}}" # Echo without first delimiter
}
declare -a my_array=( 'Paris' 'Berlin' 'London' 'Brussel' 'Madrid' 'Oslo' )
array::join ', ' "${my_array[@]}"
array::join '*' {1..9} | bc # 1*2*3*4*5*6*7*8*9=362880 Factorial 9
declare -a null_array=()
array::join '== Ultimate separator of nothing ==' "${null_array[@]}"
Выход:
Paris, Berlin, London, Brussel, Madrid, Oslo
362880