Wie kann man ein Array in Bash zurückgeben, ohne Globals zu verwenden?


78

Ich habe eine Funktion, die ein Array erstellt, und ich möchte das Array an den Aufrufer zurückgeben:

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

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

Damit bekomme ich nur einen erweiterten String. Wie kann ich my_list "zurückgeben", ohne etwas Globales zu verwenden?

Antworten:


40

Was ist los mit Globals?

Das Zurückgeben von Arrays ist wirklich nicht praktikabel. Es gibt viele Fallstricke.

Das heißt, hier ist eine Technik, die funktioniert, wenn es in Ordnung ist, dass die Variable denselben Namen hat:

$ 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

Die declare -pBefehle (mit Ausnahme des Befehls in f()werden verwendet, um den Status des Arrays zu Demonstrationszwecken anzuzeigen. f()Sie werden als Mechanismus zum Zurückgeben des Arrays verwendet.

Wenn das Array einen anderen Namen haben soll, können Sie Folgendes tun:

$ 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 Gute Antwort, aber was sind diese Fallstricke, die Sie bei der Rückgabe eines Arrays erwähnen? Die Antwort von cdarke scheint völlig vernünftig.
Hilfsmethode

6
@OliverWeiler: Zum Beispiel glättet die Technik in cdarkes Antwort Arrays. f () { local a=($(g)); declare -p a; }; g () { local a=(a 'b c' d); echo "${a[@]}"; }; fAusgänge "deklarieren -aa = '([0] =" a "[1] =" b "[2] =" c "[3] =" d ")'". Sie werden feststellen, dass Sie anstelle von 3 Elementen jetzt 4 haben.
Bis auf weiteres angehalten.

Nach langem Ausprobieren verstehe ich endlich, was Dennis in seinem Kommentar mit "cdarkes Antwort glättet Arrays" gemeint hat. In der "${array[@]}"Syntax werden Array-Elemente entsprechend in echoAnführungszeichen gesetzt - es werden jedoch keine Anführungszeichen gedruckt. Daher echofunktioniert jede verwendete Lösung nur dann ordnungsgemäß, wenn keine Array-Elemente Leerzeichen enthalten. Ich habe Dennis 'Beispiel abstrahiert und es etwas robuster gemacht, um eine praktische, wiederverwendbare Implementierung zu erhalten .
Stephen M. Harris

Alternative Methode zur Verwendung eines anderen Variablennamens:f () { local __resultvar=$1; local _local_; _local_=(abc def); declare -p _local_ | sed "s/_local_/$__resultvar/"; }
Bpedman

Da bash nichts Fortgeschritteneres wie struct-s oder sogar object unterstützt, das ein Array einer Reihe von Elementen zurückgibt, die zusammen gehören, ist dies die einzig vernünftige Lösung, die mir in den Sinn kommt (korrigieren Sie mich, wenn ich falsch liege). Stellen Sie sich vor, Sie möchten eine Funktion create_person haben, die eine Datenstruktur zurückgeben muss, die den Namen der Person und die Adresse enthält. Wie sonst machst du das in Bash?
Rbaleksandar

45

Mit Bash Version 4.3 und höher können Sie ein nameref verwenden, sodass der Aufrufer den Array-Namen übergeben kann und der Angerufene ein nameref verwenden kann, um das benannte Array indirekt zu füllen .

#!/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

Erzeugt die Ausgabe:

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

Sie können die Funktion auch ein vorhandenes Array aktualisieren lassen:

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
}

Dies ist ein eleganterer und effizienterer Ansatz, da keine Befehlsersetzung erforderlich ist $() , um die Standardausgabe der aufgerufenen Funktion abzurufen. Es ist auch hilfreich, wenn die Funktion mehr als eine Ausgabe zurückgibt - wir können einfach so viele Namensreferenzen wie die Anzahl der Ausgaben verwenden.


Hier ist das Bash-HandbuchFolgendes sagt zu nameref:

Einer Variablen kann das Attribut nameref mithilfe der Option -n den Deklarations- oder lokalen integrierten Befehlen (siehe Bash Builtins) zugewiesen werden, um ein nameref oder einen Verweis auf eine andere Variable zu erstellen. Dadurch können Variablen indirekt manipuliert werden. Immer wenn auf die nameref-Variable verwiesen, zugewiesen, deaktiviert oder ihre Attribute geändert werden (außer das nameref-Attribut selbst zu verwenden oder zu ändern), wird die Operation tatsächlich für die Variable ausgeführt, die durch den Wert der nameref-Variablen angegeben wird. Ein nameref wird üblicherweise in Shell-Funktionen verwendet, um auf eine Variable zu verweisen, deren Name als Argument an die Funktion übergeben wird. Wenn beispielsweise ein Variablenname als erstes Argument an eine Shell-Funktion übergeben wird, wird ausgeführt

Wenn Sie -n ref = $ 1 innerhalb der Funktion deklarieren, wird eine nameref-Variablenreferenz erstellt, deren Wert der Variablenname ist, der als erstes Argument übergeben wird. Verweisende Zuweisungen und Zuweisungen sowie Änderungen an den Attributen werden als Verweise, Zuweisungen und Attributänderungen an der Variablen behandelt, deren Name als $ 1 übergeben wurde.


1
Upvoted. Beachten Sie, dass die Gefahr einer Namenskollision besteht . Beachten Sie außerdem, dass das Array , auf das verwiesen wird, immer noch global ist.
Bis auf weiteres angehalten.

Du meinst arroder my_array? Beide sind lokal für die jeweiligen Funktionen und daher außerhalb unsichtbar.
Codeforester

1
Sie haben Recht mit den lokalen Vars. Entschuldigung, ich habe vermisst, dass Sie eine Funktion für beide verwendet haben.
Bis auf weiteres angehalten.

16

Bash kann Datenstrukturen nicht als Rückgabewerte weitergeben. Ein Rückgabewert muss ein numerischer Exit-Status zwischen 0 und 255 sein. Sie können jedoch durchaus Befehls- oder Prozessersetzung verwenden, um Befehle an eine eval-Anweisung zu übergeben, wenn Sie dazu neigen.

Das ist meiner Meinung nach selten die Mühe wert. Wenn Sie Datenstrukturen in Bash weitergeben müssen, verwenden Sie eine globale Variable - dafür sind sie da. Wenn Sie dies jedoch aus irgendeinem Grund nicht tun möchten, denken Sie an Positionsparameter.

Ihr Beispiel könnte leicht umgeschrieben werden, um Positionsparameter anstelle globaler Variablen zu verwenden:

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

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

Dies alles schafft jedoch eine gewisse unnötige Komplexität. Bash-Funktionen funktionieren im Allgemeinen am besten, wenn Sie sie eher wie Prozeduren mit Nebenwirkungen behandeln und sie nacheinander aufrufen.

# 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

Wenn Sie sich nur Sorgen machen, Ihren globalen Namespace zu verschmutzen, können Sie auch die nicht festgelegte integrierte Funktion verwenden , um eine globale Variable zu entfernen, nachdem Sie damit fertig sind. Lassen Sie my_list anhand Ihres ursprünglichen Beispiels global sein (indem Sie das lokale Schlüsselwort entfernen ) und fügen Sie unset my_listes am Ende von my_algorithm hinzu , um nach sich selbst aufzuräumen.


1
Ihre erste Struktur funktioniert nur, wenn der Produzent ( create_array) den Konsumenten ( ) aufrufen kann use_array, nicht umgekehrt.
Musiphil

12

Sie waren mit Ihrer ursprünglichen Lösung nicht so weit weg. Sie hatten einige Probleme, haben ein Komma als Trennzeichen verwendet und die zurückgegebenen Elemente nicht in einer Liste erfasst. Versuchen Sie Folgendes:

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

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

In Anbetracht der Kommentare zu eingebetteten Räumen können einige Verbesserungen das Problem IFSlösen:

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
Dies gibt kein Array zurück, sondern eine Zeichenfolge, die Leerzeichen als Trennzeichen verwendet. Diese Lösung behandelt Leerzeichen in Array-Elementen falsch, sodass sie nicht zum Behandeln von z. B. einem Array von Pfaden verwendet werden kann.
Andrey Tarantsov

3
@AndreyTarantsov: Gibt create_array eine Liste wieder, da ich sie verwendet habe [@]. Wenn ich sie verwendet [*]hätte, wäre es eine einzelne Zeichenfolge gewesen (sie kann nur eine Zahl zwischen 0 und 255 zurückgeben). Innerhalb my_algorithmdes Arrays wird der Funktionsaufruf in Klammern gesetzt. In my_algorithmder Variablen resultbefindet sich also ein Array. Ich verstehe den Punkt über eingebettete Räume innerhalb von Werten, die immer Probleme verursachen.
Cdarke

Dies funktioniert einwandfrei. Ich empfehle, das oldIFS nach außen zu verschieben und ein globales für das ArrayIFS zu erstellen, das vorzugsweise nicht druckbar ist. ZB arrayIFS=$'\001'.

12

Verwenden Sie die von Matt McClure entwickelte Technik: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html

Wenn Sie globale Variablen vermeiden, können Sie die Funktion in einer Pipe verwenden. Hier ist ein Beispiel:

#!/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

Ausgabe ist:

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

2
Verwenden Sie arr+=("value")anstelle der Indizierung mit ${#arr[@]}. Sehen Sie dies aus dem Grund. Backticks sind veraltet, schwer zu lesen und schwer zu verschachteln. Verwenden Sie $()stattdessen. Ihre Funktion funktioniert nicht, wenn eine Zeichenfolge in makeJunkeine neue Zeile enthält.
Bis auf weiteres angehalten.

Meine Variation (unten) funktioniert mit mehrzeiligen Zeichenfolgen.
TomRoche

Verbesserungsvorschläge behoben. Danke
Steve Zobell

9

Eine reine, minimale und robuste Lösung, die auf dem eingebauten 'declare -p' basiert - ohne verrückte globale Variablen

Dieser Ansatz umfasst die folgenden drei Schritte:

  1. Konvertieren Sie das Array mit 'declare -p' und speichern Sie die Ausgabe in einer Variablen.
    myVar="$( declare -p myArray )"
    Die Ausgabe der declare -pAnweisung kann verwendet werden, um das Array neu zu erstellen. Zum Beispiel könnte die Ausgabe von so declare -p myVaraussehen:
    declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
  2. Verwenden Sie das integrierte Echo, um die Variable an eine Funktion zu übergeben oder von dort zurückzugeben.
    • Um Leerzeichen in Array-Feldern beim Echo der Variablen beizubehalten, wird IFS vorübergehend auf ein Steuerzeichen (z. B. eine vertikale Registerkarte) festgelegt.
    • Nur die rechte Seite der Deklarationsanweisung in der Variablen soll wiedergegeben werden - dies kann durch Parametererweiterung der Form $ {parameter # word} erreicht werden. Wie für das obige Beispiel:${myVar#*=}
  3. Erstellen Sie abschließend das Array neu, an das es übergeben wird, indem Sie die integrierten Funktionen eval und 'declare -a' verwenden.

Beispiel 1 - Ein Array von einer Funktion zurückgeben

#!/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

Ausgabe von Beispiel 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")'

Beispiel 2 - Übergeben Sie ein Array an eine Funktion

#!/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

Ausgabe von Beispiel 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")'

In Beispiel 2 sollten wir nach Möglichkeit die Verwendung von Positionsparametern bevorzugen.
mcoolive

Ich mag diesen Ansatz wirklich - er ist elegant, wenn man die Bash-Einschränkungen berücksichtigt. Hat jedoch in Beispiel 1 eine Änderung des IFS wirklich Auswirkungen? Alles ist doppelt zitiert, daher kann ich nicht sehen, wie es helfen kann. Wenn ich IFS = $ '\ n' setze und ein Array von Zeichenfolgen mit Zeilenvorschüben ausgeben kann, ist alles in Ordnung. Vermisse ich hier vielleicht einen Randfall?
bland328

4

Nützliches Beispiel: Geben Sie ein Array von der Funktion zurück

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

Hier ist eine Lösung ohne externe Array-Referenzen und ohne IFS-Manipulation:

# 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

[ Hinweis: Folgendes wurde als Bearbeitung dieser Antwort aus Gründen abgelehnt , die für mich keinen Sinn ergeben (da die Bearbeitung nicht dazu gedacht war, den Autor des Beitrags anzusprechen!), Daher nehme ich den Vorschlag an, sie separat zu machen Antworten.]

Eine einfachere Implementierung von Steve Zobells Anpassung der Technik von Matt McClure verwendet die von BastaMatt vorgeschlagene integrierte Bash (seit Version == 4 ) , um eine Darstellung eines Arrays zu erstellen, das zur Laufzeit in ein Array konvertiert werden kann. (Beachten Sie, dass sowohl undreadarray readarraymapfile derselbe Code denselben Namen haben.) Es werden weiterhin globale Zeichen vermieden (was die Verwendung der Funktion in einer Pipe ermöglicht) und es werden weiterhin böse Zeichen verarbeitet.

Einige ausführlichere (z. B. mehr Modularisierung), aber immer noch spielerische Beispiele finden Sie unter bash_pass_arrays_between_functions . Im Folgenden finden Sie einige leicht ausführbare Beispiele, die hier bereitgestellt werden, um zu vermeiden, dass Moderatoren über externe Links nachdenken.

Schneiden Sie den folgenden Block aus und fügen Sie ihn zum Erstellen in ein Bash-Terminal ein /tmp/source.sh und /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 ...

Lauf /tmp/junk1.sh : Ausgabe sollte sein

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

Hinweis lines_to_array_representation behandelt auch Leerzeilen. Versuchen Sie, den folgenden Block in Ihr Bash-Terminal einzufügen:

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 ...

Führen Sie die /tmp/junk2.sh@ -Befehlszeile aus. Ihre Ausgabe sollte meiner ähnlich sein:

`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

Ich habe verschiedene Implementierungen ausprobiert und keine Arrays mit Elementen mit Leerzeichen erhalten ... weil sie alle verwendet werden mussten echo.

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

Lösung

Dann stieß ich auf Dennis Williamsons Antwort . Ich habe seine Methode in die folgenden Funktionen integriert, damit sie a) ein beliebiges Array akzeptieren und b) zum Übergeben, Duplizieren und Anhängen von Arrays verwendet werden können.

# 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\ *=}
}  

Dann können andere Funktionen ein Array mit abfangbaren Ausgaben oder indirekten Argumenten zurückgeben.

# 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

Wenn Sie die externen vermeiden wollen verwenden sed, können Sie wahrscheinlich Bash Regex - Operator verwenden =~und ${BASH_REMATCH}an seinem Platz.
Bis auf weiteres angehalten.

@ TennisWilliamson Mir ist keine Möglichkeit bekannt, einen globalen Ersatz mit =~und durchzuführen ${BASH_REMATCH}. Das Übereinstimmungsmuster ist jedoch so einfach, dass Regex nicht einmal benötigt wird. Ich habe die Funktion aktualisiert, um stattdessen die Variablensubstitution zu verwenden sed.
Stephen M. Harris

Ich konnte diesen Code nicht bekommen, um die Arrays zu reproduzieren. Ich habe den gesamten Code kopiert und am Ende hinzugefügt: echo "$ {test1 [0]}". Die Antwort lautet ("eins" "zwei" "zweieinhalb"). Alles ist im nullten Element und Index 1 und 2 sind leer. Die gleichen Ergebnisse für test2.
Steve Zobell

Die Variablensubstitution ${r//\[[0-9]\]=}funktioniert nicht mit Arrays mit mehr als 9 Elementen (sie ersetzt nicht [10] =). Sie können extglob aktivieren und ${r//\[+([0-9])\]=}stattdessen verwenden.
Kounoupis

Dies hat seltsame Nebenwirkungen für Array-Elemente mit mehreren aufeinanderfolgenden Leerzeichen. Versuchen Sie: B=(" a " "" " " "b" " c " " d ") eval A=$(use_array B)
Kounoupis

2

Ich brauchte kürzlich eine ähnliche Funktionalität, daher ist das Folgende eine Mischung aus den Vorschlägen von RashaMatt und Steve Zobell .

  1. Echo jedes Array / Listenelement als separate Zeile innerhalb einer Funktion
  2. Verwenden Sie mapfile , um alle Array- / Listenelemente zu lesen, die von einer Funktion wiedergegeben werden.

Soweit ich sehen kann, bleiben die Zeichenfolgen intakt und die Leerzeichen bleiben erhalten.

#!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

Noch ein paar Variationen…

#!/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

Ich habe kürzlich eine Besonderheit in BASH entdeckt, dass eine Funktion direkten Zugriff auf die Variablen hat, die in den Funktionen deklariert sind, die höher im Aufrufstapel liegen. Ich habe gerade erst darüber nachgedacht, wie diese Funktion genutzt werden kann (sie verspricht sowohl Vorteile als auch Gefahren), aber eine offensichtliche Anwendung ist eine Lösung für den Geist dieses Problems.

Ich würde auch lieber einen Rückgabewert erhalten, als eine globale Variable zu verwenden, wenn ich die Erstellung eines Arrays delegiere. Es gibt mehrere Gründe für meine Präferenz, unter anderem, um zu vermeiden, dass ein bereits vorhandener Wert möglicherweise gestört wird, und um zu vermeiden, dass ein Wert zurückbleibt, der beim späteren Zugriff möglicherweise ungültig ist. Es gibt zwar Problemumgehungen für diese Probleme, aber am einfachsten ist es, wenn die Variable den Gültigkeitsbereich verlässt, wenn der Code damit fertig ist.

Meine Lösung stellt sicher, dass das Array bei Bedarf verfügbar ist und bei der Rückkehr der Funktion verworfen wird, und lässt eine globale Variable mit demselben Namen ungestört.

#!/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[@]}"

Hier ist die Ausgabe dieses Codes: Programmausgabe

Wenn die Funktion request_array Anrufe get_an_array , get_an_array kann direkt die eingestellte myArr Variable, die lokal ist request_array . Da myarr mit erstellt wird declare, ist es lokal für request_array und verlässt daher den Bereich, wenn request_array zurückkehrt.

Obwohl diese Lösung einen Wert nicht buchstäblich zurückgibt, schlage ich vor, dass sie insgesamt die Versprechen eines echten Funktionsrückgabewerts erfüllt.


1

Dies kann auch durch einfaches Übergeben einer Array-Variablen an die Funktion und Zuweisen von Array-Werten zu dieser Variable erfolgen. Verwenden Sie dann diese Variable außerhalb der Funktion. Zum Beispiel.

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

Der einfachste Weg, den ich gefunden habe

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

result=($(my_function))

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

Dies läuft /bin/bashnicht/bin/sh
CallMarl

0

Wenn Ihre Quelldaten mit jedem Listenelement in einer separaten Zeile formatiert sind, ist die integrierte mapfileMethode eine einfache und elegante Möglichkeit, eine Liste in ein Array einzulesen:

$ 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")'

Beachten Sie, dass Sie wie beim readeingebauten System normalerweise nicht * mapfilein einer Pipeline (oder Subshell) verwenden würden, da die zugewiesene Array-Variable für nachfolgende Anweisungen nicht verfügbar wäre (* es sei denn, die Bash-Jobsteuerung ist deaktiviert und shopt -s lastpipefestgelegt).

$ 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.

Dies ist eine sehr gut gemachte Antwort ... auf eine andere Frage. Leider stellt diese Frage ausdrücklich ‚Wie zurückkehren ein Array‘ (emph hinzugefügt) nicht ‚ wie eine Liste in ein Array lesen‘. Vielleicht gibt es noch eine andere Frage, die dies tatsächlich beantwortet?
TomRoche

0

Ich würde vorschlagen, an einen Codeblock weiterzuleiten, um die Werte eines Arrays festzulegen. Die Strategie ist POSIX-kompatibel, sodass Sie sowohl Bash als auch Zsh erhalten und nicht das Risiko von Nebenwirkungen wie bei den veröffentlichten Lösungen eingehen.

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

Dies funktioniert für zshund bashund wird nicht durch Leerzeichen oder Sonderzeichen beeinflusst. Im Fall des OP wird die Ausgabe durch Echo transformiert, sodass ein Array nicht ausgegeben, sondern gedruckt wird (wie in anderen Shell-Funktionen erwähnt, werden Status und keine Werte zurückgegeben). Wir können es in einen Pipeline-fähigen Mechanismus ändern:

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 }
}

Wenn dies der Fall ist, könnte man den create_arrayPipeline-Prozess entfernen my_algorithmund die beiden Funktionen miteinander verketten

create_array | my_algorithm

0

Sie können die declare -pMethode auch einfacher verwenden, indem Sie die declare -adoppelte Auswertung nutzen, wenn der Wert eine Zeichenfolge ist (keine echten Parens außerhalb der Zeichenfolge):

# 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

Überprüfung des Ergebnisses declare -p dstergibtdeclare -a dst=([0]="one" [2]="two three")" , was zeigt, dass diese Methode sowohl spärliche Arrays als auch Einträge mit einem IFS-Zeichen (Leerzeichen) korrekt behandelt.

Das erste ist, das Quell-Array zu dehydrieren, indem declare -peine gültige Bash-Deklaration generiert wird. Da es sich bei der Deklaration um eine vollständige Anweisung handelt, einschließlich "deklarieren" und des Variablennamens, entfernen wir diesen Teil von vorne mit ${Result#*=}und lassen die Klammern mit den Indizes und Werten darin : ([0]="one" [2]="two three").

Anschließend wird das Array rehydriert, indem dieser Wert Ihrer eigenen Deklarationsanweisung zugeführt wird, in der Sie den Namen des Arrays auswählen. Es beruht auf der Tatsache, dass die rechte Seite der dstArray-Deklaration eine Zeichenfolge mit Klammern innerhalb der Zeichenfolge ist und keine echten Klammern in der Deklaration selbst, z declare -a dst=( "true parens outside string" ). B. nicht . Dies löst aus declare, dass die Zeichenfolge zweimal ausgewertet wird, einmal in eine gültige Anweisung mit Klammern (und Anführungszeichen im beibehaltenen Wert) und eine weitere für die tatsächliche Zuweisung. Dh es wertet zuerst declare -a dst=([0]="one" [2]="two three"), dann auswertet , dass als Aussage.

Beachten Sie, dass dieses doppelte Bewertungsverhalten spezifisch für das -aund ist-A Optionen von declare.

Oh, und diese Methode funktioniert auch mit assoziativen Arrays. Wechseln Sie einfach -azu-A .

Da diese Methode auf stdout basiert, funktioniert sie wie Pipelines über Subshell-Grenzen hinweg wie Pipelines.

Ich diskutiere diese Methode ausführlicher in meinem Blog-Beitrag


-1

Sie können dies versuchen

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

Die Ausgabe ist

1st one
2nd two
3rd three
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.