Em um script Bash, eu gostaria de dividir uma linha em pedaços e armazená-los em uma matriz.
A linha:
Paris, France, Europe
Eu gostaria de tê-los em uma matriz como esta:
array[0] = Paris
array[1] = France
array[2] = Europe
Eu gostaria de usar código simples, a velocidade do comando não importa. Como eu posso fazer isso?
,
(vírgula-espaço) e não um único caractere , como vírgula. Se você está interessado apenas neste último, é fácil seguir as respostas aqui: stackoverflow.com/questions/918886/…cut
é um comando útil do bash a ter em mente também. O separador é definível en.wikibooks.org/wiki/Cut Você também pode extrair dados de uma estrutura de registro de largura fixa. pt.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmRespostas:
Note-se que os caracteres
$IFS
são tratados individualmente como separadores de modo que, neste caso, os campos podem ser separados por qualquer uma vírgula ou um espaço em vez da sequência de um dos dois caracteres. Curiosamente, os campos vazios não são criados quando o espaço de vírgula aparece na entrada porque o espaço é tratado especialmente.Para acessar um elemento individual:
Para iterar sobre os elementos:
Para obter o índice e o valor:
O último exemplo é útil porque as matrizes Bash são esparsas. Em outras palavras, você pode excluir um elemento ou adicionar um elemento e, em seguida, os índices não são contíguos.
Para obter o número de elementos em uma matriz:
Como mencionado acima, as matrizes podem ser esparsas, portanto você não deve usar o comprimento para obter o último elemento. Veja como você pode no Bash 4.2 e posterior:
em qualquer versão do Bash (de algum lugar após o 2.05b):
Compensações negativas maiores são selecionadas mais longe do final da matriz. Observe o espaço antes do sinal de menos no formulário antigo. É necessário.
fonte
IFS=', '
, então você não precisa remover os espaços separadamente. Teste:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
para saída de teste, a propósito.France, Europe, "Congo, The Democratic Republic of the"
isso será dividido após o congo.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
será divididoarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
como uma nota. Portanto, isso funciona apenas com campos sem espaços, poisIFS=', '
existe um conjunto de caracteres individuais - não um delimitador de string.Todas as respostas a esta pergunta estão erradas de uma maneira ou de outra.
Resposta errada # 1
1: Este é um mau uso de
$IFS
. O valor da$IFS
variável não é usado como um único separador de cadeia de comprimento variável , mas como um conjunto de separadores de cadeia de caracteres únicos , em que cada campo que seread
separa da linha de entrada pode ser finalizado por qualquer caractere do conjunto (vírgula ou espaço, neste exemplo).Na verdade, para os verdadeiros defensores, o significado completo de
$IFS
é um pouco mais envolvido. No manual do bash :Basicamente, para valores não nulos não padrão de
$IFS
, os campos podem ser separados com (1) uma sequência de um ou mais caracteres que são todos do conjunto de "caracteres de espaço em branco do IFS" (ou seja, o que for <espaço> , <tab> e <newline> ("nova linha", significando avanço de linha (LF) ) estão presentes em qualquer local$IFS
) ou (2) qualquer "caractere de espaço em branco do IFS" que esteja presente$IFS
junto com os "caracteres de espaço em branco do IFS" na linha de entrada.Para o OP, é possível que o segundo modo de separação que descrevi no parágrafo anterior seja exatamente o que ele deseja para sua sequência de entrada, mas podemos ter certeza de que o primeiro modo de separação que descrevi não está correto. Por exemplo, e se sua string de entrada fosse
'Los Angeles, United States, North America'
?2: Mesmo se você usasse esta solução com um separador de caractere único (como uma vírgula por si só, ou seja, sem espaço a seguir ou outra bagagem), se o valor da
$string
variável contiver LFs,read
será necessário interrompa o processamento quando encontrar o primeiro LF. Oread
builtin processa apenas uma linha por chamada. Isso é verdade mesmo se você estiver canalizando ou redirecionando a entrada apenas para aread
instrução, como estamos fazendo neste exemplo com o mecanismo aqui-string e, portanto, a entrada não processada é garantida como perdida. O código que alimenta oread
builtin não tem conhecimento do fluxo de dados em sua estrutura de comando que o contém.Você pode argumentar que é improvável que isso cause um problema, mas ainda assim, é um risco sutil que deve ser evitado, se possível. Isso é causado pelo fato de que o
read
interno realmente faz dois níveis de divisão de entrada: primeiro em linhas e depois em campos. Como o OP deseja apenas um nível de divisão, esse uso doread
built-in não é apropriado, e devemos evitá-lo.3: Um problema potencial não óbvio com esta solução é que
read
sempre descarta o campo à direita se estiver vazio, embora, de outra forma, preserve os campos vazios. Aqui está uma demonstração:Talvez o OP não se importe com isso, mas ainda é uma limitação que vale a pena conhecer. Reduz a robustez e a generalidade da solução.
Esse problema pode ser resolvido anexando um delimitador à direita da cadeia de entrada antes de alimentá-lo
read
, como demonstrarei mais adiante.Resposta errada # 2
Idéia semelhante:
(Nota: eu adicionei os parênteses ausentes em torno da substituição de comando que o atendedor parece ter omitido.)
Idéia semelhante:
Essas soluções utilizam a divisão de palavras em uma atribuição de matriz para dividir a sequência em campos. Curiosamente, assim como
read
a divisão geral de palavras também usa a$IFS
variável especial, embora, neste caso, esteja implícito que ela esteja configurada com o valor padrão de <space><tab> <newline> e, portanto, qualquer sequência de um ou mais IFS caracteres (que são todos os caracteres de espaço em branco agora) é considerado um delimitador de campo.Isso resolve o problema de dois níveis de divisão cometidos por
read
, uma vez que a divisão de palavras por si só constitui apenas um nível de divisão. Mas, exatamente como antes, o problema aqui é que os campos individuais na sequência de entrada já podem conter$IFS
caracteres e, portanto, seriam divididos incorretamente durante a operação de divisão de palavras. Isso não é o caso de nenhuma das seqüências de entrada de amostra fornecidas por esses respondentes (que conveniente ...), mas é claro que isso não muda o fato de que qualquer base de código que usasse esse idioma correria o risco de explodindo se essa suposição fosse violada em algum momento abaixo da linha. Mais uma vez, considere meu contra-exemplo de'Los Angeles, United States, North America'
(ou'Los Angeles:United States:North America'
).Além disso, a palavra de divisão é normalmente seguido por expansão nome de arquivo ( aka expansão de nome aka englobamento), que, se feito, seria palavras potencialmente corruptos contendo os caracteres
*
,?
ou[
seguido por]
(e, seextglob
estiver definido, fragmentos entre parênteses precedida por?
,*
,+
,@
, ou!
) combinando-os com objetos do sistema de arquivos e expandindo as palavras ("globs") de acordo. O primeiro desses três respondedores inteligentemente resolveu esse problema, executandoset -f
antecipadamente para desativar o globbing. Tecnicamente, isso funciona (embora você provavelmente deva adicionarset +f
depois, para reativar o globbing do código subsequente que pode depender dele), mas é indesejável ter que mexer com as configurações globais do shell para hackear uma operação básica de análise de string para array no código local.Outro problema com esta resposta é que todos os campos vazios serão perdidos. Isso pode ou não ser um problema, dependendo do aplicativo.
Nota: Se você usar esta solução, é melhor usar a forma
${string//:/ }
"substituição de padrão" da expansão de parâmetros , em vez de invocar uma substituição de comando (que bifurca o shell), iniciar um pipeline e executando um executável externo (tr
oused
), pois a expansão de parâmetros é puramente uma operação interna do shell. (Além disso, para ostr
esed
soluções, a variável de entrada deve ser duas vezes citado no interior da substituição de comando, caso contrário repartição de palavras levaria efeito noecho
comando e, potencialmente, suje os valores de campo Além disso, a.$(...)
Forma de substituição de comando é preferível para o velho`...`
forma, pois simplifica o aninhamento de substituições de comando e permite um melhor destaque da sintaxe pelos editores de texto.)Resposta errada # 3
Esta resposta é quase a mesma que a nº 2 . A diferença é que o atendedor assumiu que os campos são delimitados por dois caracteres, um dos quais sendo representado no padrão
$IFS
e o outro não. Ele resolveu esse caso bastante específico removendo o caractere não representado pelo IFS usando uma expansão de substituição de padrão e, em seguida, usando a divisão de palavras para dividir os campos no caractere delimitador representado pelo IFS sobrevivente.Esta não é uma solução muito genérica. Além disso, pode-se argumentar que a vírgula é realmente o caractere delimitador "primário" aqui, e que removê-lo e depois dependendo do caractere de espaço para a divisão do campo está simplesmente errado. Mais uma vez, considere minhas contra-exemplo:
'Los Angeles, United States, North America'
.Além disso, novamente, a expansão do nome do arquivo pode corromper as palavras expandidas, mas isso pode ser evitado desativando temporariamente o globbing para a atribuição com
set -f
e depoisset +f
.Além disso, novamente, todos os campos vazios serão perdidos, o que pode ou não ser um problema, dependendo do aplicativo.
Resposta errada # 4
Isso é semelhante aos itens 2 e 3, na medida em que utiliza a divisão de palavras para concluir o trabalho, mas agora o código define explicitamente
$IFS
para conter apenas o delimitador de campo de caractere único presente na cadeia de entrada. Deve-se repetir que isso não pode funcionar para delimitadores de campo com vários caracteres, como o delimitador de espaço de vírgula do OP. Mas para um delimitador de caractere único como o LF usado neste exemplo, ele quase chega a ser perfeito. Os campos não podem ser divididos acidentalmente no meio, como vimos com respostas erradas anteriores, e há apenas um nível de divisão, conforme necessário.Um problema é que a expansão do nome de arquivo corromperá as palavras afetadas como descrito anteriormente, embora mais uma vez isso possa ser resolvido envolvendo a instrução crítica em
set -f
eset +f
.Outro problema em potencial é que, como o LF se qualifica como um "caractere de espaço em branco do IFS", conforme definido anteriormente, todos os campos vazios serão perdidos, assim como nos itens 2 e 3 . Obviamente, isso não seria um problema se o delimitador não fosse um "caractere de espaço em branco do IFS" e, dependendo do aplicativo, isso pode não ter importância, mas vicia a generalidade da solução.
Então, para resumir, supondo que você tenha um delimitador de um caractere e ele seja um "caractere de espaço em branco do IFS" ou não se importe com campos vazios, envolva a instrução crítica em
set -f
eset +f
, então, esta solução funcionará , mas caso contrário não.(Além disso, para fins de informação, a atribuição de um LF a uma variável no bash pode ser feita mais facilmente com a
$'...'
sintaxe, por exemploIFS=$'\n';
.)Resposta errada # 5
Idéia semelhante:
Essa solução é efetivamente um cruzamento entre o número 1 (na
$IFS
definição de espaço entre vírgulas) e o número 2-4 (na medida em que utiliza a divisão de palavras para dividir a sequência em campos). Por causa disso, ele sofre com a maioria dos problemas que afligem todas as respostas erradas acima, como o pior de todos os mundos.Além disso, em relação à segunda variante, pode parecer que a
eval
chamada é completamente desnecessária, pois seu argumento é uma literal de string com aspas simples e, portanto, é estaticamente conhecida. Mas, na verdade, há um benefício muito óbvio em usareval
dessa maneira. Normalmente, quando você executar um comando simples que consiste em uma atribuição de variável única , ou seja, sem uma palavra de comando real que se lhe segue, a atribuição tem efeito no ambiente shell:Isso é verdade mesmo que o comando simples envolva várias atribuições de variáveis; novamente, desde que não haja uma palavra de comando, todas as atribuições de variáveis afetam o ambiente do shell:
Porém, se a atribuição de variável estiver anexada a um nome de comando (eu gosto de chamar isso de "atribuição de prefixo"), ela não afetará o ambiente do shell e, em vez disso, afetará apenas o ambiente do comando executado, independentemente de ser um ou externo:
Citações relevantes do manual do bash :
É possível explorar esse recurso de atribuição de variáveis para alterar
$IFS
apenas temporariamente, o que nos permite evitar todo o lance de salvar e restaurar como o que está sendo feito com a$OIFS
variável na primeira variante. Mas o desafio que enfrentamos aqui é que o comando que precisamos executar é em si uma mera atribuição de variáveis e, portanto, não envolveria uma palavra de comando para tornar a$IFS
atribuição temporária. Você pode pensar: por que não adicionar uma palavra de comando no-op à declaração como a: builtin
para tornar a$IFS
tarefa temporária? Isso não funciona porque tornaria a$array
atribuição temporária também:Então, estamos efetivamente em um impasse, um pouco complicado. Mas, quando
eval
executa seu código, ele é executado no ambiente do shell, como se fosse um código-fonte estático normal, e, portanto, podemos executar a$array
atribuição dentro doeval
argumento para que ela entre em vigor no ambiente do shell, enquanto a$IFS
atribuição de prefixo que O prefixo doeval
comando não sobreviverá aoeval
comando. Este é exatamente o truque que está sendo usado na segunda variante desta solução:Então, como você pode ver, é realmente um truque inteligente e realiza exatamente o que é necessário (pelo menos no que diz respeito à efetivação da atribuição) de uma maneira bastante óbvia. Na verdade, não sou contra esse truque em geral, apesar do envolvimento de
eval
; apenas tenha cuidado entre aspas simples a sequência de argumentos para se proteger contra ameaças à segurança.Mais uma vez, devido à aglomeração de problemas "pior de todos os mundos", essa ainda é uma resposta errada ao requisito do OP.
Resposta errada # 6
Hum ... o que? O OP tem uma variável de cadeia que precisa ser analisada em uma matriz. Essa "resposta" começa com o conteúdo literal da sequência de entrada colada em um literal de matriz. Eu acho que é uma maneira de fazer isso.
Parece que o respondente pode ter assumido que a
$IFS
variável afeta toda a análise do bash em todos os contextos, o que não é verdade. No manual do bash:Portanto, a
$IFS
variável especial é realmente usada apenas em dois contextos: (1) divisão de palavras que é executada após a expansão (ou seja, não ao analisar o código-fonte do bash) e (2) para dividir as linhas de entrada em palavras peloread
built-in.Deixe-me tentar deixar isso mais claro. Eu acho que pode ser bom fazer uma distinção entre análise e execução . O Bash deve primeiro analisar o código-fonte, que obviamente é um evento de análise , e depois executa o código, quando ocorre a expansão na imagem. A expansão é realmente um evento de execução . Além disso, discordo da descrição da
$IFS
variável que acabei de citar acima; em vez de dizer que a divisão de palavras é realizada após a expansão , eu diria que a divisão de palavras é realizada durante a expansão ou, talvez ainda mais precisamente, a divisão de palavras faz parte deo processo de expansão. A frase "divisão de palavras" refere-se apenas a esta etapa de expansão; ele nunca deve ser usado para se referir à análise do código-fonte do bash, embora, infelizmente, os documentos pareçam usar as palavras "split" e "words" muito. Aqui está um trecho relevante da versão linux.die.net do manual do bash:Você poderia argumentar que a versão GNU do manual é um pouco melhor, pois ela opta pela palavra "tokens" em vez de "words" na primeira frase da seção Expansão:
O ponto importante é
$IFS
que não altera a maneira como o bash analisa o código-fonte. A análise do código-fonte do bash é, na verdade, um processo muito complexo que envolve o reconhecimento dos vários elementos da gramática do shell, como seqüências de comandos, listas de comandos, pipelines, expansões de parâmetros, substituições aritméticas e substituições de comandos. Na maioria das vezes, o processo de análise do bash não pode ser alterado por ações no nível do usuário, como atribuições de variáveis (na verdade, existem algumas pequenas exceções a esta regra; por exemplo, consulte as váriascompatxx
configurações de shell, que pode alterar certos aspectos do comportamento de análise on-the-fly). As "palavras" / "tokens" upstream resultantes desse complexo processo de análise são expandidas de acordo com o processo geral de "expansão", conforme detalhado nos trechos da documentação acima, onde a divisão da palavra do texto expandido (expansível?) Para o downstream palavras é simplesmente uma etapa desse processo. A divisão de palavras apenas toca no texto que foi cuspido em uma etapa de expansão anterior; isso não afeta o texto literal que foi analisado diretamente da fonte pelo testream.Resposta errada # 7
Esta é uma das melhores soluções. Observe que voltamos a usar
read
. Eu não disse anteriormente que issoread
é inapropriado porque realiza dois níveis de divisão, quando precisamos apenas de um? O truque aqui é que você pode chamar deread
maneira que efetivamente apenas faça um nível de divisão, especificamente dividindo apenas um campo por invocação, o que exige o custo de ter que chamá-lo repetidamente em um loop. É um truque, mas funciona.Mas há problemas. Primeiro: quando você fornece pelo menos um argumento NAME
read
, ele ignora automaticamente os espaços em branco à esquerda e à direita em cada campo que é separado da sequência de entrada. Isso ocorre se$IFS
o valor padrão é definido ou não, conforme descrito anteriormente nesta postagem. Agora, o OP pode não se importar com isso para seu caso de uso específico e, de fato, pode ser um recurso desejável do comportamento de análise. Mas nem todo mundo que deseja analisar uma seqüência de caracteres em campos deseja isso. Existe uma solução, no entanto: Um uso não óbvio deread
é passar zero argumentos NAME . Nesse caso,read
armazenará toda a linha de entrada obtida do fluxo de entrada em uma variável denominada$REPLY
e, como bônus, ela nãotira o espaço em branco à esquerda e à esquerda do valor. Esse é um uso muito robusto, doread
qual tenho explorado frequentemente em minha carreira de programação de shell. Aqui está uma demonstração da diferença de comportamento:O segundo problema com esta solução é que na verdade não trata o caso de um separador de campo personalizado, como o espaço de vírgula do OP. Como antes, os separadores de vários caracteres não são suportados, o que é uma limitação infeliz dessa solução. Poderíamos tentar pelo menos dividir por vírgula especificando o separador para a
-d
opção, mas veja o que acontece:Previsivelmente, o espaço em branco ao redor não contabilizado foi atraído para os valores de campo e, portanto, isso teria que ser corrigido posteriormente por meio de operações de corte (isso também poderia ser feito diretamente no loop while). Mas há outro erro óbvio: a Europa está faltando! O que aconteceu com isso? A resposta é que
read
retorna um código de retorno com falha se atingir o final do arquivo (neste caso, podemos chamá-lo de final de string) sem encontrar um terminador de campo final no campo final. Isso faz com que o loop while pare prematuramente e perdemos o campo final.Tecnicamente, esse mesmo erro também afetou os exemplos anteriores; a diferença é que o separador de campo foi considerado LF, que é o padrão quando você não especifica a
-d
opção, e o<<<
mecanismo ("aqui-string") anexa automaticamente um LF à string imediatamente antes de alimentá-lo como entrada para o comando. Portanto, nesses casos, resolvemos acidentalmente o problema de um campo final descartado anexando inadvertidamente um terminador fictício adicional à entrada. Vamos chamar essa solução de solução "dummy-terminator". Podemos aplicar a solução dummy-terminator manualmente para qualquer delimitador personalizado, concatenando-a na cadeia de entrada quando instanciamos na cadeia here:Lá, problema resolvido. Outra solução é interromper o loop while apenas se ambos (1)
read
retornarem falha e (2)$REPLY
estiverem vazios, o que significa queread
não foi possível ler nenhum caractere antes de atingir o final do arquivo. Demo:Essa abordagem também revela o LF secreto que é automaticamente anexado à string here pelo
<<<
operador de redirecionamento. É claro que ele poderia ser retirado separadamente por meio de uma operação explícita de corte, conforme descrito há pouco, mas obviamente a abordagem manual de terminação fictícia resolve isso diretamente, para que possamos continuar com isso. A solução manual de terminação fictícia é realmente bastante conveniente, pois resolve esses dois problemas (o problema do campo final descartado e o problema de LF anexado) de uma só vez.Portanto, no geral, esta é uma solução bastante poderosa. A única fraqueza restante é a falta de suporte para delimitadores de vários caracteres, que abordarei mais adiante.
Resposta errada # 8
(Na verdade, é da mesma postagem que o nº 7 ; o atendedor forneceu duas soluções na mesma postagem.)
O
readarray
builtin, que é um sinônimomapfile
, é ideal. É um comando interno que analisa um bytestream em uma variável de matriz de uma só vez; sem mexer com loops, condicionais, substituições ou qualquer outra coisa. E não tira clandestinamente nenhum espaço em branco da string de entrada. E (se-O
não for fornecido), limpa convenientemente a matriz de destino antes de atribuir a ela. Mas ainda não é perfeito, daí a minha crítica a ela como uma "resposta errada".Primeiro, apenas para tirar isso do caminho, observe que, assim como o comportamento de
read
fazer uma análise de campo,readarray
descarta o campo à direita se estiver vazio. Novamente, isso provavelmente não é uma preocupação para o OP, mas pode ser para alguns casos de uso. Voltarei a isso daqui a pouco.Segundo, como antes, ele não suporta delimitadores de vários caracteres. Vou dar uma correção para isso em um momento também.
Terceiro, a solução escrita não analisa a cadeia de entrada do OP e, de fato, não pode ser usada como está para analisá-la. Vou expandir isso momentaneamente também.
Pelas razões acima, ainda considero que esta é uma "resposta errada" à pergunta do OP. Abaixo, darei o que considero a resposta certa.
Resposta correta
Aqui está uma tentativa ingênua de fazer o # 8 funcionar apenas especificando a
-d
opção:Vemos que o resultado é idêntico ao resultado obtido pela abordagem de dupla condicional da
read
solução de loop discutida no item 7 . Quase podemos resolver isso com o truque manual do terminador fictício:O problema aqui é que
readarray
preservou o campo à direita, pois o<<<
operador de redirecionamento anexou o LF à sequência de entrada e, portanto, o campo à direita não estava vazio (caso contrário, teria sido descartado). Podemos resolver isso desabilitando explicitamente o elemento final da matriz após o fato:Os únicos dois problemas que permanecem, que estão realmente relacionados, são (1) o espaço em branco estranho que precisa ser aparado e (2) a falta de suporte para delimitadores de vários caracteres.
É claro que o espaço em branco pode ser aparado posteriormente (por exemplo, consulte Como aparar o espaço em branco de uma variável Bash? ). Mas se pudermos hackear um delimitador de vários caracteres, isso resolveria os dois problemas de uma só vez.
Infelizmente, não há uma maneira direta de fazer funcionar um delimitador de vários caracteres. A melhor solução que eu pensei é pré-processar a sequência de entrada para substituir o delimitador de vários caracteres por um delimitador de um caractere que garantirá não colidir com o conteúdo da sequência de entrada. O único caractere que tem essa garantia é o byte NUL . Isso ocorre porque, no bash (embora não no zsh, aliás), as variáveis não podem conter o byte NUL. Esta etapa de pré-processamento pode ser realizada em linha em uma substituição de processo. Veja como fazer isso usando o awk :
Lá finalmente! Esta solução não dividirá erroneamente os campos no meio, não cortará prematuramente, não eliminará campos vazios, não se danificará nas expansões de nomes de arquivos, não removerá automaticamente os espaços em branco à esquerda e à direita, não deixará um LF clandestino no final, não requer loops e não aceita um delimitador de caractere único.
Solução de aparar
Por fim, queria demonstrar minha própria solução de aparar bastante complexa usando a
-C callback
opção obscura dereadarray
. Infelizmente, fiquei sem espaço contra o draconiano limite de 30.000 caracteres do Stack Overflow, por isso não poderei explicar. Vou deixar isso como um exercício para o leitor.fonte
-d
opção parareadarray
aparecer pela primeira vez no Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
e eliminar a concatenação da final", "
, não precisará passar pela ginástica para eliminar o registro final. Então:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
no Bash que suportareadarray
. Observe o seu método é Bash 4.4 ou superior Eu acho que por causa da-d
emreadarray
readarray
. Nesse caso, você pode usar a segunda melhor solução integradaread
. Estou me referindo a isso:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(com aawk
substituição, se você precisar de suporte ao delimitador de vários caracteres). Deixe-me saber se você tiver algum problema; Tenho certeza de que essa solução deve funcionar em versões bastante antigas do bash, de volta à versão 2 - lançada há duas décadas.Aqui está uma maneira sem definir o IFS:
A ideia é usar a substituição de string:
para substituir todas as correspondências de $ substring por espaço em branco e, em seguida, usar a sequência substituída para inicializar uma matriz:
Nota: esta resposta faz uso do operador split + glob . Portanto, para impedir a expansão de alguns caracteres (como
*
), é uma boa ideia fazer uma pausa no globbing deste script.fonte
${string//:/ }
evita desembolsar expansãoarray=(${string//:/ })
Imprime três
fonte
a=($(echo $t | tr ',' "\n"))
. Mesmo resultado coma=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
umabash
concha, e o últimoecho
apenas imprime uma linha em branco. Qual versão do Linux e qual shell você está usando? Infelizmente, não é possível exibir a sessão do terminal em um comentário.Às vezes me ocorreu que o método descrito na resposta aceita não funcionou, especialmente se o separador for um retorno de carro.
Nesses casos, resolvi desta maneira:
fonte
read -a arr <<< "$strings"
não funcionouIFS=$'\n'
.A resposta aceita funciona para valores em uma linha.
Se a variável tiver várias linhas:
Precisamos de um comando muito diferente para obter todas as linhas:
while read -r line; do lines+=("$line"); done <<<"$string"
Ou o muito mais simples readay do bash :
Imprimir todas as linhas é muito fácil, aproveitando o recurso printf:
fonte
Isso é semelhante à abordagem de Jmoney38 , mas usando sed:
Impressões 1
fonte
A chave para dividir sua string em uma matriz é o delimitador de vários caracteres
", "
. Qualquer solução usadaIFS
para delimitadores de vários caracteres é inerentemente errada, pois o IFS é um conjunto desses caracteres, não uma sequência.Se você atribuir
IFS=", "
, a sequência será interrompida em OU","
OU em" "
qualquer combinação deles que não seja uma representação precisa do delimitador de dois caracteres de", "
.Você pode usar
awk
oused
dividir a sequência, com a substituição do processo:É mais eficiente usar um regex diretamente no Bash:
Com o segundo formulário, não há sub shell e ele será inerentemente mais rápido.
Editar por bgoldst: Aqui estão alguns benchmarks que comparam minha
readarray
solução à solução regex dawg e também incluí aread
solução para o problema (nota: eu modifiquei levemente a solução regex para maior harmonia com minha solução) (também veja meus comentários abaixo do postar):fonte
$BASH_REMATCH
. Funciona e, de fato, evita sub-conchas. +1 de mim. No entanto, a título de crítica, o regex em si é um pouco não ideal, pois parece que você foi forçado a duplicar parte do token delimitador (especificamente a vírgula), para contornar a falta de suporte a multiplicadores não gananciosos (também pesquisas) no ERE (sabor regex "estendido" incorporado no bash). Isso o torna um pouco menos genérico e robusto.\n
linhas de texto delimitadas) compreendendo esses campos, de modo que a desaceleração catastrófica provavelmente não ocorreria. Se você tem uma string com 100.000 campos - talvez o Bash não seja o ideal ;-) Obrigado pela referência. Eu aprendi uma coisa ou duas.Solução delimitadora de vários caracteres do bash puro.
Como outros apontaram neste tópico, a pergunta do OP deu um exemplo de uma sequência delimitada por vírgula a ser analisada em uma matriz, mas não indicou se ele / ela estava interessado apenas em delimitadores de vírgula, delimitadores de caractere único ou multi-caractere delimitadores.
Como o Google tende a classificar essa resposta no topo ou perto dos resultados de pesquisa, eu queria fornecer aos leitores uma resposta forte à pergunta dos delimitadores de vários caracteres, pois isso também é mencionado em pelo menos uma resposta.
Se você está procurando uma solução para um problema de delimitador de vários caracteres, sugiro revisar a publicação de Mallikarjun M , em particular a resposta de gniourf_gniourf, que fornece esta solução BASH pura e elegante usando a expansão de parâmetros:
Link para o comentário citado / publicação referenciada
Link para a pergunta citada: Como dividir uma string em um delimitador de vários caracteres no bash?
fonte
Isso funciona para mim no OSX:
Se sua string tiver um delimitador diferente, substitua-o primeiro por um espaço:
Simples :-)
fonte
Outra maneira de fazer isso sem modificar o IFS:
Em vez de alterar o IFS para corresponder ao delimitador desejado, podemos substituir todas as ocorrências do delimitador desejado
", "
pelo conteúdo de$IFS
via"${string//, /$IFS}"
.Talvez isso seja lento para cordas muito grandes?
Isso se baseia na resposta de Dennis Williamson.
fonte
Me deparei com este post ao analisar uma entrada como: word1, word2, ...
nenhuma das opções acima me ajudou. resolveu usando o awk. Se isso ajudar alguém:
fonte
Tente isto
É simples. Se desejar, você também pode adicionar uma declaração (e também remover as vírgulas):
O IFS é adicionado para desfazer o acima, mas funciona sem ele em uma nova instância do bash
fonte
Podemos usar o comando tr para dividir a string no objeto da matriz. Funciona em MacOS e Linux
Outra opção usa o comando IFS
fonte
Usa isto:
fonte
array=( $string )
é um (infelizmente muito comum) antipattern: palavra cisão ocorre:string='Prague, Czech Republic, Europe'
; A expansão do nome do caminho ocorre:string='foo[abcd],bar[efgh]'
falhará se você tiver um arquivo chamado, por exemplo,food
oubarf
em seu diretório. O único uso válido de uma construção desse tipo é quandostring
é um glob.ATUALIZAÇÃO: Não faça isso, devido a problemas com a avaliação.
Com um pouco menos de cerimônia:
por exemplo
fonte
$
na sua variável e você vai ver ... eu escrever muitos scripts e eu nunca tive que usar um únicoeval
Aqui está o meu hack!
Dividir strings por strings é uma coisa bastante chata de se fazer usando o bash. O que acontece é que temos abordagens limitadas que funcionam apenas em alguns casos (divididas por ";", "/", "." E assim por diante) ou que temos vários efeitos colaterais nos resultados.
A abordagem abaixo exigiu várias manobras, mas acredito que funcionará para a maioria das nossas necessidades!
fonte
Para elementos com várias linhas, por que não algo como
fonte
Outra maneira seria:
Agora seus elementos são armazenados na matriz "arr". Para percorrer os elementos:
fonte
eval
truque). Sua solução deixa$IFS
definida como o valor de espaço de vírgula após o fato.Como existem muitas maneiras de resolver isso, vamos começar definindo o que queremos ver em nossa solução.
readarray
para esse fim. Vamos usá-lo.IFS
, repetir, usareval
ou adicionar um elemento extra e removê-lo.O
readarray
comando é mais fácil de usar com novas linhas como delimitador. Com outros delimitadores, ele pode adicionar um elemento extra à matriz. A abordagem mais limpa é primeiro adaptar nossa entrada a um formulário que funcione bemreadarray
antes de transmiti-la.A entrada neste exemplo não possui um delimitador de vários caracteres. Se aplicarmos um pouco de bom senso, é melhor entender como entrada separada por vírgula, para a qual cada elemento pode precisar ser aparado. Minha solução é dividir a entrada por vírgula em várias linhas, aparar cada elemento e passar tudo para
readarray
.fonte
Outra abordagem pode ser:
Depois disso, 'arr' é uma matriz com quatro strings. Isso não requer lidar com o IFS, ler ou qualquer outro material especial, portanto, muito mais simples e direto.
fonte