Usando a substituição de parâmetro em uma matriz Bash

Eu tenho o arquivo.txt que preciso ler em uma matriz Bash. Em seguida, preciso remover espaços, aspas duplas e tudo, exceto a primeira vírgula em cada entrada . Veja o quão longe eu cheguei:

$ cat file.txt 10,this 2 0 , i s 30,"all" 40,I 50,n,e,e,d,2 60",s e,e" $ cat script.sh #!/bin/bash readarray -t ARRAY<$1 ARRAY=( "${ARRAY[@]// /}" ) ARRAY=( "${ARRAY[@]//\"/}" ) for ELEMENT in "${ARRAY[@]}";do echo "|ELEMENT|$ELEMENT|" done $ ./script.sh file.txt |ELEMENT|10,this| |ELEMENT|20,is| |ELEMENT|30,all| |ELEMENT|40,I| |ELEMENT|50,n,e,e,d,2| |ELEMENT|60,se,e| 

O que funciona muito bem, exceto para a situação de vírgula. Estou ciente de que existem várias maneiras de esfolar esse gato, mas devido ao script maior do qual isso faz parte, eu realmente gostaria de usar a substituição de parâmetro para chegar aqui:

|ELEMENT|10,this| |ELEMENT|20,is| |ELEMENT|30,all| |ELEMENT|40,I| |ELEMENT|50,need2| |ELEMENT|60,see| 

Isso é possível por meio da substituição de parâmetro?

Comentários

  • Há algum motivo para você precisar manter o texto em uma matriz e por que você pode ‘ deixar, por exemplo, awk ou sed fazem o processamento dos dados?
  • @Jeff – O loop na matriz será um pesadelo para implementar no script maior em que ‘ estou trabalhando.
  • @JonRed Eu não ‘ não sei o que você está fazendo, então ‘ é inteiramente possível que você não tenha escolha no assunto, mas geralmente, quando você se pega fazendo tais acrobacias complexas de cordas no shell, que ‘ uma indicação muito boa de que você deve usar uma linguagem de programação real. O shell não foi projetado como uma linguagem de programação e, embora possa ser usado como tal, realmente não é ‘ uma boa idéia para coisas mais complexas. Eu recomendo fortemente que você considere mudar para perl ou python ou qualquer outra linguagem de script.
  • @terdon É ‘ engraçado, acabei de dizer quase o exato mesma coisa com meu colega antes de ler este post. Eu basicamente disse que esta é a versão final deste script e que quaisquer requisitos adicionais exigirão a reescrita em Perl. Então, sim, eu definitivamente concordo

Resposta

Eu removeria o que você precisa remover usando sed antes de carregar na matriz (observe também os nomes de variáveis em minúsculas, em geral é melhor evitar variáveis em maiúsculas em scripts de shell):

#!/bin/bash readarray -t array< <(sed "s/"//g; s/ *//g; s/,/"/; s/,//g; s/"/,/" "$1") for element in "${array[@]}";do echo "|ELEMENT|$element|" done 

Isso produz a seguinte saída em seu arquivo de exemplo:

$ foo.sh file |ELEMENT|10,this| |ELEMENT|20,is| |ELEMENT|30,all| |ELEMENT|40,I| |ELEMENT|50,need2| |ELEMENT|60,see| 

Se você realmente deve usar o parâmetro substituição, tente algo assim:

#!/bin/bash readarray -t array< "$1" array=( "${array[@]// /}" ) array=( "${array[@]//\"/}" ) array=( "${array[@]/,/\"}" ) array=( "${array[@]//,/}" ) array=( "${array[@]/\"/,}" ) for element in "${array[@]}"; do echo "|ELEMENT|$element|" done 

Comentários

  • @JonRed adicionei uma versão com parâmetro substituição, mas ‘ é complexo, pesado e feio. Fazer esse tipo de coisa no shell raramente é uma boa ideia.
  • Observe que se você ‘ remover os espaços e aspas duplas, esses caracteres ficarão disponíveis para usar em vez de RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
  • @Kusalananda sim, acabei de ler sua resposta. Deveria ter pensado nisso! Obrigado 🙂
  • Responde diretamente à pergunta, ilustra por que minha solução preferida não é ‘ t ideal e fornece a alternativa mais viável. Você venceu, melhor resposta.

Resposta

Pelo que eu posso ver, não há necessidade de leia-o em uma matriz bash para criar essa saída:

$ sed "s/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/" <file |ELEMENT|10,this| |ELEMENT|20,is| |ELEMENT|30,all| |ELEMENT|40,I| |ELEMENT|50,need2| |ELEMENT|60,see| 

O sed expression exclui espaços e aspas duplas, substitui a primeira vírgula por um espaço (não há outros espaços na string neste ponto), exclui todas as outras vírgulas, restaura a primeira vírgula e os prefixa e acrescenta os dados extras .

Alternativamente, com GNU sed:

sed "s/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/" <file 

(padrão sed não suporta a combinação de 2 e g como sinalizadores para s comando).

Comentários

  • com GNU sed, você pode usar 's/,//2g para remover vírgulas, começando com o segundo
  • E os últimos dois comandos s /// podem ser s/.*/|ELEMENT|&|/ mas isso pode ser mais difícil para o sed.
  • @glennjackman Possivelmente, mas parece bem legal.
  • Sim, isso é parte de um script maior. A matriz é necessária, não apenas para a saída. Daí o meu interesse na substituição de parâmetros. Eu poderia fazer um loop no array com isso, mas será um pesadelo para implementar. Terndon forneceu uma solução sem loop usando sed que eu ‘ provavelmente recorrerei se a substituição de parâmetro for proibida.
  • Se eu não fosse ‘ t vinculado ao uso de uma matriz, no entanto, esta seria a melhor solução.

Resposta

ELEMENT="50,n,e,e,d,2" IFS=, read -r first rest <<<"$ELEMENT" printf "%s,%s\n" "$first" "${rest//,/}" 
50,need2 

Abandone o hábito de usar nomes de variáveis ALLCAPS. Você eventualmente irá colidir com uma variável de “sistema” crucial como PATH e quebrar seu código.

Comentários

  • Não substituição de parâmetro. MAS, eu não sabia que nomes de variáveis ALLCAPS eram um mau hábito no Bash. Você fez um bom argumento, que uma pesquisa rápida no Google definitivamente confirma. Obrigado por melhorar meu estilo! 🙂
  • Eu ‘ respondi a perguntas em que a pessoa escreveu PATH=something; ls $PATH e depois perguntou sobre ls: command not found erro.
  • Existem quase uma centena de variáveis integradas que são nomeadas em maiúsculas (clique nesta página de manual link ) para ver …

Resposta

[Este é essencialmente um mais desenvolvido versão da resposta de glenn jackmann ]

Construindo uma matriz associativa a partir da chave e valor retirados, usando a primeira vírgula como separador:

declare -A arr while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt for k in "${!arr[@]}"; do printf "|ELEMENT|%s,%s|\n" "$k" "${arr[$k]}" done |ELEMENT|20,is| |ELEMENT|10,this| |ELEMENT|50,need2| |ELEMENT|40,I| |ELEMENT|60,see| |ELEMENT|30,all| 

Resposta

Você pode fazer um loop na matriz e usar uma variável intermediária:

for((i=0; i < "${#ARRAY[@]}"; i++)) do rest="${ARRAY[i]#*,}" ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}" done 

Isso atribui a rest a parte após a primeira vírgula; em seguida, concatenamos três peças de volta ao original variável:

  • a parte antes da primeira vírgula
  • uma vírgula
  • a substituição em rest de cada vírgula por nada

Comentários

  • Este foi meu primeiro pensamento e é simples o suficiente para o exemplo, mas isso é parte de um script maior, onde o array é enorme e ‘ s já faz loops e seria uma coisa inteira. Isso definitivamente funcionaria, mas seria muito complicado de implementar no projeto maior em que ‘ estou trabalhando.
  • Razoável; Eu apenas tentei responder dentro das limitações (apenas expansão de parâmetro).

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *