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"
Uma ideia/abordagem usando
bash
esed
, você pode tentar algo como:Uma maneira de verificar o valor de
sed_input
éOu
Incorporar um utilitário externo do bash dentro de um loop de shell como
cut
esed
deve ser evitado. Veja why-is-using-a-shell-loop-to-process-text-considered-bad-practiceA
sed
invocaçã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
Adicionando linhas adicionais ao arquivo de entrada do OP para demonstrar a correspondência de limites de palavras e uma propriedade
name
que ocorre mais de uma vez em uma linha:Suposições:
name
não é um caractere alfabético ([a-zA-Z]
); caso contrário, podemos expandir onext_char
teste (vejaawk
o código abaixo)Ideia geral:
properties.txt
as entradas em uma matriz (map[name]=value
)input.txt
, faça um loop por todasnames
, verificando se há alguma correspondência de limite de palavra para substituirUma ideia usando
awk
:NOTAS:
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$
,^
) precisarão ser escapados antes de tentar usarsub() / gsub() / match()
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:
Por questões de tempo, criei alguns arquivos maiores a partir do arquivo de propriedades do OP e do meu
input.txt
arquivo (veja acima):Cronometragem para arquivos de dados maiores:
NOTA: o tempo é de um sistema Ubuntu 22.04 (metal, vm) rodando em um Intel i7-1260P
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á
^hello
ou quaisquer outros.
*
[
?
\
caracteres corretamente, pois sed trabalha com regex.^
corresponde ao início de uma linha.Você poderia escapar dos caracteres especiais com algo parecido com isso. Veja também https://stackoverflow.com/a/2705678/9072753 .
Notas: use shellcheck. Use $(...) em vez de acentos graves. Não abuse de cats - apenas use
<file
em vez de<<<$(cat "$PROPERTIES_FILE")
. Não GRITE - considere variáveis em minúsculas. Considere m4, envsubst ou jinja2 ou apenas cpp para modelagem.Concordo que isso seria muito mais eficiente em
awk
ouperl
oupython
, 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
sed
comandos de substituição individuais em outro arquivo de script (ou acumulá-los em uma string, como o Jetchisel sugere) e então executá-lo.As duas saídas de tempo na parte inferior são para uma execução
sed
e para o script inteiro, respectivamente.e depois -
adenda
A maioria dos scripts bash se beneficiam muito ao mover subshells para built-ins.
Uma versão simplificada do meu
sed
script baseado em - acima:Usando processamento simples de string bash para tudo -
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
sed
script lidou com ele em cerca de 12,5s. Eu quebrei a versão all-in-memory all-bash em pouco menos de 3m.Seu código parece ser executado em tempo O(mn) procurando por
m
possíveis propriedades na entrada de tamanhon
.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:
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:
para
blah $key1
deveria resultar emblah $key2
e nãoblah value
Também pode ser possível processar vários
inputfile
em 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.Hmmm..... você escreve
Você não especifica o formato de
$INPUT_FILE
ou$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:Dessa forma, apenas dois programas são executados:
sed
é executado no arquivo de propriedades para gerar um conjunto de-e s"@param_name@"param_value"g
parâ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.outro conjunto é executado com os parâmetros acima, para alterar todas as ocorrências de
@parameter_name@
aparameter_value
saí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.mk
arquivo, que é analisado para gerar osMakefile
configuráveis, os configuráveis de código-fonte e os configuráveis de documentação em um único arquivo.Isso produzirá a saída que você mostra a partir da entrada que você mostra, usando qualquer awk:
Isso foi executado em relação à entrada de amostra que você forneceu:
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.