Як повернути масив у bash без використання глобалів?


82

У мене є функція, яка створює масив, і я хочу повернути масив абоненту:

create_array() {
  local my_list=("a", "b", "c")
  echo "${my_list[@]}"
}

my_algorithm() {
  local result=$(create_array)
}

Завдяки цьому я отримую лише розширений рядок. Як я можу "повернути" my_list, не використовуючи нічого глобального?

Відповіді:


40

Що не так із глобалами?

Повернення масивів насправді не є практичним. Тут є багато підводних каменів.

Тим не менш, ось одна техніка, яка працює, якщо нормально, що змінна має одне і те ж ім’я:

$ f () { local a; a=(abc 'def ghi' jkl); declare -p a; }
$ g () { local a; eval $(f); declare -p a; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

Ці declare -pкоманди (за винятком одного в f()використовується для відображення стану масиву для демонстраційних цілей. У f()ньому використовуються в якості механізму для повернення масиву.

Якщо вам потрібно, щоб масив мав іншу назву, ви можете зробити щось подібне:

$ g () { local b r; r=$(f); r="declare -a b=${r#*=}"; eval "$r"; declare -p a; declare -p b; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

-bash: declare: a: not found
declare -a b='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

7
+1 Гарна відповідь, але про які підводні камені ви говорите, повертаючи масив? Відповідь cdarke видається цілком обґрунтованою.
helpermethod

6
@OliverWeiler: Наприклад, техніка у відповіді cdarke вирівнює масиви. f () { local a=($(g)); declare -p a; }; g () { local a=(a 'b c' d); echo "${a[@]}"; }; fвиводить "declare -aa = '([0] =" a "[1] =" b "[2] =" c "[3] =" d ")'". Ви помітите, що замість 3 елементів у вас тепер 4.
Денніс Вільямсон,

1
Після довгих спроб і помилок я нарешті розумію, що Денніс мав на увазі у своєму коментарі "відповідь cdarke вирівнює масиви". "${array[@]}"Синтаксис буде процитувати елементи масиву відповідним чином --- але echoне друкується неекрановані лапки. Отже, будь-яке рішення, яке використовує echo, працюватиме належним чином, лише якщо жоден елемент масиву не містить пробілів. Я абстрагував приклад Денніса і зробив його трохи надійнішим, щоб отримати практичну, багаторазову реалізацію .
Stephen M. Harris

Альтернативний метод використання іншого імені змінної:f () { local __resultvar=$1; local _local_; _local_=(abc def); declare -p _local_ | sed "s/_local_/$__resultvar/"; }
bpedman

Оскільки bash не підтримує нічого більш просунутого, як struct-s або навіть об'єкт, що повертає масив з набору елементів, що належать разом, є єдиним гідним рішенням, яке мені спадає на думку (виправте мене, якщо я помиляюся). Уявіть, що ви хочете мати функцію create_person, яка повинна повертати структуру даних, що містить адресу імені людини. Як інакше ви це зробите в bash?
rbaleksandar

50

У версії Bash 4.3 та новіших версіях ви можете використовувати nameref, щоб абонент міг передати ім'я масиву, а викликана сторона може використовувати nameref для заповнення іменованого масиву опосередковано .

#!/usr/bin/env bash

create_array() {
    local -n arr=$1             # use nameref for indirection
    arr=(one "two three" four)
}

use_array() {
    local my_array
    create_array my_array       # call function to populate the array
    echo "inside use_array"
    declare -p my_array         # test the array
}

use_array                       # call the main function

Виводить результат:

inside use_array
declare -a my_array=([0]="one" [1]="two three" [2]="four")

Ви можете змусити функцію також оновити існуючий масив:

update_array() {
    local -n arr=$1             # use nameref for indirection
    arr+=("two three" four)     # update the array
}

use_array() {
    local my_array=(one)
    update_array my_array       # call function to update the array
}

Це більш елегантний та ефективний підхід, оскільки нам не потрібна заміна команди, $() щоб отримати стандартний вивід функції, що викликається. Це також допомагає, якщо функція повертає більше одного виводу - ми можемо просто використовувати стільки виправлень імен, скільки кількість виходів.


Ось що в Посібнику Bash сказано про nameref:

Змінній можна призначити атрибут nameref, використовуючи опцію -n для оголошення або локальних вбудованих команд (див. Bash Builtins), щоб створити nameref або посилання на іншу змінну. Це дозволяє побічно маніпулювати змінними. Кожного разу, коли на змінну nameref посилаються, присвоюють, скасовують або змінюють її атрибути (крім використання або зміни самого атрибута nameref), операція фактично виконується над змінною, зазначеною значенням змінної nameref. Nameref зазвичай використовується в функціях оболонки для посилання на змінну, ім'я якої передається як аргумент функції. Наприклад, якщо ім'я змінної передається функції оболонки як перший аргумент, що працює

оголосити -n ref = $ 1 всередині функції створює змінну nameref ref, значенням якої є ім'я змінної, передане як перший аргумент. Посилання та призначення на ref та зміни його атрибутів розглядаються як посилання, призначення та модифікації атрибутів змінної, ім'я якої передано як $ 1.


1
Прихильний. Зверніть увагу, що існує можливість зіткнення імен . Також зауважте, що посиланий масив все ще є глобальним.
Денніс Вільямсон

Ви маєте на увазі arrчи my_array? Обидва вони є локальними для відповідних функцій і, отже, невидимі зовні.
codeforester

1
Ви маєте рацію щодо місцевих варів. Вибачте, я пропустив, що ви використовували функцію для обох.
Денніс Вільямсон

17

Bash не може передавати структури даних як значення, що повертаються. Повернене значення має мати числовий статус виходу від 0 до 255. Однак ви можете, звичайно, використовувати заміну команд або процесів для передачі команд оператору eval, якщо ви так схильні.

Імхо, це рідко варте клопоту. Якщо вам потрібно передавати структури даних у Bash, використовуйте глобальну змінну - для цього вони призначені. Якщо ви не хочете цього робити з якихось причин, подумайте з точки зору позиційних параметрів.

Ваш приклад можна легко переписати, використовуючи позиційні параметри замість глобальних змінних:

use_array () {
    for idx in "$@"; do
        echo "$idx"
    done
}

create_array () {
    local array=("a" "b" "c")
    use_array "${array[@]}"
}

Однак все це створює певну непотрібну складність. Функції Bash, як правило, працюють найкраще, коли ви ставитеся до них більше як до процедур з побічними ефектами і викликаєте їх послідовно.

# Gather values and store them in FOO.
get_values_for_array () { :; }

# Do something with the values in FOO.
process_global_array_variable () { :; }

# Call your functions.
get_values_for_array
process_global_array_variable

Якщо все, що вас турбує, це забруднення вашого глобального простору імен, ви також можете використовувати вбудований unset, щоб видалити глобальну змінну після того, як закінчите з нею. На вашому оригінальному прикладі нехай my_list буде глобальним (видаливши локальне ключове слово) і додайте unset my_listв кінець my_algorithm, щоб очистити після себе.


1
Ваша перша структура працює лише в тому випадку, якщо виробник ( create_array) може зателефонувати споживачеві ( use_array), а не навпаки.
musiphil

13

Ви не були настільки далекі від свого оригінального рішення. У вас було кілька проблем, ви використовували кому як роздільник, і вам не вдалося зафіксувати повернуті елементи у списку, спробуйте наступне:

my_algorithm() {
  local result=( $(create_array) )
}

create_array() {
  local my_list=("a" "b" "c")  
  echo "${my_list[@]}" 
}

Беручи до уваги коментарі щодо вбудованих просторів, кілька налаштувань IFSможуть вирішити це:

my_algorithm() {
  oldIFS="$IFS"
  IFS=','
  local result=( $(create_array) )
  IFS="$oldIFS"
  echo "Should be 'c d': ${result[1]}"
}

create_array() {
  IFS=','
  local my_list=("a b" "c d" "e f") 
  echo "${my_list[*]}" 
}

4
Це не повертає масив, воно повертає рядок, використовуючи пробіл як роздільник. Це рішення неправильно обробляє пробіли в елементах масиву, тому його не можна використовувати для обробки, наприклад, масиву шляхів.
Андрій Таранцов

3
@AndreyTarantsov: create_array повторить список, оскільки я використовував [@], якщо б використовував, [*]то це був би один рядок (він не може повернути нічого, крім числа між 0-255). Усередині my_algorithmмасив створюється шляхом вкладання виклику функції в дужки. Отже, у my_algorithmзмінній resultє масив. Я приймаю думку про вбудовані пробіли у значеннях, які завжди викликають проблеми.
cdarke

Це працює нормально, я рекомендую перенести oldIFS у глобальний зовнішній регіон та створити глобальний для масивуIFS, встановлений, бажано, для друку. Напр arrayIFS=$'\001'.

12

Використовуйте техніку, розроблену Меттом Макклюром: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html

Уникнення глобальних змінних означає, що ви можете використовувати функцію в каналі. Ось приклад:

#!/bin/bash

makeJunk()
{
   echo 'this is junk'
   echo '#more junk and "b@d" characters!'
   echo '!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'"'"
}

processJunk()
{
    local -a arr=()    
    # read each input and add it to arr
    while read -r line
    do 
       arr+=('"'"$line"'" is junk') 
    done;

    # output the array as a string in the "declare" representation
    declare -p arr | sed -e 's/^declare -a [^=]*=//'
}

# processJunk returns the array in a flattened string ready for "declare"
# Note that because of the pipe processJunk cannot return anything using
# a global variable
returned_string="$(makeJunk | processJunk)"

# convert the returned string to an array named returned_array
# declare correctly manages spaces and bad characters
eval "declare -a returned_array=${returned_string}"

for junk in "${returned_array[@]}"
do
   echo "$junk"
done

Вихід:

"this is junk" is junk
"#more junk and "b@d" characters!" is junk
"!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'" is junk

2
Використовуйте arr+=("value")замість індексації за допомогою ${#arr[@]}. Перегляньте це з причини. Зворотні посилання застаріли, їх важко читати та важко вкладати. Використовуйте $()замість цього. Ваша функція не працює, якщо рядок у makeJunkмістить новий рядок.
Денніс Вільямсон

Мій варіант (нижче) робить роботу з багаторядкових рядків.
TomRoche

Виправлено запропоновані вдосконалення. Дякую
Стів Зобелл

9

Чисте Bash, мінімальне та надійне рішення, засноване на вбудованому декларуванні -p - без божевільних глобальних змінних

Цей підхід передбачає такі три кроки:

  1. Перетворіть масив на 'declare -p' і збережіть результат у змінній.
    myVar="$( declare -p myArray )"
    Вихідні дані declare -pоператора можуть бути використані для відтворення масиву. Наприклад, результат declare -p myVarможе виглядати так:
    declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
  2. Використовуйте вбудоване ехо, щоб передати змінну функції або повернути її звідти.
    • Для того, щоб зберегти пробіли в полях масиву під час повторення змінної, IFS тимчасово встановлюється на керуючий символ (наприклад, вертикальна вкладка).
    • Повторюється лише правий бік оператора оголошення у змінній - цього можна досягти розширенням параметра у формі $ {параметр # слово}. Що стосується наведеного вище прикладу:${myVar#*=}
  3. Нарешті, відтворіть масив, куди він передається, використовуючи вбудовані eval та 'declare -a'.

Приклад 1 - повернення масиву з функції

#!/bin/bash

# Example 1 - return an array from a function

function my-fun () {
 # set up a new array with 3 fields - note the whitespaces in the
 # 2nd (2 spaces) and 3rd (2 tabs) field
 local myFunArray=( "1st field" "2nd  field" "3rd       field" )

 # show its contents on stderr (must not be output to stdout!)
 echo "now in $FUNCNAME () - showing contents of myFunArray" >&2
 echo "by the help of the 'declare -p' builtin:" >&2
 declare -p myFunArray >&2

 # return the array
 local myVar="$( declare -p myFunArray )"
 local IFS=$'\v';
 echo "${myVar#*=}"

 # if the function would continue at this point, then IFS should be
 # restored to its default value: <space><tab><newline>
 IFS=' '$'\t'$'\n';
}

# main

# call the function and recreate the array that was originally
# set up in the function
eval declare -a myMainArray="$( my-fun )"

# show the array contents
echo ""
echo "now in main part of the script - showing contents of myMainArray"
echo "by the help of the 'declare -p' builtin:"
declare -p myMainArray

# end-of-file

Результат прикладу 1:

now in my-fun () - showing contents of myFunArray
by the help of the 'declare -p' builtin:
declare -a myFunArray='([0]="1st field" [1]="2nd  field" [2]="3rd       field")'

now in main part of the script - showing contents of myMainArray
by the help of the 'declare -p' builtin:
declare -a myMainArray='([0]="1st field" [1]="2nd  field" [2]="3rd      field")'

Приклад 2 - передати масив функції

#!/bin/bash

# Example 2 - pass an array to a function

function my-fun () {
 # recreate the array that was originally set up in the main part of
 # the script
 eval declare -a myFunArray="$( echo "$1" )"

 # note that myFunArray is local - from the bash(1) man page: when used
 # in a function, declare makes each name local, as with the local
 # command, unless the ‘-g’ option is used.

 # IFS has been changed in the main part of this script - now that we
 # have recreated the array it's better to restore it to the its (local)
 # default value: <space><tab><newline>
 local IFS=' '$'\t'$'\n';

 # show contents of the array
 echo ""
 echo "now in $FUNCNAME () - showing contents of myFunArray"
 echo "by the help of the 'declare -p' builtin:"
 declare -p myFunArray
}

# main

# set up a new array with 3 fields - note the whitespaces in the
# 2nd (2 spaces) and 3rd (2 tabs) field
myMainArray=( "1st field" "2nd  field" "3rd     field" )

# show the array contents
echo "now in the main part of the script - showing contents of myMainArray"
echo "by the help of the 'declare -p' builtin:"
declare -p myMainArray

# call the function and pass the array to it
myVar="$( declare -p myMainArray )"
IFS=$'\v';
my-fun $( echo "${myVar#*=}" )

# if the script would continue at this point, then IFS should be restored
# to its default value: <space><tab><newline>
IFS=' '$'\t'$'\n';

# end-of-file

Результат прикладу 2:

now in the main part of the script - showing contents of myMainArray
by the help of the 'declare -p' builtin:
declare -a myMainArray='([0]="1st field" [1]="2nd  field" [2]="3rd      field")'

now in my-fun () - showing contents of myFunArray
by the help of the 'declare -p' builtin:
declare -a myFunArray='([0]="1st field" [1]="2nd  field" [2]="3rd       field")'

У прикладі 2, я думаю, нам слід віддавати перевагу використанню позиційних параметрів, коли це можливо.
mcoolive

Мені дуже подобається такий підхід - він елегантний, враховуючи обмеженість. Однак, повторно в прикладі 1, це зміна МФС дійсно мають ніякого ефекту? Все подвійне цитування, тому я не бачу, як це може допомогти. Коли я встановлюю IFS = $ '\ n' і видаю масив рядків, що містять канали рядків, все виходить нормально. Мені, можливо, тут не вистачає крайової справи?
bland328

4

Корисний приклад: повернення масиву з функції

function Query() {
  local _tmp=`echo -n "$*" | mysql 2>> zz.err`;
  echo -e "$_tmp";
}

function StrToArray() {
  IFS=$'\t'; set $1; for item; do echo $item; done; IFS=$oIFS;
}

sql="SELECT codi, bloc, requisit FROM requisits ORDER BY codi";
qry=$(Query $sql0);
IFS=$'\n';
for row in $qry; do
  r=( $(StrToArray $row) );
  echo ${r[0]} - ${r[1]} - ${r[2]};
done

3

Ось рішення без посилань на зовнішні масиви та без маніпуляцій з IFS:

# add one level of single quotes to args, eval to remove
squote () {
    local a=("$@")
    a=("${a[@]//\'/\'\\\'\'}")   # "'" => "'\''"
    a=("${a[@]/#/\'}")           # add "'" prefix to each word
    a=("${a[@]/%/\'}")           # add "'" suffix to each word
    echo "${a[@]}"
}

create_array () {
    local my_list=(a "b 'c'" "\\\"d
")
    squote "${my_list[@]}"
}

my_algorithm () {
    eval "local result=($(create_array))"
    # result=([0]="a" [1]="b 'c'" [2]=$'\\"d\n')
}

3

[ Примітка: наступне було відхилено як редагування цієї відповіді з причин, які для мене не мають сенсу (оскільки редакція не була призначена для звернення до автора публікації!), Тому я приймаю пропозицію зробити її окремою відповідь.]

Більш проста реалізація адаптації Стіва Зобелла методики Метта МакКлура використовує вбудований bash (починаючи з версії == 4 ), readarray як пропонується RastaMatt, для створення представлення масиву, який може бути перетворений в масив під час виконання. (Зверніть увагу на те, що обидва readarrayі mapfileназивають один і той самий код.) Він все одно уникає глобалів (що дозволяє використовувати функцію в трубі), і все ще обробляє неприємні символи.

Деякі більш розроблені (наприклад, більша модуляризація), але все ще певні приклади іграшок, див. Bash_pass_arrays_between_functions . Нижче наведено декілька легко виконуваних прикладів, наведених тут, щоб уникнути модераторів, які б не стосувались зовнішніх посилань.

Виріжте наступний блок і вставте його в термінал bash, щоб створити /tmp/source.shта /tmp/junk1.sh:

FP='/tmp/source.sh'     # path to file to be created for `source`ing
cat << 'EOF' > "${FP}"  # suppress interpretation of variables in heredoc
function make_junk {
   echo 'this is junk'
   echo '#more junk and "b@d" characters!'
   echo '!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'"'"
}

### Use 'readarray' (aka 'mapfile', bash built-in) to read lines into an array.
### Handles blank lines, whitespace and even nastier characters.
function lines_to_array_representation {
    local -a arr=()
    readarray -t arr
    # output array as string using 'declare's representation (minus header)
    declare -p arr | sed -e 's/^declare -a [^=]*=//'
}
EOF

FP1='/tmp/junk1.sh'      # path to script to run
cat << 'EOF' > "${FP1}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash

source '/tmp/source.sh'  # to reuse its functions

returned_string="$(make_junk | lines_to_array_representation)"
eval "declare -a returned_array=${returned_string}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}"
done
EOF
chmod u+x "${FP1}"
# newline here ... just hit Enter ...

Виконати /tmp/junk1.sh: вихід повинен бути

this is junk
#more junk and "b@d" characters!
!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'

Примітка lines_to_array_representationтакож обробляє порожні рядки. Спробуйте вставити наступний блок у ваш термінал bash:

FP2='/tmp/junk2.sh'      # path to script to run
cat << 'EOF' > "${FP2}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash

source '/tmp/source.sh'  # to reuse its functions

echo '`bash --version` the normal way:'
echo '--------------------------------'
bash --version
echo # newline

echo '`bash --version` via `lines_to_array_representation`:'
echo '-----------------------------------------------------'
bash_version="$(bash --version | lines_to_array_representation)"
eval "declare -a returned_array=${bash_version}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}"
done
echo # newline

echo 'But are they *really* the same? Ask `diff`:'
echo '-------------------------------------------'

echo 'You already know how to capture normal output (from `bash --version`):'
declare -r PATH_TO_NORMAL_OUTPUT="$(mktemp)"
bash --version > "${PATH_TO_NORMAL_OUTPUT}"
echo "normal output captured to file @ ${PATH_TO_NORMAL_OUTPUT}"
ls -al "${PATH_TO_NORMAL_OUTPUT}"
echo # newline

echo 'Capturing L2AR takes a bit more work, but is not onerous.'
echo "Look @ contents of the file you're about to run to see how it's done."

declare -r RAW_L2AR_OUTPUT="$(bash --version | lines_to_array_representation)"
declare -r PATH_TO_COOKED_L2AR_OUTPUT="$(mktemp)"
eval "declare -a returned_array=${RAW_L2AR_OUTPUT}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}" >> "${PATH_TO_COOKED_L2AR_OUTPUT}"
done
echo "output from lines_to_array_representation captured to file @ ${PATH_TO_COOKED_L2AR_OUTPUT}"
ls -al "${PATH_TO_COOKED_L2AR_OUTPUT}"
echo # newline

echo 'So are they really the same? Per'
echo "\`diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l\`"
diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l
echo '... they are the same!'
EOF
chmod u+x "${FP2}"
# newline here ... just hit Enter ...

Запустіть /tmp/junk2.sh@ commandline. Ваш результат повинен бути подібним до мого:

`bash --version` the normal way:
--------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

`bash --version` via `lines_to_array_representation`:
-----------------------------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

But are they *really* the same? Ask `diff`:
-------------------------------------------
You already know how to capture normal output (from `bash --version`):
normal output captured to file @ /tmp/tmp.Ni1bgyPPEw
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.Ni1bgyPPEw

Capturing L2AR takes a bit more work, but is not onerous.
Look @ contents of the file you're about to run to see how it's done.
output from lines_to_array_representation captured to file @ /tmp/tmp.1D6O2vckGz
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.1D6O2vckGz

So are they really the same? Per
`diff -uwB /tmp/tmp.Ni1bgyPPEw /tmp/tmp.1D6O2vckGz | wc -l`
0
... they are the same!

2

Я пробував різні реалізації, і жоден з збережених масивів, в яких були елементи з пробілами ... тому що всі вони мали використовувати echo.

# These implementations only work if no array items contain spaces.
use_array() {  eval echo  '(' \"\${${1}\[\@\]}\" ')';  }
use_array() {  local _array="${1}[@]"; echo '(' "${!_array}" ')';  }

Рішення

Тоді я натрапив на відповідь Денніса Вільямсона . Я включив його метод у наступні функції, щоб вони могли: а) приймати довільний масив і б) використовуватися для передачі, дублювання та додавання масивів.

# Print array definition to use with assignments, for loops, etc.
#   varname: the name of an array variable.
use_array() {
    local r=$( declare -p $1 )
    r=${r#declare\ -a\ *=}
    # Strip keys so printed definition will be a simple list (like when using
    # "${array[@]}").  One side effect of having keys in the definition is 
    # that when appending arrays (i.e. `a1+=$( use_array a2 )`), values at
    # matching indices merge instead of pushing all items onto array.
    echo ${r//\[[0-9]\]=}
}
# Same as use_array() but preserves keys.
use_array_assoc() {
    local r=$( declare -p $1 )
    echo ${r#declare\ -a\ *=}
}  

Потім інші функції можуть повертати масив, використовуючи вловлюваний вихід або непрямі аргументи.

# catchable output
return_array_by_printing() {
    local returnme=( "one" "two" "two and a half" )
    use_array returnme
}
eval test1=$( return_array_by_printing )

# indirect argument
return_array_to_referenced_variable() {
    local returnme=( "one" "two" "two and a half" )
    eval $1=$( use_array returnme )
}
return_array_to_referenced_variable test2

# Now both test1 and test2 are arrays with three elements

Якщо ви хочете уникнути використання зовнішнього sed, ви, ймовірно, можете скористатися оператором збігів регулярних виразів Баша =~і ${BASH_REMATCH}замість нього.
Денніс Вільямсон

@DennisWilliamson Я не знаю жодного способу зробити глобальну заміну за допомогою =~та ${BASH_REMATCH}. Але шаблон збігу досить простий, тому регулярний вираз навіть не потрібен; Я оновив функцію, використовуючи замість змінних замість sed.
Stephen M. Harris

Я не зміг отримати цей код для відтворення масивів. Я скопіював весь код і додав це наприкінці: echo "$ {test1 [0]}". Відповідь така ("один" "два" "два з половиною"). Все в нульовому елементі, а індекси 1 і 2 порожні. Такі ж результати для тесту2.
Steve Zobell

Заміна змінної ${r//\[[0-9]\]=}не працюватиме з масивами, що мають більше 9 елементів (вона не замінить [10] =). Ви можете увімкнути extglob і використовувати ${r//\[+([0-9])\]=}замість цього.
kounoupis

Це має дивні побічні ефекти для елементів масиву з кількома послідовними пробілами. Спробуйте: B=(" a " "" " " "b" " c " " d ") eval A=$(use_array B)
kounoupis

2

Нещодавно мені потрібна була подібна функціональність, тому нижче подається поєднання пропозицій, зроблених RashaMatt та Steve Zobell .

  1. повторити кожен елемент масиву / списку як окремий рядок із функції
  2. використовуйте mapfile для читання всіх елементів масиву / списку, що відображаються функцією.

Наскільки я бачу, рядки зберігаються цілими, а пробіли зберігаються.

#!bin/bash

function create-array() {
  local somearray=("aaa" "bbb ccc" "d" "e f g h")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}

mapfile -t resa <<< "$(create-array)"

# quick output check
declare -p resa

Ще кілька варіацій ...

#!/bin/bash

function create-array-from-ls() {
  local somearray=("$(ls -1)")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}

function create-array-from-args() {
  local somearray=("$@")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}


mapfile -t resb <<< "$(create-array-from-ls)"
mapfile -t resc <<< "$(create-array-from-args 'xxx' 'yy zz' 't s u' )"

sentenceA="create array from this sentence"
sentenceB="keep this sentence"

mapfile -t resd <<< "$(create-array-from-args ${sentenceA} )"
mapfile -t rese <<< "$(create-array-from-args "$sentenceB" )"
mapfile -t resf <<< "$(create-array-from-args "$sentenceB" "and" "this words" )"

# quick output check
declare -p resb
declare -p resc
declare -p resd
declare -p rese
declare -p resf

2

Нещодавно я виявив химерність у BASH у тому, що функція має прямий доступ до змінних, оголошених у функціях вище у стеці викликів. Я тільки що почав обмірковувати, як використовувати цю функцію (вона обіцяє як вигоди, так і небезпеки), але одне очевидне застосування - це вирішення духу цієї проблеми.

Я також вважаю за краще отримати повернене значення, а не використовувати глобальну змінну при делегуванні створення масиву. Є кілька причин мого уподобання, серед яких уникнення можливого порушення попередньо існуючого значення та уникнення залишення значення, яке може бути недійсним при подальшому доступі. Незважаючи на те, що ці способи вирішення цих проблем є найпростішими, коли змінна виходить за межі обсягу, коли код закінчений з нею.

Моє рішення гарантує, що масив буде доступний, коли це потрібно, і буде відкинутий, коли функція повернеться, і залишить без порушення глобальну змінну з тим самим іменем.

#!/bin/bash

myarr=(global array elements)

get_an_array()
{
   myarr=( $( date +"%Y %m %d" ) )
}

request_array()
{
   declare -a myarr
   get_an_array "myarr"
   echo "New contents of local variable myarr:"
   printf "%s\n" "${myarr[@]}"
}

echo "Original contents of global variable myarr:"
printf "%s\n" "${myarr[@]}"
echo

request_array 

echo
echo "Confirm the global myarr was not touched:"
printf "%s\n" "${myarr[@]}"

Ось результат цього коду: вихід програми

Коли функція request_array викликає get_an_array , get_an_array може безпосередньо встановити локальну змінну myarr для request_array . Оскільки myarr створюється за допомогою declare, місцевим є request_array і, таким чином, виходить за межі обсягу, коли request_array повертається.

Хоча це рішення буквально не повертає значення, я вважаю, що в цілому воно задовольняє обіцянки істинної функції, що повертає значення.


1

Це також можна зробити, просто передавши функцію змінної масиву та призначивши значення масиву цій змінній, а потім використовуючи цю змінну поза функцією. Наприклад.

create_array() {
  local  __resultArgArray=$1
  local my_list=("a" "b" "c")
  eval $__resultArgArray="("${my_list[@]}")"
}

my_algorithm() {
  create_array result
  echo "Total elements in the array: ${#result[@]}"
  for i in "${result[@]}"
  do
    echo $i
  done
}

my_algorithm

1

Найлегший спосіб y знайшов

my_function()
{
    array=(one two three)
    echo ${array[@]}
}

result=($(my_function))

echo ${result[0]}
echo ${result[1]}
echo ${result[2]}

Це працює через те, що /bin/bashні/bin/sh
CallMarl

0

Якщо вихідні дані відформатовані з кожним елементом списку в окремому рядку, то mapfileвбудований - це простий та елегантний спосіб читати список у масив:

$ list=$(ls -1 /usr/local)           # one item per line

$ mapfile -t arrayVar <<<"$list"     # -t trims trailing newlines

$ declare -p arrayVar | sed 's#\[#\n[#g'
declare -a arrayVar='(
[0]="bin"
[1]="etc"
[2]="games"
[3]="include"
[4]="lib"
[5]="man"
[6]="sbin"
[7]="share"
[8]="src")'

Зауважте, що, як і readвбудований, ви зазвичай не будете * використовувати mapfileв конвеєрі (або підкорінці), оскільки призначена змінна масиву буде недоступною для наступних операторів (*, якщо керування роботою bash не вимкнено і shopt -s lastpipeне встановлено).

$ help mapfile
mapfile: mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
    Read lines from the standard input into an indexed array variable.

    Read lines from the standard input into the indexed array variable ARRAY, or
    from file descriptor FD if the -u option is supplied.  The variable MAPFILE
    is the default ARRAY.

    Options:
      -n count  Copy at most COUNT lines.  If COUNT is 0, all lines are copied.
      -O origin Begin assigning to ARRAY at index ORIGIN.  The default index is 0.
      -s count  Discard the first COUNT lines read.
      -t                Remove a trailing newline from each line read.
      -u fd             Read lines from file descriptor FD instead of the standard input.
      -C callback       Evaluate CALLBACK each time QUANTUM lines are read.
      -c quantum        Specify the number of lines read between each call to CALLBACK.

    Arguments:
      ARRAY             Array variable name to use for file data.

    If -C is supplied without -c, the default quantum is 5000.  When
    CALLBACK is evaluated, it is supplied the index of the next array
    element to be assigned and the line to be assigned to that element
    as additional arguments.

    If not supplied with an explicit origin, mapfile will clear ARRAY before
    assigning to it.

    Exit Status:
    Returns success unless an invalid option is given or ARRAY is readonly or
    not an indexed array.

Це дуже гарно зроблена відповідь ... на інше питання. На жаль, це питання прямо задає питання "Як повернути масив" (додано emph), а не "як прочитати список у масиві". Можливо, є інше питання, на яке це насправді відповідає?
TomRoche

0

Я б запропонував підвести трубопровід до блоку коду, щоб встановити значення масиву. Стратегія сумісна з POSIX, тому ви отримуєте як Bash, так і Zsh, і не ризикуєте побічними ефектами, як опубліковані рішення.

i=0                   # index for our new array
declare -a arr        # our new array

# pipe from a function that produces output by line
ls -l | { while read data; do i=$i+1; arr[$i]="$data"; done }

# example of reading that new array
for row in "${arr[@]}"; do echo "$row"; done

Це буде працювати для zshі bash, і на нього не впливатимуть пробіли або спеціальні символи. У випадку з OP вихідні дані трансформуються за допомогою echo, тому насправді він не виводить масив, а друкує його (як інші згадані функції оболонки повертають статус, а не значення). Ми можемо змінити його на готовий конвеєрний механізм:

create_array() {
  local my_list=("a", "b", "c")
  for row in "${my_list[@]}"; do
    echo "$row"
  done
}

my_algorithm() {
  i=0
  declare -a result
  create_array | { while read data; do i=$i+1; result[$i]="$data"; done }
}

Якщо так схилятись, можна було б видалити create_arrayпроцес конвеєру my_algorithmта об’єднати дві функції між собою

create_array | my_algorithm

0

Ви також можете скористатися declare -pметодом легше, скориставшись declare -aподвійною оцінкою, коли значенням є рядок (за межами рядка немає справжніх парен):

# return_array_value returns the value of array whose name is passed in.
#   It turns the array into a declaration statement, then echos the value
#   part of that statement with parentheses intact.  You can use that
#   result in a "declare -a" statement to create your own array with the
#   same value.  Also works for associative arrays with "declare -A".
return_array_value () {
  declare Array_name=$1  # namespace locals with caps to prevent name collision
  declare Result

  Result=$(declare -p $Array_name)  # dehydrate the array into a declaration
  echo "${Result#*=}"               # trim "declare -a ...=" from the front
}

# now use it.  test for robustness by skipping an index and putting a
# space in an entry.
declare -a src=([0]=one [2]="two three")
declare -a dst="$(return_array_value src)"    # rehydrate with double-eval

declare -p dst
> declare -a dst=([0]="one" [2]="two three")  # result matches original

Перевірка результату declare -p dstдає declare -a dst=([0]="one" [2]="two three")", демонструючи, що цей метод коректно обробляє як розріджені масиви, так і записи з символом IFS (пробіл).

Перше, що потрібно, це зневоднення вихідного масиву, використовуючи declare -pдля його створення дійсну декларацію bash. Оскільки декларація є повне заяву, в тому числі «оголосити» і ім'я змінної, ми знімаємо цю частину з передньої сторони ${Result#*=}, залишаючи круглі дужки з індексами і значеннями всередині: ([0]="one" [2]="two three").

Потім він регідратує масив, подаючи це значення у ваш власний оператор оголошення, де ви вибираєте ім'я масиву. Він покладається на той факт, що права частина dstоголошення масиву - це рядок із дужками, що знаходяться всередині рядка, а не справжні дужки у самому оголошенні, наприклад, ні declare -a dst=( "true parens outside string" ) . Це запускає declareобчислення рядка двічі, один раз у дійсний оператор із дужками (і лапки у збереженому значенні), і інший для фактичного призначення. Тобто він спочатку оцінює declare -a dst=([0]="one" [2]="two three"), а потім оцінює це як твердження.

Зверніть увагу, що ця поведінка подвійного оцінювання є специфічною для параметрів -aі -Aоголошень.

О, і цей метод працює і з асоціативними масивами, просто змініть -aна -A.

Оскільки цей метод покладається на stdout, він працює за межами оболонки, як конвеєри, як зазначали інші.

Я детальніше обговорюю цей метод у своєму дописі в блозі


-1

Ви можете спробувати це

my_algorithm() {
  create_array list
  for element in "${list[@]}"
  do
    echo "${element}"
  done
}

create_array() {
  local my_list=("1st one" "2nd two" "3rd three")

  eval "${1}=()"
  for element in "${my_list[@]}"
  do
    eval "${1}+=(\"${element}\")"
  done
}

my_algorithm

Вихідний результат

1st one
2nd two
3rd three
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.