LINUX.ORG.RU

Самый «правильный» join на BASH?

 


3

2

Довольно часто возникает задача вывести все элементы массива, вставив кастомный разделитель.

Решение в духе:

del=':'
join=${arr[@]}
join=${join//[[:space:]]/$del}

- работает только применительно к массивам, в которых почему-либо (внезапно!) элементы не содержат пробелов.

Решение с перебором элементов циклом:

del=':'
unset join
for ((i=0; i<${#arr[@]}; i++)); do
 join+=${join:+$del}${arr[i]}
done
- после Perl видится громоздким.

Может, я что-то упустил и есть некий аналог IFS для разделения элементов при выводе массивов операцией ${arr[@]} ??

★★★★★

в которых почему-либо (внезапно!) элементы не содержат пробелов.
join=${join//[[:space:]]/$del}
[[:space:]]

Удивительно, да.

есть некий аналог IFS для разделения элементов при выводе массивов операцией ${arr[@]} ??

Есть IFS.

$ ARR=("aaa" "bbb" "ccc ddd")
$ OLDIFS="$IFS" ; IFS=":" ; echo "${ARR[*]}" ; IFS="$OLDIFS"
aaa:bbb:ccc ddd

Kroz ★★★★★
()
Последнее исправление: Kroz (всего исправлений: 1)
$ a[0]=AA
$ a[1]=BB
$ a[2]=CC
$ (IFS=,; echo "${a[*]}")
AA,BB,CC
joy4eg ★★★★★
()

Гм, чего? Кроме ${array[@]} есть ещё и ${array[*]}.

$ array=('first entry' 'second entry' 'third entry')

$ printf "entry is '%s'\n" "${array[@]}"
entry is 'first entry'
entry is 'second entry'
entry is 'third entry'

$ printf "entry is '%s'\n" "${array[*]}"
entry is 'first entry second entry third entry'

$ IFS=:; echo "${array[*]}"; unset IFS
first entry:second entry:third entry
intelfx ★★★★★
()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от Kroz

Понятно. Косяк был в том, что я насиловал IFS, пытаясь получить содержимое массива как ${arr[@]}, а надо было «со звёздочкой»:

arr=(a b c 'd e')
sIFS=$IFS; IFS=$'\x0a'
out=${arr[*]}
IFS=$sIFS
echo "$out"
DRVTiny ★★★★★
() автор топика

Возможно стоит просто перестать писать на палках и костылях.

anonymous
()
Ответ на: комментарий от d_a

Можно же сначала вставить IFS'ом один символ (что-нибудь «редкое», гарантированно не встречающееся внутри элементов вектора), а потом - заменить этот символ на всё, что угодно ${var//c/vse_chto_ugodno} :)

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от d_a

Я такую функцию наваял в итоге нашей дискуссии (здесь конечно только один символ может быть разделителем) :

join () {
 local del=${1:0:1}
 [[ $del ]] || return 1
 shift
 IFS="$del"
 source <(
        cat <<SOURCE
 echo "\${$1[*]}"
SOURCE
 ) 
 unset IFS
}

Соответcтвенно, пользоваться ею можно так, например:

csvLine=$(join ',' csvCols)

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от d_a

По-моему подход с printf неправильный из-за того, что элементы массива могут содержать пробелы... Хотя... если с изменением IFS сочетать... Сейчас попробую.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

Так работает, но я не понимаю абсолютно, как printf понимает, где заканчивается одно %s и начинается другое!

declare -a arr=('a b c' 'd e f' 'g h i')
printf -v outLine '%s:::' "${arr[@]}"
echo "$outLine"

Мне всегда казалось, что ${arr[@]} должен раскрываться/интерполироваться ДО того, как он станет аргументом printf'а, а магия здесь, наверное, в том, что printf - это не внешняя команда.

DRVTiny ★★★★★
() автор топика
Последнее исправление: DRVTiny (всего исправлений: 1)
Ответ на: комментарий от DRVTiny

По-моему подход с printf неправильный из-за того, что элементы массива могут содержать пробелы...

Чой-та он вдруг неправильный. Я для отладки держу в .bashrc функцию

args() {
    printf "%d args:" $#
    printf " <%s>" "$@"
    echo
}

чтобы смотреть на сколько фактических аргументов рассыпалось моё экранирование, вот так вот печатает:
$ args 1\ 2 3\ 4
2 args: <1 2> <3 4>

Это и есть почти "нормальный" изкоробочный join, но он оставит разделитель перед первым или после последнего элемента, что неприятно, но решаемо.

d_a ★★★★★
()
Ответ на: комментарий от DRVTiny

Мне всегда казалось, что ${arr[@]} должен раскрываться/интерполироваться ДО того, как он станет аргументом printf'а

${arr[@]} и раскрывается ДО:

$ args printf -v outLine '%s:::' "${arr[@]}"
7 args: <printf> <-v> <outLine> <%s:::> <a b c> <d e f> <g h i>

printf в цикле по своим позиционным аргументам проходит и применяет форматную строку к каждому.

d_a ★★★★★
()
Ответ на: комментарий от d_a

В таком случае «${arr[@]}» должен быть одной строкой с точки зрения printf, и как раз то, что один аргумент с точки зрения любой внешней команды выглядит как 3 аргумента с точки зрения printf - говорит о том, что prtinf использует «внутреннюю кухню BASH».

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

В таком случае «${arr[@]}» должен быть одной строкой с точки зрения printf, и как раз то, что один аргумент с точки зрения любой внешней команды выглядит как 3 аргумента с точки зрения printf - говорит о том, что prtinf использует «внутреннюю кухню BASH».

Нет, неправильно. Положим arr=('a b c' 'd e f' 'g h i'), тогда:


  • "${arr[@]}" — это три аргумента всегда (в отличие от perl/php/whatever),
  • "${arr[*]}" — это один аргумент,
  • ${arr[@]}, ${arr[*]} — оба по девять,


потому что это правила подстановки для массивов, а не некая аромагия printf. Убедиться можно, например, с помощью команды /bin/touch (создаёт по файлу на каждый переданный аргумент), сомнений в его честности быть не должно:

$ /bin/touch "${arr[@]}"
$ ls -l
итого 0
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:40 a b c
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:40 d e f
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:40 g h i
$ rm ./*
$ /bin/touch "${arr[*]}"
$ ls -l
итого 0
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:40 a b c d e f g h i
$ rm ./*
$ /bin/touch ${arr[@]}
$ ls -l
итого 0
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 a
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 b
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 c
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 d
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 e
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 f
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 g
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 h
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:41 i
$ rm ./*
$ /bin/touch ${arr[*]}
$ ls -l
итого 0
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 a
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 b
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 c
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 d
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 e
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 f
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 g
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 h
-rw-rw-r--. 1 d_a d_a 0 дек 10 18:42 i


// пост написал вчера, но отправить не смог, так как лор постоянно лежит, как ни зайдёшь.

d_a ★★★★★
()
Ответ на: комментарий от d_a

Теперь понял! Век живи - век учись. Видимо, логика такая, что в «${arr[@]}» каждый элемент массива как бы утаскивает эти кавычки себе, так что элементы с пробелами оказываются аккуратно «окавычены» и touch «${arr[@]}» создаёт каждый элемент отдельно. А без кавычек содержимое массива раскрывается в одну строку, так что пробелы не экранируются.

Спасибо большое за пояснение, очень полезно и толково!

DRVTiny ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.