Wie kann ich Elemente eines Arrays in Bash verbinden?

415

Wenn ich ein Array wie dieses in Bash habe:

FOO=( a b c )

Wie verbinde ich die Elemente mit Kommas? Zum Beispiel produzieren a,b,c.

David Wolever
quelle

Antworten:

570

Umschreiblösung von Pascal Pilz als Funktion in 100% reinem 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 wir printf verwenden, um Trennzeichen mit mehreren Zeichen zu unterstützen, wobei die Idee von @gniourf_gniourf verwendet wird

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
Nicholas Sushkin
quelle
9
Verwenden Sie dies für Trennzeichen mit mehreren Zeichen: function join {perl -e '$ s = shift @ARGV; print join ($ s, @ARGV); ' "$ @"; } Join ',' abc # a, b, c
Daniel Patru
4
@dpatru trotzdem, um diese reine Bash zu machen?
CMCDragonkai
4
@puchu Was nicht funktioniert, sind Trennzeichen mit mehreren Zeichen. Zu sagen, "Raum funktioniert nicht", klingt so, als würde das Verbinden mit einem Raum nicht funktionieren. Es tut.
Eric
6
Dies fördert das Laichen von Subshells, wenn die Ausgabe in einer Variablen gespeichert wird. Verwenden Sie konsoleboxStil :) function join { local IFS=$1; __="${*:2}"; }oder function join { IFS=$1 eval '__="${*:2}"'; }. Dann __nach verwenden. Ja, ich bin derjenige, der die Verwendung __als Ergebnisvariable fördert ;) (und eine allgemeine Iterationsvariable oder temporäre Variable). Wenn das Konzept auf eine beliebte Bash-Wiki-Site
gelangt
6
Fügen Sie die Erweiterung nicht $din den Formatbezeichner von ein printf. Sie denken, Sie sind in Sicherheit, da Sie dem entkommen sind, %aber es gibt noch andere Einschränkungen: Wenn das Trennzeichen einen Backslash enthält (z. B. \n) oder wenn das Trennzeichen mit einem Bindestrich beginnt (und vielleicht andere, an die ich jetzt nicht denken kann). Sie können diese natürlich beheben (Backslashes durch doppelte Backslashes ersetzen und verwenden printf -- "$d%s"), aber irgendwann werden Sie das Gefühl haben, gegen die Shell zu kämpfen, anstatt damit zu arbeiten. Aus diesem Grund habe ich in meiner Antwort unten den Trennzeichen den zu verbindenden Begriffen vorangestellt.
gniourf_gniourf
206

Noch eine andere Lösung:

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

echo $bar

Bearbeiten: gleich, jedoch für ein mehrstelliges Trennzeichen mit variabler 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
spielt keine Rolle
quelle
7
+1. Was ist printf -v bar ",%s" "${foo[@]}". Es ist eins forkweniger (eigentlich clone). Es wird sogar gegabelt, eine Datei zu lesen : printf -v bar ",%s" $(<infile).
TrueY
14
Anstatt zu beten, $separatorenthält nicht %soder so, können Sie Ihre printfrobuste machen : printf "%s%s" "$separator" "${foo[@]}".
Musiphil
5
@musiphil Falsch. Von bash man: "Das Format wird nach Bedarf wiederverwendet, um alle Argumente zu verbrauchen. Wenn Sie zwei Formatplatzhalter wie in printf "%s%s"verwenden, wird in der ersten Instanz NUR ein Ausgabesatz verwendet, und dann werden die restlichen Argumente einfach verkettet.
AnyDev
3
@AndrDevEK: Danke, dass du den Fehler entdeckt hast. Stattdessen würde ich so etwas vorschlagen printf "%s" "${foo[@]/#/$separator}".
Musiphil
2
@musiphil, danke. Ja! Dann wird printf redundant und diese Zeile kann auf reduziert werden IFS=; regex="${foo[*]/#/$separator}". An diesem Punkt wird dies im Wesentlichen zur Antwort von gniourf_gniourf, die IMO von Anfang an sauberer ist, dh die Funktion verwendet, um den Umfang von IFS-Änderungen und temporären Variablen zu begrenzen.
AnyDev
145
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
Pascal Pilz
quelle
3
Die äußeren doppelten Anführungszeichen und die doppelten Anführungszeichen um den Doppelpunkt sind nicht erforderlich. Nur die inneren doppelten Anführungszeichen sind notwendig:bar=$( IFS=, ; echo "${foo[*]}" )
ceving
8
+1 für die kompakteste Lösung, die keine Schleifen benötigt, keine externen Befehle benötigt und dem Zeichensatz der Argumente keine zusätzlichen Einschränkungen auferlegt.
Ceving
22
Ich mag die Lösung, aber es funktioniert nur, wenn IFS ein Zeichen ist
Jayen
8
Irgendeine Idee, warum dies nicht funktioniert, wenn @anstelle von *, wie in $(IFS=, ; echo "${foo[@]}")? Ich kann sehen, dass das *Leerzeichen in den Elementen bereits erhalten bleibt, auch hier nicht sicher, wie, da dies @normalerweise erforderlich ist.
Haridsv
10
Ich habe oben die Antwort auf meine eigene Frage gefunden. Die Antwort ist, dass IFS nur für anerkannt wird *. Suchen Sie in der Bash-Manpage nach "Spezielle Parameter" und suchen Sie nach der Erklärung neben *:
haridsv
66

Vielleicht zB

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

echo "$FOOJOIN"
Martin Clayton
quelle
3
Wenn Sie das tun, denkt es, dass IFS- die Variable ist. Sie müssen dies tun echo "-${IFS}-"(die geschweiften Klammern trennen die Striche vom Variablennamen).
Bis auf weiteres angehalten.
1
Immer noch das gleiche Ergebnis (ich habe nur die Striche echo $IFS
eingefügt
41
Das heißt, das scheint immer noch zu funktionieren ... Also, wie die meisten Dinge bei Bash, werde ich so tun, als würde ich es verstehen und mit meinem Leben weitermachen.
David Wolever
2
Ein "-" ist kein gültiges Zeichen für einen Variablennamen, daher macht die Shell das Richtige, wenn Sie $ IFS- verwenden. Sie benötigen $ {IFS} - (bash, ksh, sh und zsh in Linux und Solaris) nicht auch zustimmen).
Idelic
2
@ David Der Unterschied zwischen Ihrem Echo und Dennis ist, dass er doppelte Anführungszeichen verwendet hat. Der Inhalt von IFS wird bei der Eingabe als Deklaration von Worttrennzeichen verwendet, sodass Sie immer eine leere Zeile ohne Anführungszeichen erhalten.
Martin Clayton
30

Ü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: Es wurde beobachtet, dass diese Lösung im Nicht-POSIX-Modus gut funktioniert. Im POSIX-Modus sind die Elemente weiterhin ordnungsgemäß verbunden, werden jedoch IFS=,dauerhaft.

konsolebox
quelle
Funktioniert leider nur für Einzelzeichen-Trennzeichen
maoizm
24

Hier ist eine 100% reine Bash-Funktion, die den Job erledigt:

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 Unterschale erforderlich, um das Ergebnis der Funktion zu erhalten. Wenn Ihnen das nicht gefällt printf -v(warum würde es Ihnen nicht gefallen?) Und Sie einen Variablennamen übergeben, können Sie natürlich eine globale Variable für die zurückgegebene Zeichenfolge 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}"
}
gniourf_gniourf
quelle
1
Ihre letzte Lösung ist sehr gut, könnte aber sauberer gemacht werden, indem Sie join_reteine lokale Variable erstellen und diese am Ende wiederholen. Dies ermöglicht die Verwendung von join () auf die übliche Art $(join ":" one two three)und Weise der Shell-Skripterstellung, z. B. und erfordert keine globale Variable.
James Sneeringer
1
@ JamesSneeringer Ich habe dieses Design absichtlich verwendet, um Unterschalen zu vermeiden. Im Gegensatz zu vielen anderen Sprachen sind globale Variablen beim Shell-Scripting nicht unbedingt eine schlechte Sache. vor allem, wenn sie hier sind, um Unterschalen zu vermeiden. Darüber hinaus $(...)schneidet nach Zeilenumbrüchen; Wenn das letzte Feld des Arrays nachfolgende Zeilenumbrüche enthält, werden diese gekürzt (siehe Demo, in der sie nicht mit meinem Design gekürzt werden).
gniourf_gniourf
Dies funktioniert mit Trennzeichen mit mehreren Zeichen, was mich glücklich macht ^ _ ^
spiffytech
Um das "Warum möchten Sie printf -v nicht?" Anzusprechen: In Bash sind lokale Variablen nicht wirklich funktionslokal, sodass Sie solche Dinge tun können. (Rufen Sie die Funktion f1 mit der lokalen Variablen x auf, die wiederum die Funktion f2 aufruft, die x modifiziert - was im Bereich von f1 als lokal deklariert wird.) Aber es ist nicht wirklich so, wie lokale Variablen funktionieren sollten. Wenn lokale Variablen wirklich lokal sind (oder angenommen werden, zum Beispiel in einem Skript, das sowohl mit bash als auch mit ksh arbeiten muss), verursacht dies Probleme mit diesem gesamten Schema "Geben Sie einen Wert zurück, indem Sie ihn in der Variablen mit diesem Namen speichern".
Tetsujin
15

Dies unterscheidet sich nicht allzu sehr von vorhandenen Lösungen, vermeidet jedoch die Verwendung einer separaten Funktion, ändert sich nicht IFSin der übergeordneten Shell und befindet sich in einer einzigen Zeile:

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

ergebend

a,b,c

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

Benjamin W.
quelle
13

Keine externen Befehle verwenden:

$ 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

Warnung: Es wird davon ausgegangen, dass Elemente keine Leerzeichen haben.

Nil Geisweiller
quelle
4
Wenn Sie keine Zwischenvariable verwenden möchten, kann dies noch kürzer erfolgen:echo ${FOO[@]} | tr ' ' ','
jesjimher
2
Ich verstehe die negativen Stimmen nicht. Es ist eine sehr kompakte und lesbare Lösung als andere hier veröffentlichte und es wird deutlich gewarnt, dass es nicht funktioniert, wenn Leerzeichen vorhanden sind.
Jesjimher
12

Ich würde das Array als Zeichenfolge wiedergeben, dann die Leerzeichen in Zeilenvorschübe umwandeln und dann pastealles in einer Zeile wie folgt verbinden:

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

Ergebnisse:

a,b,c

Dies scheint mir das schnellste und sauberste zu sein!

Yanick Girouard
quelle
$FOOist jedoch nur das erste Element des Arrays. Dies wird auch für Array-Elemente unterbrochen, die Leerzeichen enthalten.
Benjamin W.
9

Bei der Wiederverwendung von @ spielt die Lösung keine Rolle, aber bei einer One-Anweisung wird die Substitution $ {: 1} vermieden und eine Zwischenvariable benötigt.

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

printf hat 'Die Formatzeichenfolge wird so oft wie nötig wiederverwendet, um die Argumente zu erfüllen.' 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 zerhacken, da beim Schneiden nur die Länge der LIST beibehalten wird, wenn die Felder zählen.

Valise
quelle
7
s=$(IFS=, eval 'echo "${FOO[*]}"')
Aal ghEEz
quelle
8
Sie sollten Ihre Antwort konkretisieren.
Joce
Das allerbeste. Vielen Dank!!
Peter Pan Gz
4
Ich wünschte, ich könnte diese Antwort ablehnen, weil sie eine Sicherheitslücke öffnet und weil sie Leerzeichen in Elementen zerstört.
Aal Gheez
1
@bxm in der Tat scheint es Leerzeichen zu bewahren und es erlaubt nicht, aus dem Kontext der Echo-Argumente zu entkommen. Ich dachte mir, dass das Hinzufügen @Qden foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
verknüpften
1
Vermeiden Sie Lösungen, die Unterschalen verwenden, sofern dies nicht erforderlich ist.
konsolebox
5

printf-Lösung, die Trennzeichen beliebiger Länge akzeptiert (basierend auf @ spielt keine Rolle, Antwort)

#/!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
Riccardo Galli
quelle
Dies erzeugt eine Ausgabe mit einem führenden Komma.
Mark Renouf
Der letzte Balken = $ {bar: $ {# sep}} entfernt das Trennzeichen. Ich habe nur kopiert und in eine Bash-Shell eingefügt und es funktioniert. Welche Shell benutzt du?
Riccardo Galli
2
Jeder printf Formatbezeichner (z. B. %sunbeabsichtigt in $sepwird Probleme verursachen.
Peter.O
sepkann mit saniert werden ${sep//\%/%%}. Ich mag deine Lösung besser als ${bar#${sep}}oder ${bar%${sep}}(alternativ). Dies ist hilfreich, wenn es in eine Funktion konvertiert wird, in der das Ergebnis in einer generischen Variablen wie __und nicht echogespeichert wird.
konsolebox
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
konsolebox
4
$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d
Steven Penny
quelle
Dies sollte oben sein.
Eric Walker
6
Dies sollte nicht oben sein: Was wäre wenn HISTSIZE=0?
Har-Wradim
@ har-wradim, bei dem Trick geht es paste -sd,nicht um die Verwendung von Geschichte.
Veda
@Veda Nein, es geht um die Verwendung der Kombination, und es würde nicht funktionieren, wenn HISTSIZE=0- probieren Sie es aus.
Har-Wradim
4

Kürzere Version der Top-Antwort:

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

Verwendungszweck:

joinStrings "$myDelimiter" "${myArray[@]}"
Camilo Martin
quelle
1
Eine längere Version, aber keine Notwendigkeit, eine Kopie eines join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Teils
Noch eine andere Version: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } Dies funktioniert mit Verwendung: join_strings 'delim' "${array[@]}"oder nicht join_strings 'delim' ${array[@]}
zitiert
4

Kombinieren Sie das bisher Beste aller Welten mit der folgenden 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 deaktiviert, keine externen Aufrufe, kein printf ...)
  • kompakt, vollständig und fehlerfrei (funktioniert mit Einzel- und Mehrzeichenbegrenzern, funktioniert mit Begrenzern, die Leerzeichen, Zeilenumbrüche und andere Shell-Sonderzeichen enthalten, funktioniert mit leerem 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
Gast
quelle
1
Nicht so schön: mindestens 2 Probleme: 1. join_ws ,(ohne Argumente) gibt falsch aus ,,. 2. join_ws , -egibt fälschlicherweise nichts aus (das liegt daran, dass Sie falsch verwenden echoanstatt printf). Ich weiß eigentlich nicht, warum Sie die Verwendung von echostatt beworben haben printf: echoist notorisch kaputt und printfist ein robustes eingebautes.
gniourf_gniourf
1

Im Moment benutze 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, aber (im allgemeinen Fall) schrecklich kaputt geht, wenn Array-Elemente ein Leerzeichen enthalten.

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

David Wolever
quelle
Woher bekommen Sie diese Array-Werte? Wenn Sie es so fest codieren, warum nicht einfach foo = "a, b, c"?
Ghostdog74
In diesem Fall codiere ich die Werte tatsächlich hart, aber ich möchte sie in ein Array einfügen, damit ich sie einzeln kommentieren kann. Ich habe die Antwort aktualisiert, um Ihnen zu zeigen, was ich meine.
David Wolever
Angenommen, Sie verwenden tatsächlich Bash, könnte dies besser funktionieren : ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Der Operator $()ist leistungsfähiger als Backtics (ermöglicht das Verschachteln von $()und ""). Das Umschließen ${TO_IGNORE[@]}mit doppelten Anführungszeichen sollte ebenfalls helfen.
Kevinarpe
1

Mein Versuch.

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

Verwenden Sie Perl für Trennzeichen mit mehreren Zeichen:

function join {
   perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; 
}

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

Oder in einer Zeile:

perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
Daniel Patru
quelle
funktioniert für mich, obwohl der joinName mit etwas Mist in Konflikt steht OS X.. ich würde es nennen conjoined, oder vielleicht jackie_joyner_kersee?
Alex Gray
1

Vielen Dank an @gniourf_gniourf für detaillierte Kommentare zu meiner Kombination der besten Welten bisher. Entschuldigung für den nicht gründlich entworfenen und getesteten Posting-Code. 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 der Empfängnis ist

  • (immer noch) 100% reine Bash (danke, dass Sie ausdrücklich darauf hingewiesen haben, dass printf ebenfalls ein eingebautes Bash ist. Das war mir vorher nicht bewusst ...)
  • funktioniert mit Trennzeichen für mehrere Zeichen
  • kompakter und vollständiger und diesmal sorgfältig überlegt und langfristig mit zufälligen Teilzeichenfolgen aus Shell-Skripten unter anderem auf die Verwendung von Shell-Sonderzeichen oder Steuerzeichen oder keinen Zeichen in Trennzeichen und / oder Parametern sowie Randfällen getestet und Eckfälle und andere Streitfragen mögen überhaupt keine Argumente. Das garantiert nicht, dass es keinen Fehler mehr gibt, aber es wird etwas schwieriger sein, einen zu finden. Übrigens leiden sogar die derzeit am besten bewerteten Antworten und verwandte Antworten unter solchen Dingen - dem Fehler ...

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 $ 
$
Gast
quelle
1

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 beispielsweise, dass einige Zeichenfolgen in meinem Shell-Skript übergeben werden und ich diese verwenden muss, 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.

Dexin Wang
quelle
Sie können printf -v bar ...die printf-Schleife verwenden, anstatt sie in einer Subshell ausführen und die Ausgabe erfassen zu müssen.
Codeforester
Alle oben genannten ausgefallenen Lösungen haben nicht funktioniert, aber Ihre grobe Lösung hat für mich (Y)
funktioniert
1

Hier ist eine, die die meisten POSIX-kompatiblen Shells unterstützen:

join_by() {
    # Usage:  join_by "||" a b c d
    local arg arr=() sep="$1"
    shift
    for arg in "$@"; do
        if [ 0 -lt "${#arr[@]}" ]; then
            arr+=("${sep}")
        fi
        arr+=("${arg}") || break
    done
    printf "%s" "${arr[@]}"
}
user541686
quelle
Es ist in Ordnung, Bash-Code, aber POSIX hat überhaupt keine Arrays (oder local).
Anders Kaseorg
@Anders: Ja, ich habe das kürzlich auf die harte Tour gelernt :( Ich lasse es jedoch
vorerst offen,
1

Die Verwendung der variablen Indirektion, um direkt auf ein Array zu verweisen, funktioniert ebenfalls. Benannte Referenzen können ebenfalls verwendet werden, wurden jedoch erst in 4.3 verfügbar.

Der Vorteil dieser Form einer Funktion besteht darin, dass Sie das Trennzeichen optional haben können (standardmäßig das erste Standardzeichen IFS, das ein Leerzeichen ist; machen Sie es möglicherweise zu einer leeren Zeichenfolge, wenn Sie möchten), und es vermeidet das zweimalige Erweitern von Werten (zuerst) wenn als Parameter übergeben, und zweitens als"$@" innerhalb der Funktion).

Bei dieser Lösung muss der Benutzer die Funktion auch nicht innerhalb einer Befehlssubstitution aufrufen, die eine Unterschale aufruft, um eine verknüpfte Version einer Zeichenfolge zu erhalten, die einer anderen Variablen zugewiesen ist.

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.

Fühlen Sie sich frei, einen bequemeren Namen für die Funktion zu verwenden.

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

Ein Parameter ist eine Entität, die Werte speichert. Dies kann ein Name, eine Nummer oder eines der unten unter Sonderparameter aufgeführten Sonderzeichen sein. Eine Variable ist ein Parameter, der durch einen Namen gekennzeichnet ist.

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 die speziellen Parameter @, array[@]macht auch eine gültige Referenz.

Geänderte oder selektive Expansionsformen (wie die Teilstringerweiterung), die vom Parameter selbst abweichen, funktionieren nicht mehr.

Aktualisieren

In der Release-Version von Bash 5.0 wird die variable Indirektion bereits als indirekte Erweiterung bezeichnet und ihr Verhalten ist bereits im Handbuch explizit dokumentiert:

Wenn das erste Zeichen des Parameters ein Ausrufezeichen (!) Ist und der Parameter kein Namensreferenz, wird eine Indirektionsebene eingeführt. Bash verwendet den Wert, der durch Erweitern des restlichen Parameters als neuer Parameter gebildet wird. Dies wird dann erweitert und dieser Wert wird im Rest der Erweiterung anstelle der Erweiterung des ursprünglichen Parameters verwendet. Dies wird als indirekte Expansion bezeichnet.

Beachten Sie, dass in der Dokumentation von ${parameter}, parameterals "Shell-Parameter wie beschrieben (in) PARAMETER oder Array-Referenz " bezeichnet wird. In der Dokumentation von Arrays wird erwähnt, dass "auf jedes Element eines Arrays mit verwiesen werden kann ${name[subscript]}". Dies macht __r[@]eine Array-Referenz.

Join durch Argumente Version

Siehe meinen Kommentar in Riccardo Gallis Antwort .

konsolebox
quelle
2
Gibt es einen bestimmten Grund für die Verwendung __als Variablenname? Macht den Code wirklich unlesbar.
PesaThe
@PesaThe Es ist nur eine Präferenz. Ich bevorzuge die Verwendung generischer Namen für eine Rückgabevariable. Andere nicht generische Namen schreiben sich bestimmten Funktionen zu und müssen auswendig gelernt werden. Das Aufrufen mehrerer Funktionen, die Werte für verschiedene Variablen zurückgeben, kann dazu führen, dass der Code weniger einfach zu befolgen ist. Die Verwendung eines generischen Namens würde den Scripter zwingen, den Wert von der Rückgabevariablen in die richtige Variable zu übertragen, um Konflikte zu vermeiden, und der Code wird dadurch besser lesbar, da explizit wird, wohin die zurückgegebenen Werte gehen. Ich mache jedoch nur wenige Ausnahmen von dieser Regel.
konsolebox
0

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}
dengel
quelle
0

Wenn Sie das Array in einer Schleife erstellen, haben Sie folgende einfache Möglichkeit:

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

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

Dies ist der kürzeste Weg, dies zu tun.

Beispiel,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
user31986
quelle
1
Dies funktioniert nicht korrekt für Zeichenfolgen mit Leerzeichen: `t = (a" b c "d); echo $ {t [2]} (druckt "b c"); echo $ {"$ {t [*]}" // /,} (druckt a, b, c, d)
kounoupis
7
bash: ${"${arr[*]}"// /,}: bad substitution
Cameron Hudson
0

Vielleicht zu spät zur Party, aber das funktioniert bei mir:

function joinArray() {
  local delimiter="${1}"
  local output="${2}"
  for param in ${@:3}; do
    output="${output}${delimiter}${param}"
  done

  echo "${output}"
}
TacB0sS
quelle
-1

Vielleicht fehlt mir etwas Offensichtliches, da ich ein Neuling in der ganzen Bash / Zsh-Sache bin, aber es sieht für mich so aus, als müssten Sie es überhaupt nicht verwenden printf. Es wird auch nicht wirklich hässlich, darauf zu verzichten.

join() {
  separator=$1
  arr=$*
  arr=${arr:2} # throw away separator and following space
  arr=${arr// /$separator}
}

Zumindest hat es bei mir bisher ohne Probleme geklappt.

Zum Beispiel, join \| *.shwas, sagen wir, ich bin in meinem ~Verzeichnis, ausgegeben wird utilities.sh|play.sh|foobar.sh. Gut genug für mich.

EDIT: Dies ist im Grunde die Antwort von Nil Geisweiller , aber verallgemeinert in eine Funktion.

Jordan
quelle
1
Ich bin nicht der Downvoter, aber die Manipulation eines Global in einer Funktion scheint ziemlich verrückt zu sein.
Tripleee
-2
liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

Dies sorgt auch für das zusätzliche Komma am Ende. Ich bin kein Bash-Experte. Nur mein 2c, da dies elementarer und verständlicher ist

byte_array
quelle
-2
awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}' "${arr[@]}"

oder

$ a=(1 "a b" 3)
$ b=$(IFS=, ; echo "${a[*]}")
$ echo $b
1,a b,3
Miau
quelle