Jeśli mam taką tablicę w Bash:
FOO=( a b c )
Jak połączyć elementy przecinkami? Na przykład produkcja a,b,c
.
Jeśli mam taką tablicę w Bash:
FOO=( a b c )
Jak połączyć elementy przecinkami? Na przykład produkcja a,b,c
.
Odpowiedzi:
Przepisywanie rozwiązania przez Pascala Pilza jako funkcja w 100% czystej Bash (bez zewnętrznych poleceń):
function join_by { local IFS="$1"; shift; echo "$*"; }
Na przykład,
join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
Alternatywnie możemy użyć printf do obsługi ograniczników wieloznakowych, korzystając z pomysłu @gniourf_gniourf
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
Na przykład,
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
stylu :) function join { local IFS=$1; __="${*:2}"; }
lub function join { IFS=$1 eval '__="${*:2}"'; }
. Następnie użyj __
po. Tak, promuję użycie __
jako zmiennej wynikowej;) (i wspólnej zmiennej iteracyjnej lub zmiennej tymczasowej). Jeśli pomysł
$d
w specyfikatorze formatu printf
. Myślisz, że jesteś bezpieczny, ponieważ „uciekłeś”, %
ale istnieją inne zastrzeżenia: kiedy ogranicznik zawiera odwrotny ukośnik (np. \n
) Lub kiedy ogranicznik zaczyna się od myślnika (i może innych, o których nie mogę teraz myśleć). Możesz je oczywiście naprawić (zastąpić odwrotnymi ukośnikami podwójnymi odwrotnymi ukośnikami i użyć printf -- "$d%s"
), ale w pewnym momencie poczujesz, że walczysz z powłoką zamiast z nią pracować. Dlatego w poniższej odpowiedzi wstawiłem separator do warunków, które należy dołączyć.
Jeszcze inne rozwiązanie:
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}
echo $bar
Edycja: tak samo, ale dla wieloznakowego separatora o zmiennej długości:
#!/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[@]}"
. To o jeden fork
mniej (właściwie clone
). Jest nawet rozwidlone odczytu pliku: printf -v bar ",%s" $(<infile)
.
$separator
nie zawiera %s
lub takich, można zrobić swój printf
wytrzymałość: printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
spowoduje użycie separatora TYLKO pierwszego zestawu danych wyjściowych, a następnie po prostu połączy pozostałe argumenty.
printf "%s" "${foo[@]/#/$separator}"
.
IFS=; regex="${foo[*]/#/$separator}"
. W tym momencie zasadniczo staje się to odpowiedzią gniourf_gniourf, która IMO jest czystsza od samego początku, to znaczy za pomocą funkcji ograniczającej zakres zmian IFS i zmienne temp.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
zamiast *
, jak w $(IFS=, ; echo "${foo[@]}")
? Widzę, że *
już zachowuje białe znaki w elementach, znowu nie jestem pewien, jak to zrobić , ponieważ @
zwykle jest to wymagane ze względu na to.
*
. Na stronie podręcznika użytkownika bash wyszukaj „Parametry specjalne” i poszukaj wyjaśnienia obok *
:
Może np.
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(nawiasy klamrowe oddzielają myślniki od nazwy zmiennej).
echo $IFS
robi to samo.
Co zaskakujące, moje rozwiązanie nie zostało jeszcze podane :) To dla mnie najprostszy sposób. Nie potrzebuje funkcji:
IFS=, eval 'joined="${foo[*]}"'
Uwaga: Zaobserwowano, że to rozwiązanie działa dobrze w trybie innym niż POSIX. W trybie POSIX elementy są nadal prawidłowo łączone, ale IFS=,
stają się trwałe.
Oto w 100% czysta funkcja Bash, która wykonuje zadanie:
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}"
}
Popatrz:
$ 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
$
Zachowuje to nawet końcowe znaki nowego wiersza i nie wymaga podpowłoki, aby uzyskać wynik funkcji. Jeśli nie podoba ci się printf -v
(dlaczego by ci się nie podobało?) I przekazujesz nazwę zmiennej, możesz oczywiście użyć zmiennej globalnej dla zwracanego ciągu:
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
zmienną lokalną, a następnie odbijając ją na końcu. Pozwala to na użycie join () w zwykły sposób skryptowania powłoki, np. $(join ":" one two three)
I nie wymaga zmiennej globalnej.
$(...)
przycina końcowe znaki nowej linii; więc jeśli ostatnie pole tablicy zawiera końcowe znaki nowej linii, zostaną one przycięte (zobacz wersję demonstracyjną, w której nie zostały one przycięte przez mój projekt).
Nie różni się to zbytnio od istniejących rozwiązań, ale unika używania osobnej funkcji, nie modyfikuje się IFS
w powłoce nadrzędnej i znajduje się w jednym wierszu:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
powodując
a,b,c
Ograniczenie: separator nie może być dłuższy niż jedna postać.
Bez użycia zewnętrznych poleceń:
$ 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
Ostrzeżenie, zakłada, że elementy nie mają białych znaków.
echo ${FOO[@]} | tr ' ' ','
Chciałbym powtórzyć tablicę jako ciąg znaków, a następnie przekształcić spacje w kanały liniowe, a następnie użyć paste
do połączenia wszystkiego w jednym wierszu w taki sposób:
tr " " "\n" <<< "$FOO" | paste -sd , -
Wyniki:
a,b,c
To wydaje mi się najszybsze i najczystsze!
$FOO
jest tylko pierwszym elementem tablicy. Ponadto powoduje to uszkodzenie elementów tablicy zawierających spacje.
Ponowne użycie @ nie ma znaczenia rozwiązanie, ale z jedną instrukcją, unikając podstacji $ {: 1} i potrzeby zmiennej pośredniej.
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
printf ma „Ciąg formatu jest ponownie wykorzystywany tak często, jak to konieczne, aby spełnić argumenty”. na stronach podręcznika, aby udokumentować konkatenacje łańcuchów. Zatem sztuczką jest użycie długości LISTY do posiekania ostatniego speratora, ponieważ cięcie zachowa tylko długość LISTY w miarę liczenia pól.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
może uchronić połączone wartości przed błędną interpretacją, gdy mają w sobie 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 rozwiązanie, które akceptuje separatory dowolnej długości (na podstawie @ nie ma znaczenia odpowiedź)
#/!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
specyfikator formatu (np. %s
Niezamierzenie w $sep
spowoduje problemy).
sep
można się zdezynfekować ${sep//\%/%%}
. I jak rozwiązania lepsze niż ${bar#${sep}}
lub ${bar%${sep}}
(alternatywnie). Jest to przydatne, jeśli zostanie przekonwertowane na funkcję przechowującą wynik w zmiennej ogólnej, takiej jak __
, a nie w echo
nim.
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,
na wykorzystaniu historii.
HISTSIZE=0
- wypróbuj.
Krótsza wersja najwyższej odpowiedzi:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Stosowanie:
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' "${@}"; }
Działa z użyciem: join_strings 'delim' "${array[@]}"
lub bez cudzysłowu:join_strings 'delim' ${array[@]}
Połącz najlepsze ze wszystkich dotychczasowych światów z następującym pomysłem.
# join with separator
join_ws() { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }
To małe arcydzieło
Przykłady:
$ 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 ,
(bez argumentów) niepoprawnie wyprowadza ,,
. 2. join_ws , -e
błędnie wyprowadza nic (to dlatego, że niewłaściwie używasz echo
zamiast printf
). W rzeczywistości nie wiem, dlaczego reklamowałeś użycie echo
zamiast printf
: echo
jest notorycznie zepsuty i printf
jest solidnym wbudowanym programem.
Obecnie używam:
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 ' ' ','`"
Które działa, ale (w ogólnym przypadku) przerwie się okropnie, jeśli elementy tablicy będą miały spację.
(Dla zainteresowanych jest to skrypt otoki wokół pep8.py )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
. Operator $()
ma większą moc niż backtics (umożliwia zagnieżdżanie $()
i ""
). ${TO_IGNORE[@]}
Pomóc powinno także zawijanie podwójnych cudzysłowów.
Użyj perla dla separatorów wieloznakowych:
function join {
perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@";
}
join ', ' a b c # a, b, c
Lub w jednym wierszu:
perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
join
nazwa koliduje z jakimś gównem na OS X
... zadzwoniłbym conjoined
, a może jackie_joyner_kersee
?
Dziękuję @gniourf_gniourf za szczegółowe komentarze na temat mojej dotychczasowej kombinacji najlepszych światów. Przepraszamy za kod pocztowy, który nie został dokładnie zaprojektowany i przetestowany. Oto lepsza próba.
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
To piękno z założenia jest
Dodatkowe przykłady:
$ 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 $
$
Jeśli elementy, które chcesz połączyć, nie są tablicą, ale ciągiem znaków oddzielonych spacją, możesz zrobić coś takiego:
foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
'aa','bb','cc','dd'
na przykład, moim przypadkiem użycia jest to, że niektóre ciągi znaków są przekazywane w moim skrypcie powłoki i muszę go użyć, aby uruchomić zapytanie SQL:
./my_script "aa bb cc dd"
W moim skrypcie muszę wykonać polecenie „WYBIERZ * Z tabeli, GDZIE nazwa IN („ aa ”,„ bb ”,„ cc ”,„ dd ”). Wtedy powyższe polecenie będzie przydatne.
printf -v bar ...
zamiast uruchamiać pętlę printf w podpowłoce i przechwytywać dane wyjściowe.
Oto jeden, który obsługuje większość powłok kompatybilnych z 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
).
Działa również użycie pośredniej zmiennej do bezpośredniego odniesienia do tablicy. Można również użyć nazwanych referencji, ale stały się one dostępne dopiero w 4.3.
Zaletą korzystania z tej formy funkcji jest to, że separator może być opcjonalny (domyślnie jest to pierwszy znak domyślny IFS
, który jest spacją; być może uczyni to pustym ciągiem, jeśli chcesz), i pozwala on uniknąć dwukrotnego zwiększania wartości (najpierw gdy przekazany jako parametry, a drugi jako"$@"
wewnątrz funkcji).
To rozwiązanie nie wymaga również od użytkownika wywoływania funkcji w podstawieniu polecenia - które przywołuje podpowłokę, aby otrzymać połączoną wersję ciągu przypisanego do innej zmiennej.
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.
Możesz użyć wygodniejszej nazwy dla tej funkcji.
Działa to od 3.1 do 5.0-alfa. Jak zaobserwowano, pośrednia zmienność działa nie tylko ze zmiennymi, ale także z innymi parametrami.
Parametr to jednostka przechowująca wartości. Może to być nazwa, liczba lub jeden ze znaków specjalnych wymienionych poniżej w części Parametry specjalne. Zmienna jest parametrem oznaczonym nazwą.
Tablice i elementy tablic są również parametrami (encje przechowujące wartość), a odniesienia do tablic są również technicznie odniesieniami do parametrów. I podobnie jak parametr specjalny @
,array[@]
również stanowi ważne odniesienie.
Zmienione lub selektywne formy rozszerzenia (takie jak rozszerzenie substringu), które odbiegają od odniesienia do samego parametru, już nie działają.
W wydanej wersji Bash 5.0 zmienna pośrednia jest już nazywana rozszerzeniem pośrednim, a jej zachowanie jest już wyraźnie udokumentowane w podręczniku:
Jeśli pierwszym znakiem parametru jest wykrzyknik (!), A parametr nie jest nazwą, to wprowadza on poziom pośredni. Bash używa wartości utworzonej przez rozwinięcie reszty parametru jako nowego parametru; jest on następnie rozwijany i ta wartość jest wykorzystywana w pozostałej części rozwinięcia, a nie w rozszerzeniu oryginalnego parametru. Jest to znane jako ekspansja pośrednia.
Biorąc pod uwagę, że w dokumentacji ${parameter}
, parameter
jest określany jako „parametr powłoki, jak opisano (w) PARAMETRY lub odwołanie do tablicy ”. W dokumentacji tablic wspomniano, że „do dowolnego elementu tablicy można się odwoływać za pomocą ${name[subscript]}
”. To sprawia, __r[@]
że odwołanie do tablicy.
Zobacz mój komentarz w odpowiedzi Riccardo Galli .
__
jako nazwy zmiennej? Sprawia, że kod jest naprawdę nieczytelny.
Jeśli budujesz tablicę w pętli, oto prosty sposób:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
x=${"${arr[*]}"// /,}
To najkrótszy sposób na zrobienie tego.
Przykład,
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x # output: 1,2,3,4,5
bash: ${"${arr[*]}"// /,}: bad substitution
Być może brakuje mi czegoś oczywistego, ponieważ jestem nowicjuszem w całym bash / zsh, ale wydaje mi się, że nie musisz go wcale używać printf
. Bez tego nie robi się naprawdę brzydko.
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
Przynajmniej do tej pory działało dla mnie bez problemu.
Na przykład, join \| *.sh
co powiedzmy, że jestem w moim ~
katalogu, generuje dane wyjściowe utilities.sh|play.sh|foobar.sh
. Wystarczająco dobrze dla mnie.
EDYCJA: Jest to w zasadzie odpowiedź Nila Geisweillera , ale uogólniona na funkcję.
liststr=""
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
Daje to również dodatkowy przecinek na końcu. Nie jestem ekspertem od bash. Tylko mój 2c, ponieważ jest to bardziej elementarne i zrozumiałe