it-swarm.com.de

Wie kann ich Elemente eines Arrays in Bash verbinden?

Wenn ich ein Array wie dieses in Bash habe:

FOO=( a b c )

Wie füge ich die Elemente mit Kommas zusammen? Zum Beispiel, a,b,c erzeugen.

335
David Wolever

Umschreiblösung von Pascal Pilz als Funktion in 100% pure Bash (keine externen Befehle):

function join_by { local IFS="$1"; shift; echo "$*"; }

Zum Beispiel,

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c

Alternativ können Sie printf verwenden, um Trennzeichen mit mehreren Zeichen zu unterstützen, indem Sie die Idee von @gniourf_gniourf verwenden

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

Zum Beispiel,

join_by , a b c #a,b,c
join_by ' , ' a b c #a , b , c
join_by ')|(' a b c #a)|(b)|(c
join_by ' %s ' a b c #a %s b %s c
join_by $'\n' a b c #a<newline>b<newline>c
join_by - a b c #a-b-c
join_by '\' a b c #a\b\c
438

Noch eine andere Lösung:

#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}

echo $bar

Edit: Gleiches aber für mehrstelliges Trennzeichen für variable Länge:

#!/bin/bash
separator=")|(" # e.g. constructing regex, pray it does not contain %s
foo=('foo bar' 'foo baz' 'bar baz')
regex="$( printf "${separator}%s" "${foo[@]}" )"
regex="${regex:${#separator}}" # remove leading separator
echo "${regex}"
# Prints: foo bar)|(foo baz)|(bar baz
192
doesn't matters
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
112
Pascal Pilz

Vielleicht, z.B.

SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"

echo "$FOOJOIN"
64
martin clayton

Überraschenderweise ist meine Lösung noch nicht gegeben :) Dies ist der einfachste Weg für mich. Es braucht keine Funktion:

IFS=, eval 'joined="${foo[*]}"'

Hinweis: Diese Lösung funktioniert im Nicht-POSIX-Modus gut. Im POSIX-Modus sind die Elemente immer noch ordnungsgemäß verbunden, IFS=, wird jedoch dauerhaft.

24
konsolebox

Hier ist eine 100% reine Bash-Funktion, die die Aufgabe erfüllt:

join() {
    # $1 is return variable name
    # $2 is sep
    # $3... are the elements to join
    local retname=$1 sep=$2 ret=$3
    shift 3 || shift $(($#))
    printf -v "$retname" "%s" "$ret${@/#/$sep}"
}

Aussehen:

$ a=( one two "three three" four five )
$ join joineda " and " "${a[@]}"
$ echo "$joineda"
one and two and three three and four and five
$ join joinedb randomsep "only one element"
$ echo "$joinedb"
only one element
$ join joinedc randomsep
$ echo "$joinedc"

$ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' )
$ join joineda $'a sep with\nnewlines\n' "${a[@]}"
$ echo "$joineda"
 stuff with
newlines
a sep with
newlines
and trailing newlines


$

Dadurch bleiben auch die nachfolgenden Zeilenumbrüche erhalten, und es ist keine Subshell erforderlich, um das Ergebnis der Funktion zu erhalten. Wenn Sie den printf -v nicht mögen (warum sollten Sie ihn nicht mögen?) Und einen Variablennamen übergeben, können Sie natürlich eine globale Variable für den zurückgegebenen String verwenden:

join() {
    # $1 is sep
    # $2... are the elements to join
    # return is in global variable join_ret
    local sep=$1 IFS=
    join_ret=$2
    shift 2 || shift $(($#))
    join_ret+="${*/#/$sep}"
}
19
gniourf_gniourf

Ich würde das Array als String wiederholen, dann die Leerzeichen in Zeilenvorschub umwandeln und dann paste verwenden, um alles wie folgt in einer Zeile zu verbinden:

tr " " "\n" <<< "$FOO" | paste -sd , -

Ergebnisse:

a,b,c

Dies scheint mir das schnellste und sauberste zu sein!

11
Yanick Girouard

Bei der Wiederverwendung von @ zählt die Lösung nicht, sondern mit einer one-Anweisung, indem die Substition $ {: 1} vermieden und eine Zwischenvariable benötigt wird.

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )

printf hat 'Die Formatzeichenfolge wird so oft wiederverwendet, dass die Argumente erfüllt werden.' in seinen Manpages, so dass die Verkettungen der Strings dokumentiert werden. Dann besteht der Trick darin, die LIST-Länge zu verwenden, um den letzten Sperator zu hacken, da der Schnitt nur die Länge der LIST beibehält, wenn die Felder zählen.

8
Valise

Verwenden Sie keine externen Befehle:

$ FOO=( a b c )     # initialize the array
$ BAR=${FOO[@]}     # create a space delimited string from array
$ BAZ=${BAR// /,}   # use parameter expansion to substitute spaces with comma
$ echo $BAZ
a,b,c

Achtung, es wird davon ausgegangen, dass Elemente keine Leerzeichen enthalten.

7
Nil Geisweiller
s=$(IFS=, eval 'echo "${FOO[*]}"')
7
eel ghEEz
$ set a 'b c' d

$ history -p "[email protected]" | paste -sd,
a,b c,d
4
Steven Penny

printf-Lösung, die Trennzeichen beliebiger Länge akzeptiert (basierend auf @ zählt die Antwort nicht)

#/!bin/bash
foo=('foo bar' 'foo baz' 'bar baz')

sep=',' # can be of any length
bar=$(printf "${sep}%s" "${foo[@]}")
bar=${bar:${#sep}}

echo $bar
4
Riccardo Galli

Dies unterscheidet sich nicht nur von vorhandenen Lösungen, aber es wird keine separate Funktion verwendet, IFS wird in der übergeordneten Shell nicht geändert und steht in einer einzigen Zeile:

arr=(a b c)
printf '%s\n' "$(IFS=,; echo "${arr[*]}")"

ergebend

a,b,c

Einschränkung: Das Trennzeichen darf nicht länger als ein Zeichen sein.

4
Benjamin W.

Kürzere Version der Top-Antwort:

joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }

Verwendungszweck:

joinStrings "$myDelimiter" "${myArray[@]}"
4
Camilo Martin

Kombiniere das Beste aller Welten mit folgender Idee.

# join with separator
join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }

Dieses kleine Meisterwerk ist

  • 100% pure bash (Parametererweiterung mit IFS vorübergehend nicht gesetzt, keine externen Aufrufe, keine printf ...)
  • kompakt, vollständig und fehlerlos (arbeitet mit Ein- und Mehrzeichenbegrenzern, arbeitet mit Begrenzern, die Leerzeichen, Zeilenumbrüche und andere Sonderzeichen der Shell enthalten, arbeitet mit leeren Trennzeichen)
  • effizient (keine Subshell, keine Array-Kopie)
  • einfach und dumm und bis zu einem gewissen Grad auch schön und lehrreich

Beispiele:

$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'\n' a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C
3
guest

Mein Versuch.

$ array=(one two "three four" five)
$ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")"
one SEP two SEP three four SEP five
1
Ben Davis

Vielen Dank @gniourf_gniourf für ausführliche Kommentare zu meiner Kombination der besten Welten. Bitte entschuldigen Sie, dass der Buchungscode nicht sorgfältig erstellt und getestet wurde. Hier ist ein besserer Versuch. 

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }

Diese Schönheit von Konzeption ist

  • (immer noch) 100% pure bash (vielen Dank für den ausdrücklichen Hinweis, dass printf auch eingebaut ist. Ich war mir vorher nicht dessen bewusst ...)
  • arbeitet mit Trennzeichen mit mehreren Zeichen
  • kompakter und vollständiger und diesmal sorgfältig durchdacht und langfristig unter anderem mit zufälligen Teilzeichenfolgen aus Shell-Skripts getestet, wobei die Verwendung von Shell-Sonderzeichen oder Steuerzeichen oder keine Zeichen in den Trennzeichen und/oder Parametern sowie Edge-Fälle behandelt werden und Eckfälle und andere Probleme wie überhaupt keine Argumente. Das garantiert nicht, dass es keinen Fehler mehr gibt, aber es wird eine etwas schwierigere Herausforderung sein, einen zu finden. Übrigens, selbst die aktuell am besten bewerteten Antworten und verwandten Probleme leiden unter solchen Dingen wie ...

Zusätzliche Beispiele:

$ join_ws '' a b c
abc
$ join_ws ':' {1,7}{A..C}
1A:1B:1C:7A:7B:7C
$ join_ws -e -e
-e
$ join_ws $'\033[F' $'\n\n\n'  1.  2.  3.  $'\n\n\n\n'
3.
2.
1.
$ join_ws $ 
$
1
guest

Die Verwendung der variablen Indirektion zum direkten Verweisen auf ein Array funktioniert ebenfalls. Es können auch benannte Referenzen verwendet werden, die jedoch erst in 4.3 verfügbar waren.

Die Verwendung dieser Form einer Funktion hat den Vorteil, dass das Trennzeichen optional sein kann (standardmäßig wird das erste Zeichen der Standardvariable IFS verwendet, das ein Leerzeichen ist; wenn Sie möchten, kann es eine leere Zeichenfolge sein). erst, wenn als Parameter übergeben, und zweitens als "[email protected]" in der Funktion).

Diese Lösung erfordert auch nicht, dass der Benutzer die Funktion innerhalb einer Befehlssubstitution aufruft, die eine Subshell aufruft, um eine verbundene Version einer Zeichenfolge einer anderen Variablen zuzuweisen.

Was die Nachteile angeht: Sie müssten vorsichtig sein, wenn Sie einen korrekten Parameternamen übergeben, und __r würde Ihnen __r[@] übergeben. Das Verhalten der Variablen-Indirektion zur Erweiterung anderer Parameterformen wird ebenfalls nicht explizit dokumentiert.

function join_by_ref {
    __=
    local __r=$1[@] __s=${2-' '}
    printf -v __ "%s${__s//\%/%%}" "${!__r}"
    __=${__%${__s}}
}

array=(1 2 3 4)

join_by_ref array
echo "$__" # Prints '1 2 3 4'.

join_by_ref array '%s'
echo "$__" # Prints '1%s2%s3%s4'.

join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution".
echo "$__" # Prints nothing but newline.

Dies funktioniert von 3.1 bis 5.0-alpha. Wie erwähnt funktioniert die Indirektion von Variablen nicht nur mit Variablen, sondern auch mit anderen Parametern.

Ein Parameter ist eine Entität, die Werte speichert. Es kann ein Name sein, eine Nummer oder eines der Sonderzeichen, die unter Special .__ aufgeführt sind. Parameter. Eine Variable ist ein Parameter, der mit einem Namen bezeichnet wird.

Arrays und Array-Elemente sind ebenfalls Parameter (Entitäten, die Werte speichern), und Verweise auf Arrays sind auch technisch Verweise auf Parameter. Und ähnlich wie der spezielle Parameter @ macht array[@] auch eine gültige Referenz.

Geänderte oder selektive Erweiterungsformen (wie die Erweiterung der Teilzeichenfolge), die vom Parameter selbst abweichen, funktionieren nicht mehr.

1
konsolebox

Im Moment verwende ich:

TO_IGNORE=(
    E201 # Whitespace after '('
    E301 # Expected N blank lines, found M
    E303 # Too many blank lines (pep8 gets confused by comments)
)
ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`"

Was funktioniert, wird (im allgemeinen Fall) jedoch fürchterlich brechen, wenn Arrayelemente ein Leerzeichen enthalten.

(Für Interessenten ist dies ein Wrapper-Skript um pep8.py )

1
David Wolever

Die meisten POSIX-kompatiblen Shells unterstützen Folgendes:

join_by() {
    # Usage:  join_by "||" a b c d
    local arg arr=() sep="$1"
    shift
    for arg in "[email protected]"; do
        if [ 0 -lt "${#arr[@]}" ]; then
            arr+=("${sep}")
        fi
        arr+=("${arg}") || break
    done
    printf "%s" "${arr[@]}"
}
1
Mehrdad

Falls die Elemente, die Sie verbinden möchten, kein Array sind, sondern nur eine durch Leerzeichen getrennte Zeichenfolge, können Sie Folgendes tun:

foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
    'aa','bb','cc','dd'

mein Anwendungsfall ist zum Beispiel, dass einige Zeichenfolgen in meinem Shell-Skript übergeben werden, und ich muss dies verwenden, um eine SQL-Abfrage auszuführen:

./my_script "aa bb cc dd"

In my_script muss ich "SELECT * FROM table WHERE name IN" ('aa', 'bb', 'cc', 'dd') ausführen. Dann ist der obige Befehl nützlich.

1
Dexin Wang

Verwenden Sie Perl für Trennzeichen mit mehreren Zeichen:

function join {
   Perl -e '$s = shift @ARGV; print join($s, @ARGV);' "[email protected]"; 
}

join ', ' a b c # a, b, c

Oder in einer Zeile:

Perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
1
dpatru

Dieser Ansatz berücksichtigt Leerzeichen innerhalb der Werte, erfordert jedoch eine Schleife:

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}
0
dengel

x=${"${arr[*]}"// /,}

Dies ist der kürzeste Weg.

Beispiel,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
0
user31986

Wenn Sie das Array in einer Schleife erstellen, ist dies ein einfacher Weg:

arr=()
for x in $(some_cmd); do
   arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
0
Ian Kelling