AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 789097
Accepted
Zero
Zero
Asked: 2025-01-05 20:30:37 +0800 CST2025-01-05 20:30:37 +0800 CST 2025-01-05 20:30:37 +0800 CST

bash - divide substrings entre aspas delimitadas por espaços em uma matriz

  • 772

exemplo a seguir:

string=" 'f o o' 'f oo' 'fo o' "
array=($string)
echo "${array[0]}"

saídas:

'f

enquanto a saída esperada é:

'f o o'

A única solução que encontrei foi alterar o delimitador para nova linha (em vez de espaço):

IFS=$'\n'
string="'f o o' # 'f oo' # 'fo o' #"
array=($(echo "$string" | tr '#' '\n'))
echo "${array[0]}"

saídas:

'f o o'

que produz o resultado esperado, no entanto, isso requer definir um delimitador personalizado em string e invocará um novo subprocesso em cada chamada. Gostaria de ver uma solução melhor do que essa, obrigado!

Editar:

A string de exemplo acima deve ser suficiente, mas como alguns perguntaram como é o conteúdo do arquivo, é algo parecido com isto:

'magic1' 'some text' 'another simple text' 'some t£xt'
'magic2' 'another line with simple text' 'foo bar' 'yeah another substring'
'magic3' 'so@m{e$%& te+=*&' 'another substring' 'here is the substring'
'magic4' 'why not another line !' 'yeah I like that' 'the substring yeah !!!'

Ainda mais detalhes (como alguns usuários solicitaram): há uma função personalizada que grepretorna a linha do arquivo acima usando um substring.

por exemplo, minha função find é semelhante a esta:

_find()
{
   string="$(grep "$1" "$file")"
}

então eu chamo assim:

_find "magic2"
echo "$string"

que produz:

'magic2' 'another line with simple text' 'foo bar' 'yeah another substring'

P/S: cada substring é delimitada por espaço, e cada substring é citada entre aspas simples. _findNão é possível editá-la, pois ela é usada por outras funções. Veja os comentários para mais informações.

bash
  • 4 4 respostas
  • 148 Views

4 respostas

  • Voted
  1. Ed Morton
    2025-01-06T20:35:15+08:002025-01-06T20:35:15+08:00

    Se a entrada for tão simples quanto mostrada na pergunta e você realmente quiser evitar a geração de um subshell em cada invocação, como você declarou:

    ... no entanto, isso ... invocará um novo subprocesso em cada chamada. Eu gostaria de ver uma solução melhor do que isso

    então você poderia usar um loop isolando cada '...'substring e armazenando-a em uma matriz:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    string=" 'f o o' 'f oo' 'fo o' "
    re="('[^']*')(.*)"
    array=()
    while [[ "$string" =~ $re ]]; do
        array+=( "${BASH_REMATCH[1]}" )
        string="${BASH_REMATCH[2]}"
    done
    
    declare -p array
    

    $ ./tst.sh
    declare -a array=([0]="'f o o'" [1]="'f oo'" [2]="'fo o'")
    

    Se você não se importar com um subshell, você pode usar o GNU grep para -oapenas imprimir as substrings entre aspas simples e então lê-las em um array:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    string=" 'f o o' 'f oo' 'fo o' "
    
    readarray -t array < <(grep -o "'[^']*'" <<<"$string")
    
    declare -p array
    

    $ ./tst.sh
    declare -a array=([0]="'f o o'" [1]="'f oo'" [2]="'fo o'")
    

    ou se você não tiver um grep que suporte isso, -ovocê pode usar qualquer sed para remover espaços em branco iniciais/finais e alterar tudo ' 'para '<newline>'então ler o resultado em uma matriz:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    string=" 'f o o' 'f oo' 'fo o' "
    
    readarray -t array < <(sed -E "s/^ +| +$//g; s/' +'/'\n'/g" <<<"$string")
    
    declare -p array
    

    $ ./tst.sh
    declare -a array=([0]="'f o o'" [1]="'f oo'" [2]="'fo o'")
    

    Se você quiser se livrar do 's do resultado, basta alterar o sedcomando para sed -E "s/^ +'|' +$//g; s/' +'/\n/g".

    • 3
  2. Stéphane Chazelas
    2025-01-05T22:00:29+08:002025-01-05T22:00:29+08:00

    Se o objetivo é fazer a tokenização da sintaxe da linguagem shell, mas sem remover aspas e sem executar nenhum código, então você pode alternar para zsho que tem operadores apenas para isso: o z sinalizador de expansão de parâmetro e sua Z[tuning]variante para personalizar o que acontece com relação a novas linhas e comentários.

    Com um exemplo mais complexo:

    string="
      'f o o' 'f oo' 'fo o'
      \"bar
    baz\" ''
      $'a\'\nb'
      'foo'\''bar'
      # comment
      >>file
      \$(echo foo bar)
    "
    

    Com apenas z:

    $ array=(${(z)string})
    $ typeset -p1 array
    typeset -a array=(
      ';'
      \''f o o'\'
      \''f oo'\'
      \''fo o'\'
      ';'
      $'"bar\nbaz"'
      \'\'
      ';'
      '$'\''a\'\''\nb'\'
      ';'
      \''foo'\''\'\'\''bar'\'
      ';'
      '#'
      comment
      ';'
      '>>'
      file
      ';'
      '$(echo foo bar)'
      ';'
    )
    

    A nova linha é tratada como delimitador de comando, assim como ;(e tokenizada como ;) e #não é tratada como introdutor de comentário.

    $ array=(${(Z[n])string})
    $ typeset -p1 array
    typeset -a array=(
      \''f o o'\'
      \''f oo'\'
      \''fo o'\'
      $'"bar\nbaz"'
      \'\'
      '$'\''a\'\''\nb'\'
      \''foo'\''\'\'\''bar'\'
      '#'
      comment
      '>>'
      file
      '$(echo foo bar)'
    )
    

    Com Z[n], a nova linha sem aspas é tratada apenas como outro delimitador de token, como espaço ou tabulação.

    $ array=(${(Z[nc])string})
    $ typeset -p1 array
    typeset -a array=(
      \''f o o'\'
      \''f oo'\'
      \''fo o'\'
      $'"bar\nbaz"'
      \'\'
      '$'\''a\'\''\nb'\'
      \''foo'\''\'\'\''bar'\'
      '# comment'
      '>>'
      file
      '$(echo foo bar)'
    )
    

    Com c, #introduz um comentário, e os comentários compõem o token retornado.

    $ array=(${(Z[nC])string})
    $ typeset -p1 array
    typeset -a array=(
      \''f o o'\'
      \''f oo'\'
      \''fo o'\'
      $'"bar\nbaz"'
      \'\'
      '$'\''a\'\''\nb'\'
      \''foo'\''\'\'\''bar'\'
      '>>'
      file
      '$(echo foo bar)'
    )
    

    Com C, o mesmo, exceto que os comentários são removidos.

    Se você tiver que usar bash, já que o bash não pode armazenar NULs em suas variáveis ​​de qualquer maneira, você pode fazer o bash obter o zsh para fazer a tokenização e imprimir o resultado delimitado por NUL, que o bash pode ler de volta em uma matriz readarray -td ''(assumindo o bash 4.4 ou mais recente):

    bash-5.2$ readarray -td '' array < <(zsh -c 'print -rNC1 -- ${(Z[Cn])1}' zsh "$string")
    bash-5.2$ typeset -p array
    declare -a array=([0]="'f o o'" [1]="'f oo'" [2]="'fo o'")
    

    Ou se em um sistema com a implementação GNU grepcom suporte para PCRE/PCRE2 habilitado, você poderia fazer um processamento de aspas simples, aspas duplas e barras invertidas semelhante ao feito por shells do tipo Bourne, como o bash, com:

    readarray -td '' array < <(
      printf '%s\0' "$string" |
        grep -zPo "(?s:'.*?'|\"(?:\\\\.|[^\\\\\"])*?\"|\\\\.|\S)+"
    )
    

    No exemplo "mais complexo" acima, isso dá:

    bash-5.2$ typeset -p array
    declare -a array=(
      [0]="'f o o'"
      [1]="'f oo'"
      [2]="'fo o'"
      [3]=$'"bar\nbaz"'
      [4]="''"
      [5]="\$'a\\'\\nb'"
      [6]="'foo'\\''bar'"
      [7]="#"
      [8]="comment"
      [9]=">>file"
      [10]="\$(echo"
      [11]="foo"
      [12]="bar)"
    )
    

    Note que ele não faz tokenização de sintaxe de shell completa, apenas processa aspas. Veja como ele divide $(echo foo bar), ele também dividiria $'foo\' bar'or "$(echo '"' foo)".

    Para melhor portabilidade, você pode usar o perl (o Pin grep -P) que, ao contrário do GNU, grepdeve ser instalado na maioria dos sistemas:

    readarray -td '' array < <(
      printf '%s\0' "$string" |
        perl -C -0lne "print for /(?s:'.*?'|\"(?:\\\\.|[^\\\\\"])*?\"|\\\\.|\S)+/g"
    )
    
    • 2
  3. Best Answer
    Chris Davies
    2025-01-06T04:56:31+08:002025-01-06T04:56:31+08:00

    O mais próximo que posso chegar é isso

    # Example data line
    s=" 'f o o' 'f oo' 'fo o' "
    
    # Read parts into temporary array t
    readarray -td "'" t <<<"$s"
    
    # Extract relevant parts of t into array a, re-adding the quote marks as we go
    a=()
    for ((n=1; n<"${#t[@]}"; n+=2)); do a+=("'${t[$n]}'"); done
    
    # Print the result
    for x in "${a[@]}"; do echo ">> $x <<"; done
    

    Saída do seu exemplo simples

    >> 'f o o' <<
    >> 'f oo' <<
    >> 'fo o' <<
    

    Especificamente para seu exemplo, observe que "${a[0]}"contém sete caracteres 'f o o'(incluindo as aspas)

    Saída do seu exemplo estendido

    >> 'magic1' <<
    >> 'some text' <<
    >> 'another simple text' <<
    >> 'some t£xt' <<
    
    >> 'magic2' <<
    >> 'another line with simple text' <<
    >> 'foo bar' <<
    >> 'yeah another substring' <<
    
    >> 'magic3' <<
    >> 'so@m{e$%& te+=*&' <<
    >> 'another substring' <<
    >> 'here is the substring' <<
    
    >> 'magic4' <<
    >> 'why not another line !' <<
    >> 'yeah I like that' <<
    >> 'the substring yeah !!!' <<
    
    • 2
  4. jesse_b
    2025-01-05T21:05:38+08:002025-01-05T21:05:38+08:00

    Você pode converter suas aspas simples em novas linhas para construir uma matriz:

    mapfile -t array <<< "${string//\'/$'\n'}"
    

    Isso criará alguns elementos vazios que podem ou não ser um problema para você:

    $ declare -p array
    declare -a array=([0]=" " [1]="f o o" [2]=" " [3]="f oo" [4]=" " [5]="fo o" [6]=" ")
    

    para que você possa removê-los:

    for i in ${!array[@]}; do [[  ${array["i"]} == " " || -z "${array["i"]}" ]] && unset 'array[i]'; done
    

    Agora você ainda não tem suas aspas simples, que, como argumentei nos comentários, você provavelmente não precisa, por exemplo:

    $ set -x
    + set -x
    $ foo='f o o'
    + foo='f o o'
    $ bar="'b a r'"
    + bar=''\''b a r'\'''
    $ baz=baz
    + baz=baz
    $ echo "$foo" "$bar" "$baz" 'bing'
    + echo 'f o o' ''\''b a r'\''' baz bing
    f o o 'b a r' baz bing
    

    Observe como aspas simples armazenadas literalmente em uma variável não são as mesmas que aspas simples interpretadas pelo shell.

    Entretanto, se você realmente quiser que eles voltem, você pode fazer isso (em vez do loop acima que só remove os elementos " "):

    for i in ${!array[@]}; do 
      if [[  ${array["i"]} == " " || -z "${array["i"]}" ]]; then
        unset 'array[i]'
      else
        array["$i"]="'${array["$i"]}'"
      fi
    done
    
    • 1

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve