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 / coding / Perguntas / 79549378
Accepted
Bryan Tan
Bryan Tan
Asked: 2025-04-02 07:34:05 +0800 CST2025-04-02 07:34:05 +0800 CST 2025-04-02 07:34:05 +0800 CST

Como faço para corrigir comandos sed que ficam extremamente lentos quando a carga está alta?

  • 772

Tenho um script bash que pega um arquivo de propriedades simples e substitui os valores em outro arquivo. (O arquivo de propriedades é apenas linhas de propriedades do tipo 'foo=bar')

INPUT=`cat $INPUT_FILE`
while read line; do
   PROP_NAME=`echo $line | cut -f1 -d'='`
   PROP_VALUE=`echo $line | cut -f2- -d'=' | sed 's/\$/\\\$/g`
   time INPUT="$(echo "$INPUT" | sed "s\`${PROP_NAME}\b\`${PROP_VALUE}\`g")"
done <<<$(cat "$PROPERTIES_FILE")
# Do more stuff with INPUT

No entanto, quando minha máquina tem carga alta (mais de quarenta anos), tenho uma grande perda de tempo em meus seds

real  0m0.169s
user  0m0.001s
sys  0m0.006s

Baixa carga:

real  0m0.011s
user  0m0.002s
sys  0m0.004s

Normalmente, perder 0,1 segundo não é um grande problema, mas tanto o arquivo de propriedades quanto os arquivos de entrada têm centenas/milhares de linhas e esses 0,1 segundo somam mais de uma hora de tempo desperdiçado.

O que posso fazer para consertar isso? Preciso apenas de mais CPUs?

Propriedades de exemplo (as linhas começam com um caractere especial para criar uma maneira de indicar que algo na entrada está tentando acessar uma propriedade)

$foo=bar
$hello=world
^hello=goodbye

Entrada de amostra

This is a story about $hello. It starts at a $foo and ends in a park.

Bob said to Sally "^hello, see you soon"

Resultado esperado

This is a story about world. It starts at a bar and ends in a park.

Bob said to Sally "goodbye, see you soon"
linux
  • 7 7 respostas
  • 255 Views

7 respostas

  • Voted
  1. Best Answer
    Jetchisel
    2025-04-02T09:04:34+08:002025-04-02T09:04:34+08:00

    Uma ideia/abordagem usando bashe sed, você pode tentar algo como:

    #!/usr/bin/env bash
    
    while IFS='=' read -r prop_name prop_value; do
      if [[ "$prop_name" == "^"* ]]; then
         prop_name="\\${prop_name}"
      fi
      input_value+=("s/${prop_name}\\b/${prop_value}/g")
    done < properties.txt
    
    sed_input="$(IFS=';'; printf '%s' "${input_value[*]}")"
    
    sed "$sed_input" sample_input.txt
    

    Uma maneira de verificar o valor de sed_inputé

    declare -p sed_input
    

    Ou

    printf '%s\n' "$sed_input"
    

    • Incorporar um utilitário externo do bash dentro de um loop de shell como cute seddeve ser evitado. Veja why-is-using-a-shell-loop-to-process-text-considered-bad-practice

    • A sedinvocação acima é executada apenas uma vez, mesmo que o arquivo que precisa ser editado tenha mais de 500 linhas.

    • Veja Como posso ler um arquivo (fluxo de dados, variável) linha por linha (e/ou campo por campo)?

    • Veja Como posso usar variáveis ​​de array no bash?

    • Veja Expansão de Parâmetros

    • Veja Howto_Parameter_Expansion

    • Veja Como_eu_faço_manipulação_de_strings_no_bash

    • 5
  2. markp-fuso
    2025-04-02T10:08:48+08:002025-04-02T10:08:48+08:00

    Adicionando linhas adicionais ao arquivo de entrada do OP para demonstrar a correspondência de limites de palavras e uma propriedade nameque ocorre mais de uma vez em uma linha:

    $ cat input.txt
    This is a story about $hello. It starts at a $foo and ends in a park.
    
    Bob said to Sally "^hello, see you soon"
    
    Leave first 2 matches alone: $foobar $hellow ^hello
    ^hello $foo $hello ^hello $foo $hello
    

    Suposições:

    • para correspondência de limites de palavras, é suficiente verificar se o caractere imediatamente após uma propriedade correspondente namenão é um caractere alfabético ( [a-zA-Z]); caso contrário, podemos expandir o next_charteste (veja awko código abaixo)

    Ideia geral:

    • leia todas properties.txtas entradas em uma matriz ( map[name]=value)
    • para cada linha de input.txt, faça um loop por todas names, verificando se há alguma correspondência de limite de palavra para substituir

    Uma ideia usando awk:

    $ cat replace.awk
    
    FNR==NR { split($0,arr,"=")                             # 1st file: split on "=" delimiter
              map[arr[1]]=arr[2]                            # build map[name]=value array, eg: map[$foo]=bar
              len[arr[1]]=length(arr[1])                    # save length of "name" so we do not have to repeatedly calculate later
              next
            }
    
    NF      { newline = $0                                  # 2nd file: if we have at least one non white space field then make copy of current input line
    
              for (name in map) {                           # loop through all "names" to search for 
                  line    = newline                         # start over copy of current line
                  newline = ""
    
                  while ( pos = index(line,name) ) {        # while we have a match ...
    
                        # find next_character after "name"; if it is an
                        # alpha/numeric character we do not have a word
                        # boundary otherwise we do have a word boundary
                        # and we need to make the replacement with 
                        # map[name]=value
                        
                        next_char = substr(line,pos+len[name],1)
    
                        if (next_char ~ /[[:alnum:]]/)
                           newline = newline substr(line,1,pos+len[name]-1)
                        else
                           newline = newline substr(line,1,pos-1) map[name]
    
                        line = substr(line,pos+len[name])   # strip off rest of line to test for additional matches of "name"
                  }
                  newline = newline line                    # append remaining contents of line
              }
              $0 = newline                                  # overwrite current input line with "newline"
            }
    1                                                       # print current line
    

    NOTAS:

    • a maioria awk das funções de correspondência de strings (por exemplo, sub(), gsub(), match()) tratam o padrão de pesquisa como uma expressão regular
    • isso significa que os caracteres não alfabéticos no arquivo de propriedades do OP (por exemplo, $, ^) precisarão ser escapados antes de tentar usarsub() / gsub() / match()
    • em vez de passar por obstáculos para escapar de todos os caracteres especiais, optei por usar ...
    • a index()função trata os padrões de pesquisa como texto literal (portanto, não há necessidade de escapar caracteres especiais)

    Fazendo um test drive:

    $ awk -f replace.awk properties.txt input.txt
    This is a story about world. It starts at a bar and ends in a park.
    
    Bob said to Sally "goodbye, see you soon"
    
    Leave first 2 matches alone: $foobar $hellow goodbye
    goodbye bar world goodbye bar world
    

    Por questões de tempo, criei alguns arquivos maiores a partir do arquivo de propriedades do OP e do meu input.txtarquivo (veja acima):

    $ awk 'BEGIN {FS=OFS="="} {map[$1]=$2} END {for (i=1;i<=300;i++) {for (name in map) {nn=name x;print nn,map[name]};x++}}' properties.txt > properties.900.txt
    
    $ for ((i=1;i<=250;i++)); do cat input.txt; done > input.1500.txt
    
    $ wc -l properties.900.txt input.1500.txt
      900 properties.900.txt
     1500 input.1500.txt
    

    Cronometragem para arquivos de dados maiores:

    $ time awk -f replace.awk properties.900.txt input.1500.txt > output
    
    real    0m0.126s
    user    0m0.122s
    sys     0m0.004s
    
    $ head -12 output
    This is a story about world. It starts at a bar and ends in a park.
    
    Bob said to Sally "goodbye, see you soon"
    
    Leave first 2 matches alone: $foobar $hellow goodbye
    goodbye bar world goodbye bar world
    This is a story about world. It starts at a bar and ends in a park.
    
    Bob said to Sally "goodbye, see you soon"
    
    Leave first 2 matches alone: $foobar $hellow goodbye
    goodbye bar world goodbye bar world
    

    NOTA: o tempo é de um sistema Ubuntu 22.04 (metal, vm) rodando em um Intel i7-1260P

    • 5
  3. KamilCuk
    2025-04-02T08:42:23+08:002025-04-02T08:42:23+08:00

    O que posso fazer para corrigir isso?

    Refatore sua ideia para escrevê-la em uma única linguagem de programação de alto desempenho. Bash é um shell - ele executa outros programas. Cada programa leva tempo para iniciar.

    Você pode gerar um script sed de uma só vez e então executá-lo. Note que isso não manipulará ^helloou quaisquer outros . * [ ? \caracteres corretamente, pois sed trabalha com regex. ^corresponde ao início de uma linha.

    sed "$(sed 's/\([^=]*\)=\(.*\)/s`\1\\b`\2`g/g' "$PROPERTIES_FILE")" "$INPUT_FILE"
    

    Você poderia escapar dos caracteres especiais com algo parecido com isso. Veja também https://stackoverflow.com/a/2705678/9072753 .

    sed "$(sed 's/[]\/$*.^&[]/\\&/g; s/\([^=]*\)=\(.*\)/s`\1\\b`\2`g/g; ' "$PROPERTIES_FILE")" "$INPUT_FILE"
    

    Notas: use shellcheck. Use $(...) em vez de acentos graves. Não abuse de cats - apenas use <fileem vez de <<<$(cat "$PROPERTIES_FILE"). Não GRITE - considere variáveis ​​em minúsculas. Considere m4, envsubst ou jinja2 ou apenas cpp para modelagem.

    • 4
  4. Paul Hodges
    2025-04-02T09:30:06+08:002025-04-02T09:30:06+08:00

    Concordo que isso seria muito mais eficiente em awkou perlou python, etc...

    Mas para responder à pergunta feita, sim, você pode tornar isso muito mais eficiente com as ferramentas que você tem. Como mencionado, livre-se dos desperdiçadores de tempo. Seu código original gera processos desnecessários em praticamente todas as linhas .

    Basta fazer com que o código faça uma passagem pelo arquivo para gravar todos os sedcomandos de substituição individuais em outro arquivo de script (ou acumulá-los em uma string, como o Jetchisel sugere) e então executá-lo.

    $ cat props
    $foo=bar
    $hello=world
    ^hello=goodbye
    
    $ cat editme
    This is a story about $hello. It starts at a $foo and ends in a park.
    
    Bob said to Sally "^hello, see you soon"
    
    $ cat editme.new
    cat: editme.new: No such file or directory
    
    $ cat script
    #!/bin/bash
    date +'Inital timestamp: %D %T %N' >&2
    { printf '%s\n' '#!/bin/bash' "time sed '"
      date +'Starting read of props file: %D %T %N' >&2
      while IFS='=' read -r k v;
      do printf '  s`%q\\b`%q`g;\n' "$k" "$v"
      done < props
      date +'Closing sed command: %D %T %N' >&2
      printf '%s\n' "' editme > editme.new"
    } > editor
    date +'Done writing sed script file: %D %T %N' >&2
    cat editor
    . editor
    

    As duas saídas de tempo na parte inferior são para uma execução sede para o script inteiro, respectivamente.

    $ time ./script
    Inital timestamp: 04/01/25 20:21:21 799559000
    Starting read of props file: 04/01/25 20:21:21 812337100
    Closing sed command: 04/01/25 20:21:21 824636300
    Done writing sed script file: 04/01/25 20:21:21 837483000
    #!/bin/bash
    time sed '
      s`\$foo\b`bar`g;
      s`\$hello\b`world`g;
      s`\^hello\b`goodbye`g;
    ' editme > editme.new
    
    real    0m0.014s
    user    0m0.000s
    sys     0m0.016s
    
    real    0m0.104s
    user    0m0.075s
    sys     0m0.046s
    

    e depois -

    $ cat editme.new
    This is a story about world. It starts at a bar and ends in a park.
    
    Bob said to Sally "goodbye, see you soon"
    

    adenda

    A maioria dos scripts bash se beneficiam muito ao mover subshells para built-ins.

    Uma versão simplificada do meu sedscript baseado em - acima:

    $ cat script
    #!/bin/bash
    { printf '%s\n' '#!/bin/bash' "sed '"
      while IFS='=' read -r k v; do printf '  s`%q\\b`%q`g;\n' "$k" "$v"; done < props
      printf '%s\n' "' editme > editme.new"
    } > editor
    . editor
    
    $ time ./script
    
    real    0m0.043s
    user    0m0.000s
    sys     0m0.031s
    

    Usando processamento simples de string bash para tudo -

    $ cat v2
    #!/bin/bash
    text="$(<editme)"
    while IFS='=' read -r k v;
    do while [[ "$text" =~ "$k" ]]; do text="${text//$k/$v}"; done
    done < props
    echo "$text" > editme.2
    
    $ time ./v2
    
    real    0m0.011s
    user    0m0.015s
    sys     0m0.000s
    
    $ diff editme.new editme.2
    
    

    Isso tem um desempenho horrível em um arquivo grande, no entanto, por vários motivos. Eu fiz um arquivo de quase 400 MB e o sedscript lidou com ele em cerca de 12,5s. Eu quebrei a versão all-in-memory all-bash em pouco menos de 3m.

    • 4
  5. jhnc
    2025-04-02T21:08:50+08:002025-04-02T21:08:50+08:00

    Seu código parece ser executado em tempo O(mn) procurando por mpossíveis propriedades na entrada de tamanho n.

    Como "tanto o arquivo de propriedades quanto os arquivos de entrada têm centenas/milhares de linhas", melhorar isso para o tempo O(n) pode proporcionar uma aceleração perceptível:

    perl -e '
        # load mapping data into hash
        while ( ($k,$v) = split "=",<<>>,2 ) {
            chomp $v;
            $k2v{$k} = $v;
            last if eof;
        }
    
        # build regex from all keys (\Q escapes regex metacharacters)
        $re = join "|", map qr/\Q$_\E/, keys %k2v;
    
        # load input file as single string
        undef $/;
        $_ = <<>>;
    
        # convert all properties simultaneously
        s/($re)\b/ $k2v{$1} /ge;
    
        # output the result
        print;
    
    ' propfile inputfile
    

    Isso faz uso de uma otimização de regex Perl que permite verificar alternâncias de strings literais em tempo constante, em vez de linear.

    Presumo que a reescrita recursiva não seja desejada. Por exemplo, aplicando:

    $key1=$key2
    $key2=value
    

    para blah $key1deveria resultar em blah $key2e nãoblah value


    Também pode ser possível processar vários inputfileem um loop para que os dados de mapeamento precisem ser carregados apenas uma vez, mas será necessário adicionar algum código adicional para salvar cada saída em vez de apenas gravar no stdout.

    • 4
  6. Luis Colorado
    2025-04-02T19:18:52+08:002025-04-02T19:18:52+08:00

    Hmmm..... você escreve

    INPUT=`cat $INPUT_FILE`  # you start a process here for cat.
    while read line
    do  
        PROP_NAME=`echo $line | cut -f1 -d'='`  # you start two processes here
        PROP_VALUE=`echo $line | cut -f2- -d'='`   # you start two processes here
        time INPUT="$(echo "$INPUT" | sed "s\`${PROP_NAME}\b\`${PROP_VALUE}\`g")" # two more processes
    done <<<$(cat "$PROPERTIES_FILE")# Do more stuff with INPUT  # and one more here.
    

    Você não especifica o formato de $INPUT_FILEou $PROPERTIES_FILE, então é pouca ajuda que eu possa lhe dar, mas sugiro que você coloque tudo em um único pipeline de comandos, cada um fazendo algum processo para todo o conjunto de dados. Algo como:

    # this command generates substitution commands for all property name and value pairs
    # in the form or `-e` commands for _sed(1)_ to make all substitutions in one shot.
    # the pairs are in format VARIABLE = value  --->  -e 's"@VARIABLE@"value"g'
    SED_PARAMETERS=$(
    sed -E                                                                         \
        -e 's"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$"-e '\''s\"@\1@\"\2\"g'\''"' \
        < "${PROPERTIES_FILE}" # first group is PROP_NAME, second is PROP_VALUE
    )
    # then, a sed command is run with this parameters on the input to produce a
    # substitution made file
    sed -E ${SED_PARAMETERS} < "${INPUT_FILE}" # outputs the transformed file to stdout.
        
    

    Dessa forma, apenas dois programas são executados:

    1. sedé executado no arquivo de propriedades para gerar um conjunto de -e s"@param_name@"param_value"gparâmetros (de acordo com o que é lido do arquivo de parâmetros) a serem usados ​​em uma única execução do arquivo de entrada para alterar todos os parâmetros de uma só vez.

    2. outro conjunto é executado com os parâmetros acima, para alterar todas as ocorrências de @parameter_name@a parameter_valuesaída é para stdout, para que você possa encadeá-lo e canalizá-lo para outro arquivo.

    se você deixar isso em um script de shell e usar stdin para alimentar o segundo comando sed (1), então você pode fazer a substituição de parâmetros em tempo real. Eu uso essa abordagem para colocar todos os parâmetros de configuração em um config.mkarquivo, que é analisado para gerar os Makefileconfiguráveis, os configuráveis ​​de código-fonte e os configuráveis ​​de documentação em um único arquivo.

    • 2
  7. Ed Morton
    2025-04-02T21:16:36+08:002025-04-02T21:16:36+08:00

    Isso produzirá a saída que você mostra a partir da entrada que você mostra, usando qualquer awk:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    awk '
        NR == FNR {
            pos = index($0, "=")
            tag = substr($0, 1, pos - 1)
            val = substr($0, pos + 1)
    
            # Make any regexp metachars in the tag literal 
            gsub(/[^^\\[:alnum:]]/, "[&]", tag)
            gsub(/\\/, "&&", tag)
            gsub(/\^/, "\\\\&", tag)
    
            tags2vals[tag] = val
            next
        }
        {
            for ( tag in tags2vals ) {
                if ( match($0, tag) ) {
                    val = tags2vals[tag]
                    $0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
                }
            }
            print
        }
    ' props input
    
    $ ./tst.sh
    This is a story about world. It starts at a bar and ends in a park.
    
    Bob said to Sally "goodbye, see you soon"
    

    Isso foi executado em relação à entrada de amostra que você forneceu:

    $ head props input
    ==> props <==
    $foo=bar
    $hello=world
    ^hello=goodbye
    
    ==> input <==
    This is a story about $hello. It starts at a $foo and ends in a park.
    
    Bob said to Sally "^hello, see you soon"
    

    mas se sua entrada real puder conter definições de propriedades recursivas ( $foo=$hello) e/ou substrings na entrada ( this is $foobar here) que você não deseja corresponder, então você precisará melhorá-la para lidar com elas da maneira que quiser.

    Veja É possível escapar metacaracteres regex de forma confiável com sed (é uma questão do sed, mas o problema de escapar metacaracteres regexp também se aplica ao awk) para saber o que os gsub()s estão fazendo no script.

    • 2

relate perguntas

  • Como fazer backup dos meus arquivos no Google Drive usando Duplicity no Linux?

  • Edição condicional de TSV grande no Linux

  • Contando ocorrências de string na segunda coluna que corresponde às primeiras colunas de um arquivo [fechado]

  • Extraia valores de saída do arquivo de imagem de disco como strings no Linux [fechado]

  • Precisa de ajuda para executar um comando envolvendo várias aspas simples e duplas na linha de comando do Windows via bsub

Sidebar

Stats

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

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

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