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
.
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
.
Antworten:
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
konsolebox
Stil :) 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
$d
in 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.
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
printf -v bar ",%s" "${foo[@]}"
. Es ist eins fork
weniger (eigentlich clone
). Es wird sogar gegabelt, eine Datei zu lesen : printf -v bar ",%s" $(<infile)
.
$separator
enthält nicht %s
oder so, können Sie Ihre printf
robuste machen : printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
verwenden, wird in der ersten Instanz NUR ein Ausgabesatz verwendet, und dann werden die restlichen Argumente einfach verkettet.
printf "%s" "${foo[@]/#/$separator}"
.
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.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
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.
*
. Suchen Sie in der Bash-Manpage nach "Spezielle Parameter" und suchen Sie nach der Erklärung neben *
:
Vielleicht zB
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(die geschweiften Klammern trennen die Striche vom Variablennamen).
echo $IFS
Ü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.
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}"
}
join_ret
eine 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.
$(...)
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).
Dies unterscheidet sich nicht allzu sehr von vorhandenen Lösungen, vermeidet jedoch die Verwendung einer separaten Funktion, ändert sich nicht IFS
in 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.
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.
echo ${FOO[@]} | tr ' ' ','
Ich würde das Array als Zeichenfolge wiedergeben, dann die Leerzeichen in Zeilenvorschübe umwandeln und dann paste
alles in einer Zeile wie folgt verbinden:
tr " " "\n" <<< "$FOO" | paste -sd , -
Ergebnisse:
a,b,c
Dies scheint mir das schnellste und sauberste zu sein!
$FOO
ist jedoch nur das erste Element des Arrays. Dies wird auch für Array-Elemente unterbrochen, die Leerzeichen enthalten.
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.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
den 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 '
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
printf
Formatbezeichner (z. B. %s
unbeabsichtigt in $sep
wird Probleme verursachen.
sep
kann 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 echo
gespeichert wird.
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0
?
paste -sd,
nicht um die Verwendung von Geschichte.
HISTSIZE=0
- probieren Sie es aus.
Kürzere Version der Top-Antwort:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Verwendungszweck:
joinStrings "$myDelimiter" "${myArray[@]}"
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
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[@]}
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
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
join_ws ,
(ohne Argumente) gibt falsch aus ,,
. 2. join_ws , -e
gibt fälschlicherweise nichts aus (das liegt daran, dass Sie falsch verwenden echo
anstatt printf
). Ich weiß eigentlich nicht, warum Sie die Verwendung von echo
statt beworben haben printf
: echo
ist notorisch kaputt und printf
ist ein robustes eingebautes.
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 )
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.
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
join
Name mit etwas Mist in Konflikt steht OS X
.. ich würde es nennen conjoined
, oder vielleicht jackie_joyner_kersee
?
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
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 $
$
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.
printf -v bar ...
die printf-Schleife verwenden, anstatt sie in einer Subshell ausführen und die Ausgabe erfassen zu müssen.
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[@]}"
}
local
).
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.
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}
, parameter
als "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.
Siehe meinen Kommentar in Riccardo Gallis Antwort .
__
als Variablenname? Macht den Code wirklich unlesbar.
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[*]}
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
bash: ${"${arr[*]}"// /,}: bad substitution
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 \| *.sh
was, 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.
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