Se eu tiver uma matriz como esta no Bash:
FOO=( a b c )
Como uno os elementos com vírgulas? Por exemplo, produzindo a,b,c
.
Se eu tiver uma matriz como esta no Bash:
FOO=( a b c )
Como uno os elementos com vírgulas? Por exemplo, produzindo a,b,c
.
Respostas:
Solução de reescrita de Pascal Pilz como uma função no Bash 100% puro (sem comandos externos):
function join_by { local IFS="$1"; shift; echo "$*"; }
Por exemplo,
join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
Como alternativa, podemos usar printf para suportar delimitadores de vários caracteres, usando a ideia de @gniourf_gniourf
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
Por exemplo,
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
estilo :) function join { local IFS=$1; __="${*:2}"; }
ou function join { IFS=$1 eval '__="${*:2}"'; }
. Então use __
depois. Sim, sou eu quem promove o uso __
como uma variável de resultado;) (e uma variável de iteração comum ou variável temporária). Se o conceito chega a um site popular wiki Bash, eles me copiada :)
$d
no especificador de formato de printf
. Você pensa que está seguro desde que escapou, %
mas existem outras advertências: quando o delimitador contém uma barra invertida (por exemplo, \n
) ou quando o delimitador começa com um hífen (e talvez outros em que eu não consigo pensar agora). É claro que você pode corrigi-las (substituir barras invertidas por barras duplas e usar printf -- "$d%s"
), mas em algum momento você sentirá que está lutando contra o shell em vez de trabalhar com ele. É por isso que, na minha resposta abaixo, acrescentei o delimitador aos termos a serem unidos.
Mais uma solução:
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}
echo $bar
Editar: o mesmo, mas para o separador de comprimento variável de vários caracteres:
#!/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[@]}"
. É um a fork
menos (na verdade clone
). É ainda bifurcação lendo um arquivo: printf -v bar ",%s" $(<infile)
.
$separator
não contém %s
ou tal, você pode fazer o seu printf
robusto: printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
usaria separador no conjunto de saída ONLY de primeira instância e, em seguida, simplesmente concatenaria o restante dos argumentos.
printf "%s" "${foo[@]/#/$separator}"
.
IFS=; regex="${foo[*]/#/$separator}"
. Neste ponto, isso se torna essencialmente a resposta de gniourf_gniourf, que o IMO é mais limpo desde o início, ou seja, usando a função para limitar o escopo das alterações e variações temporárias do IFS.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
vez de *
, como em $(IFS=, ; echo "${foo[@]}")
? Eu posso ver que o *
já preserva o espaço em branco nos elementos, mais uma vez não sei como, já que @
geralmente é necessário para esse efeito.
*
. Na página de manual do bash, procure por "Parâmetros especiais" e procure a explicação ao lado de *
:
Talvez, por exemplo,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(os chavetas separam os traços do nome da variável).
echo $IFS
faz a mesma coisa.
Surpreendentemente, minha solução ainda não foi dada :) Essa é a maneira mais simples para mim. Não precisa de uma função:
IFS=, eval 'joined="${foo[*]}"'
Nota: Observou-se que esta solução funciona bem no modo não POSIX. No modo POSIX , os elementos ainda são unidos corretamente, mas IFS=,
se tornam permanentes.
Aqui está uma função Bash 100% pura que faz o trabalho:
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}"
}
Veja:
$ 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
$
Isso preserva até as novas linhas finais e não precisa de um subshell para obter o resultado da função. Se você não gosta do printf -v
(por que não gosta?) E passa um nome de variável, é claro que pode usar uma variável global para a string retornada:
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
uma variável local e repetindo-a no final. Isso permite que o join () seja usado da maneira usual de script de shell, por exemplo $(join ":" one two three)
, e não requer uma variável global.
$(...)
apara novas linhas; portanto, se o último campo da matriz contiver novas linhas à direita, elas serão aparadas (consulte a demonstração em que não são aparadas com meu design).
Isso não é muito diferente das soluções existentes, mas evita o uso de uma função separada, não modifica IFS
no shell pai e é tudo em uma única linha:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
resultando em
a,b,c
Limitação: o separador não pode ter mais de um caractere.
Usando nenhum comando externo:
$ 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
Aviso, assume que os elementos não têm espaços em branco.
echo ${FOO[@]} | tr ' ' ','
Gostaria de ecoar a matriz como uma string, depois transformar os espaços em feeds de linha e, em seguida, usar paste
para juntar tudo em uma linha da seguinte maneira:
tr " " "\n" <<< "$FOO" | paste -sd , -
Resultados:
a,b,c
Este parece ser o mais rápido e limpo para mim!
$FOO
é apenas o primeiro elemento da matriz. Além disso, isso interrompe os elementos da matriz que contêm espaços.
Com a reutilização de @, não importa a solução, mas com uma declaração, evitando a subestação $ {: 1} e a necessidade de uma variável intermediária.
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
printf tem 'A string de formato é reutilizada quantas vezes for necessário para satisfazer os argumentos.' em suas páginas de manual, para que as concatenações das strings sejam documentadas. O truque é usar o comprimento LIST para cortar o último sperator, pois o corte reterá apenas o comprimento de LIST à medida que os campos forem contados.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
poderia escapar os valores unidas a partir interpretando mal quando eles têm um marceneiro neles: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"
saídas'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
solução printf que aceita separadores de qualquer tamanho (com base em @ não importa resposta)
#/!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
especificador de formato (por exemplo, %s
involuntariamente) $sep
causará problemas.
sep
pode ser higienizado com ${sep//\%/%%}
. Gosto mais da sua solução ${bar#${sep}}
ou ${bar%${sep}}
(alternativa). Isso é bom se convertido em uma função que armazena o resultado em uma variável genérica como __
, e não echo
ela.
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,
não é sobre o uso da história.
HISTSIZE=0
- experimente.
Versão mais curta da resposta principal:
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' "${@}"; }
Funciona com o uso: join_strings 'delim' "${array[@]}"
ou sem join_strings 'delim' ${array[@]}
Combine o melhor de todos os mundos até agora com a ideia a seguir.
# join with separator
join_ws() { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }
Esta pequena obra-prima é
Exemplos:
$ 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 ,
(sem argumentos) gera incorretamente ,,
. 2. join_ws , -e
gera nada de forma errada (é porque você está usando incorretamente em echo
vez de printf
). Na verdade, não sei por que você anunciou o uso de, em echo
vez de printf
: echo
é notoriamente quebrado e printf
é um componente robusto.
Agora eu estou 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 ' ' ','`"
O que funciona, mas (no caso geral) quebrará horrivelmente se os elementos da matriz tiverem um espaço neles.
(Para os interessados, este é um script de wrapper em torno de pep8.py )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
. O operador $()
é mais poderoso que o backtics (permite o aninhamento de $()
e ""
). Quebra automática ${TO_IGNORE[@]}
com aspas duplas também deve ajudar.
Use perl para separadores de vários caracteres:
function join {
perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@";
}
join ', ' a b c # a, b, c
Ou em uma linha:
perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
join
nome esteja em conflito com alguma porcaria OS X
.. eu chamaria conjoined
, ou talvez jackie_joyner_kersee
?
Obrigado @gniourf_gniourf pelos comentários detalhados sobre minha combinação dos melhores mundos até agora. Desculpe por postar código não totalmente projetado e testado. Aqui está uma tentativa melhor.
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
Essa beleza por concepção é
Exemplos adicionais:
$ 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 $
$
Caso os elementos que você deseja juntar não sejam uma matriz apenas uma sequência separada por espaço, você pode fazer algo assim:
foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
'aa','bb','cc','dd'
por exemplo, meu caso de uso é que algumas seqüências de caracteres são passadas no meu script de shell e preciso usá-lo para executar em uma consulta SQL:
./my_script "aa bb cc dd"
Em my_script, eu preciso fazer "SELECT * FROM tabela WHERE nome IN ('aa', 'bb', 'cc', 'dd'). Em seguida, o comando acima será útil.
printf -v bar ...
vez de precisar executar o loop printf em uma subcama e capturar a saída.
Aqui está um que a maioria dos shells compatíveis com POSIX suporta:
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
).
Usar a variável indireta para se referir diretamente a uma matriz também funciona. As referências nomeadas também podem ser usadas, mas apenas se tornaram disponíveis no 4.3.
A vantagem de usar essa forma de função é que você pode ter o separador opcional (o padrão é o primeiro caractere padrão IFS
, que é um espaço; talvez seja uma string vazia, se você preferir), e evita expandir os valores duas vezes (primeiro quando passados como parâmetros e depois como "$@"
dentro da função).
Essa solução também não exige que o usuário chame a função dentro de uma substituição de comando - que convoca um subshell, para obter uma versão unida de uma string atribuída a outra variável.
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.
Sinta-se livre para usar um nome mais confortável para a função.
Isso funciona de 3.1 a 5.0-alfa. Como observado, a indireção de variáveis não funciona apenas com variáveis, mas também com outros parâmetros.
Um parâmetro é uma entidade que armazena valores. Pode ser um nome, um número ou um dos caracteres especiais listados abaixo em Parâmetros especiais. Uma variável é um parâmetro indicado por um nome.
Matrizes e elementos de matriz também são parâmetros (entidades que armazenam valor) e referências a matrizes também são tecnicamente referências a parâmetros. E muito parecido com o parâmetro especial @
,array[@]
também faz uma referência válida.
Formas de expansão alteradas ou seletivas (como expansão de substring) que desviam a referência do próprio parâmetro não funcionam mais.
Na versão de lançamento do Bash 5.0, a indireção variável já é chamada de expansão indireta e seu comportamento já está explicitamente documentado no manual:
Se o primeiro caractere do parâmetro for um ponto de exclamação (!) E o parâmetro não for um nome, ele introduzirá um nível de indireção. O Bash usa o valor formado expandindo o restante do parâmetro como o novo parâmetro; isso é expandido e esse valor é usado no restante da expansão, em vez da expansão do parâmetro original. Isso é conhecido como expansão indireta.
Observando que na documentação de ${parameter}
, parameter
é referido como "um parâmetro de shell conforme descrito (em) PARÂMETROS ou uma referência de matriz ". E na documentação de matrizes, é mencionado que "Qualquer elemento de uma matriz pode ser referenciado usando ${name[subscript]}
". Isso faz __r[@]
uma referência de matriz.
Veja meu comentário na resposta de Riccardo Galli .
__
como um nome de variável? Torna o código realmente ilegível.
Se você criar a matriz em um loop, aqui está uma maneira simples:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
x=${"${arr[*]}"// /,}
Esta é a maneira mais curta de fazer isso.
Exemplo,
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x # output: 1,2,3,4,5
bash: ${"${arr[*]}"// /,}: bad substitution
Talvez esteja perdendo algo óbvio, já que sou um novato em toda a coisa do bash / zsh, mas parece-me que você não precisa usar printf
nada. Nem fica realmente feio ficar sem.
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
Pelo menos, funcionou para mim até agora sem problemas.
Por exemplo, join \| *.sh
que, digamos que eu esteja no meu ~
diretório, produzutilities.sh|play.sh|foobar.sh
. Bom o suficiente para mim.
EDIT: Esta é basicamente a resposta de Nil Geisweiller , mas generalizada em uma função.
liststr=""
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
Isso também cuida da vírgula extra no final. Eu não sou especialista em bash. Apenas o meu 2c, já que isso é mais elementar e compreensível