Se ho un array come questo in Bash:
FOO=( a b c )
Come posso unire gli elementi con virgole? Ad esempio, producendo a,b,c
.
Se ho un array come questo in Bash:
FOO=( a b c )
Come posso unire gli elementi con virgole? Ad esempio, producendo a,b,c
.
Risposte:
Soluzione di riscrittura di Pascal Pilz in funzione di Bash puro al 100% (senza comandi esterni):
function join_by { local IFS="$1"; shift; echo "$*"; }
Per esempio,
join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
In alternativa, possiamo usare printf per supportare delimitatori multi-carattere, usando l'idea di @gniourf_gniourf
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
Per esempio,
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
stile :) function join { local IFS=$1; __="${*:2}"; }
o function join { IFS=$1 eval '__="${*:2}"'; }
. Quindi utilizzare __
dopo. Sì, sono quello che promuove l'uso di __
come variabile di risultato;) (e una variabile di iterazione comune o variabile temporanea). Se il concept arriva su un popolare sito wiki di Bash, mi copiano :)
$d
di formato di printf
. Pensi di essere al sicuro da quando sei "sfuggito" al %
ma ci sono altri avvertimenti: quando il delimitatore contiene una barra rovesciata (ad esempio, \n
) o quando il delimitatore inizia con un trattino (e forse altri a cui non riesco a pensare ora). Naturalmente puoi risolverli (sostituisci le barre rovesciate con doppie barre rovesciate e usa printf -- "$d%s"
), ma a un certo punto sentirai che stai combattendo contro la shell invece di lavorare con essa. Ecco perché, nella mia risposta di seguito, ho anteposto il delimitatore ai termini da unire.
Ancora un'altra soluzione:
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}
echo $bar
Modifica: uguale ma per separatore a lunghezza variabile multi-carattere:
#!/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[@]}"
. È uno in fork
meno (in realtà clone
). E 'anche forking lettura di un file: printf -v bar ",%s" $(<infile)
.
$separator
non contiene %s
o tali, è possibile rendere il printf
robusto: printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
userebbe il separatore nella prima istanza SOLO set di output, e quindi semplicemente concatenerebbe il resto degli argomenti.
printf "%s" "${foo[@]/#/$separator}"
.
IFS=; regex="${foo[*]/#/$separator}"
. A questo punto questa diventa essenzialmente la risposta di gniourf_gniourf che IMO è più pulita dall'inizio, ovvero usando la funzione per limitare l'ambito delle modifiche IFS e delle variazioni temporanee.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
invece di *
, come in $(IFS=, ; echo "${foo[@]}")
? Vedo che *
conserva già lo spazio bianco negli elementi, ancora una volta non so come, poiché di @
solito è necessario per questo motivo.
*
. Nella pagina man di bash, cerca "Parametri speciali" e cerca la spiegazione accanto a *
:
Forse, ad es.
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(le parentesi graffe separano i trattini dal nome della variabile).
echo $IFS
fa la stessa cosa.
Sorprendentemente la mia soluzione non è ancora stata data :) Questo è il modo più semplice per me. Non ha bisogno di una funzione:
IFS=, eval 'joined="${foo[*]}"'
Nota: questa soluzione ha funzionato bene in modalità non POSIX. In modalità POSIX , gli elementi sono ancora uniti correttamente, ma IFS=,
diventano permanenti.
Ecco una funzione Bash pura al 100% che fa il lavoro:
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}"
}
Guarda:
$ 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
$
Ciò preserva anche le nuove righe finali e non è necessaria una subshell per ottenere il risultato della funzione. Se non ti piace il printf -v
(perché non ti piacerebbe?) E passando un nome di variabile, puoi ovviamente usare una variabile globale per la stringa restituita:
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
una variabile locale e facendola eco alla fine. Ciò consente a join () di essere utilizzato nel solito modo di script di shell, ad esempio $(join ":" one two three)
, e non richiede una variabile globale.
$(...)
ritaglia le nuove linee; quindi se l'ultimo campo dell'array contiene nuove righe finali, queste verrebbero tagliate (vedere la demo dove non sono state ritagliate con il mio disegno).
Questo non è troppo diverso dalle soluzioni esistenti, ma evita di utilizzare una funzione separata, non si modifica IFS
nella shell padre ed è tutto in una sola riga:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
con il risultato di
a,b,c
Limitazione: il separatore non può essere più lungo di un personaggio.
Non usare comandi esterni:
$ 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
Attenzione, presuppone che gli elementi non abbiano spazi bianchi.
echo ${FOO[@]} | tr ' ' ','
Vorrei fare eco all'array come stringa, quindi trasformare gli spazi in feed di riga e quindi utilizzare paste
per unire tutto in una riga in questo modo:
tr " " "\n" <<< "$FOO" | paste -sd , -
risultati:
a,b,c
Questo sembra essere il più veloce e pulito per me!
$FOO
è solo il primo elemento dell'array, però. Inoltre, ciò si interrompe per gli elementi dell'array contenenti spazi.
Con il riutilizzo della soluzione @ non importa ', ma con una sola istruzione evitando la sottostizione $ {: 1} e la necessità di una variabile intermedia.
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
printf ha "La stringa di formato viene riutilizzata tutte le volte che è necessario per soddisfare gli argomenti". nelle sue pagine man, in modo che le concatenazioni delle stringhe siano documentate. Quindi il trucco consiste nell'utilizzare la lunghezza dell'ELENCO per tagliare l'ultimo speratore, poiché il taglio manterrà solo la lunghezza dell'ELENCO mentre i campi contano.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
potesse sfuggire ai valori uniti dall'interpretazione errata quando 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 '
soluzione printf che accetta separatori di qualsiasi lunghezza (basato sulla risposta @ non importa)
#/!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
di formato . (Ad es %s
involontariamente nel $sep
causerà problemi.
sep
può essere disinfettato con ${sep//\%/%%}
. Mi piace la tua soluzione meglio di ${bar#${sep}}
o ${bar%${sep}}
(alternativa). Questo è utile se convertito in una funzione che memorizza il risultato in una variabile generica come __
, e non in echo
essa.
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,
non riguarda l'uso della storia.
HISTSIZE=0
- provalo.
Versione più breve della risposta principale:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Uso:
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' "${@}"; }
funziona con l'utilizzo: join_strings 'delim' "${array[@]}"
o non quotato:join_strings 'delim' ${array[@]}
Combina il meglio di tutti i mondi finora con la seguente idea.
# join with separator
join_ws() { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }
Questo piccolo capolavoro è
Esempi:
$ 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 ,
(senza argomenti) genera erroneamente ,,
. 2. join_ws , -e
erroneamente non produce nulla (questo perché stai usando erroneamente echo
invece di printf
). In realtà non so perché tu abbia pubblicizzato l'uso echo
invece di printf
: echo
è notoriamente rotto, ed printf
è un solido built-in.
In questo momento sto usando:
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 ' ' ','`"
Il che funziona, ma (nel caso generale) si romperà orribilmente se gli elementi dell'array hanno uno spazio al loro interno.
(Per gli interessati, questo è uno script wrapper attorno a pep8.py )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
. L'operatore $()
è più potente dei backtic (consente l'annidamento di $()
e ""
). Anche avvolgere ${TO_IGNORE[@]}
con virgolette doppie dovrebbe aiutare.
Usa perl per i separatori multi-carattere:
function join {
perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@";
}
join ', ' a b c # a, b, c
O in una riga:
perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
join
nome è in conflitto con qualche schifezza su OS X
.. lo chiamerei conjoined
, o forse jackie_joyner_kersee
?
Grazie @gniourf_gniourf per commenti dettagliati sulla mia combinazione dei migliori mondi finora. Ci scusiamo per la pubblicazione di codice non completamente progettato e testato. Ecco un tentativo migliore.
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
Questa bellezza per concezione è
Esempi aggiuntivi:
$ 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 $
$
Nel caso in cui gli elementi che si desidera unire non siano un array, ma solo una stringa separata da spazi, è possibile fare qualcosa del genere:
foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
'aa','bb','cc','dd'
ad esempio, il mio caso d'uso è che alcune stringhe sono passate nel mio script shell e devo usarlo per eseguire una query SQL:
./my_script "aa bb cc dd"
In my_script, devo fare "SELEZIONA * DA tabella DOVE nome IN ('aa', 'bb', 'cc', 'dd'). Quindi il comando sopra sarà utile.
printf -v bar ...
invece di eseguire il ciclo printf in una subshell e acquisire l'output.
Eccone uno supportato dalla maggior parte delle shell compatibili con POSIX:
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
).
Funziona anche usando indiretta variabile per fare riferimento direttamente ad un array. I riferimenti nominati possono anche essere usati, ma sono diventati disponibili solo in 4.3.
Il vantaggio di utilizzare questa forma di una funzione è che puoi avere il separatore opzionale (il valore predefinito è il primo carattere di default IFS
, che è uno spazio; forse, se lo desideri, rendilo una stringa vuota), ed evita di espandere i valori due volte (prima quando passato come parametri e secondo come "$@"
all'interno della funzione).
Inoltre, questa soluzione non richiede all'utente di chiamare la funzione all'interno di una sostituzione di comando, che convoca una subshell, per ottenere una versione unita di una stringa assegnata a un'altra variabile.
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.
Sentiti libero di usare un nome più comodo per la funzione.
Funziona da 3.1 a 5.0-alfa. Come osservato, l'indirizzamento variabile non funziona solo con le variabili ma anche con altri parametri.
Un parametro è un'entità che memorizza valori. Può essere un nome, un numero o uno dei caratteri speciali elencati di seguito in Parametri speciali. Una variabile è un parametro indicato da un nome.
Anche le matrici e gli elementi della matrice sono parametri (entità che memorizzano il valore) e i riferimenti alle matrici sono anche tecnicamente riferimenti a parametri. E proprio come il parametro speciale @
, array[@]
fa anche un riferimento valido.
Le forme di espansione alterate o selettive (come l'espansione della sottostringa) che deviano il riferimento dal parametro stesso non funzionano più.
Nella versione di rilascio di Bash 5.0, l'indirizzamento variabile è già chiamato espansione indiretta e il suo comportamento è già esplicitamente documentato nel manuale:
Se il primo carattere del parametro è un punto esclamativo (!) E il parametro non è un nameref, introduce un livello di riferimento indiretto. Bash utilizza il valore formato espandendo il resto del parametro come nuovo parametro; questo viene quindi espanso e quel valore viene utilizzato nel resto dell'espansione, piuttosto che nell'espansione del parametro originale. Questo è noto come espansione indiretta.
Tenendo presente che nella documentazione di ${parameter}
, parameter
viene indicato come "un parametro di shell come descritto (in) PARAMETRI o un riferimento di matrice ". E nella documentazione degli array, si dice che "Qualsiasi elemento di un array può essere referenziato usando ${name[subscript]}
". Questo fa __r[@]
un riferimento di matrice.
Vedi il mio commento nella risposta di Riccardo Galli .
__
come nome di variabile? Rende il codice davvero illeggibile.
Se compili l'array in un ciclo, ecco un modo semplice:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
x=${"${arr[*]}"// /,}
Questo è il modo più breve per farlo.
Esempio,
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x # output: 1,2,3,4,5
bash: ${"${arr[*]}"// /,}: bad substitution
Forse mi manca qualcosa di ovvio, dal momento che sono un principiante dell'intera cosa bash / zsh, ma mi sembra che non sia necessario utilizzare printf
affatto. Né diventa davvero brutto farne a meno.
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
Almeno, ha funzionato per me finora senza problemi.
Ad esempio, join \| *.sh
che, diciamo, sono nella mia ~
directory, output utilities.sh|play.sh|foobar.sh
. Abbastanza buono per me
EDIT: Questa è sostanzialmente la risposta di Nil Geisweiller , ma generalizzata in una funzione.
liststr=""
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
Questo si occupa anche della virgola extra alla fine. Non sono un esperto di bash. Solo il mio 2c, poiché questo è più elementare e comprensibile