perl regex substituindo globalmente quando global não selecionado

2

Estou usando o Ubuntu 11.04 e escrevi um pequeno script que pesquisa arquivos de texto para certos "tokens" e substitui com um trecho pré-escrito de um arquivo de modelo com o mesmo nome.

Os arquivos de texto que estão sendo pesquisados terão duas e apenas duas instâncias de cada token. O primeiro é um texto simples e o segundo é uma versão html, e há trechos separados para cada um.

Aqui está o script:

for f in 'ls -1 .templates/template_text';
do
    g='cat .templates/template_text/$f'
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/";
done

for f in 'ls -1 .templates/template_html';
do
    g='cat .templates/template_html/$f'
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/g";
done

Estou com um problema em que mesmo quando não tenho "global" especificado no primeiro regex, ele ainda substitui os dois tokens. Não tenho certeza se isso é por causa de como estou chamando perl, um bug ou algum outro problema.

Qualquer ajuda seria apreciada.

UPDATE: Consegui fazer o script funcionar usando sed em vez de perl.

for f in 'ls -1 .templates/template_text';
do
    g='cat .templates/template_text/$f'
    h='cat .templates/template_html/$f'
    find to_process/ -type f -print0 | xargs -0 -I {} sed -i -e "0,/$f/s/$f/$g/" -e "0,/$f/s/$f/$h/" {}
done

Ainda está interessado em como fazê-lo funcionar com o comando perl.

    
por not a good scriptwriter 01.10.2011 / 05:00

2 respostas

1

Isso porque o perl lê o arquivo de texto uma linha por vez e aplica o padrão de substituição a cada linha - assim, se houver várias ocorrências do token em linhas diferentes, elas serão substituídas.

Para substituir apenas a primeira ocorrência no arquivo, você pode adicionar a opção -0 , que define o separador de registro de entrada como um caractere nulo e faz o perl ler todo o arquivo antes de fazer a substituição.

    
por 01.10.2011 / 10:49
1

s/$f/$g/ substitui a primeira ocorrência de $f por $g em cada linha. Se você quiser substituir apenas a primeira ocorrência de $f no arquivo inteiro, será necessário dizer isso. Isso é o que você fez em sed com 0,/$f/ s/$f/$g/ (substitua $f por $g até e incluindo a primeira ocorrência de $f ). Em Perl, você pode escrever isso de uma forma mais detalhada, mas mais fácil de entender como essa (observe: veja abaixo para citar questões):

perl -i -pe 'if ($n==0) {s/$f/$g/; $n=1;} elsif ($n==1) {s/$f/$h/; $n=2}'

Seu código sofre vários problemas de cotação; você terá problemas se seus nomes de arquivo contiverem espaços em branco, caracteres globbing ou caracteres não imprimíveis (como sequências de bytes que não existem na localidade atual). Felizmente, esses problemas são fáceis de resolver.

Primeiro, alguns problemas genéricos de shell. Sempre dê duas citações de substituições de variáveis "$foo" e substituições de comandos "$(foo)" a menos que você saiba por que precisa deixá-las sem aspas. Se você deixá-los sem aspas, o resultado é dividido em palavras separadas onde quer que haja espaço em branco, e cada palavra é tratada como um padrão glob. Portanto, a menos que a variável contenha uma lista de padrões glob separados por espaço em branco, coloque aspas duplas em torno dela. Além disso, recomendo usar $(…) em vez de '…' ; eles são equivalentes, exceto que a cotação aninhada dentro de '…' não é confiável (também, ' é facilmente confundida com ' ).

Não analise a saída de ls . Se você precisa agir em todos os arquivos em um diretório, o shell tem uma construção interna que funciona: globbing. Em vez de $(ls /path/to/directory) , escreva /path/to/directory/* . Isso gera nomes de arquivos com o caminho do diretório; quase sempre é o que você precisa, e se não o fizer, você pode chamar cd antes ou remover todo ou parte do diretório. Abaixo, uso ${f#*/*/} , o que significa $f com o prefixo mais curto correspondendo */*/ retirado.

for f in .templates/template_text/*; do
  g=$(cat "$f")
  h=$(cat ".templates/template_html/${f#*/*/}")
  find to_process/ -type f …
done

Com find , você pode usar a construção mais simples -exec , embora -print0 combinado com xargs -0 também funcione. Não use xargs sem -0 , já que espera entrada citada de uma forma peculiar que find não produz.

find to_process/ -type f -exec perl … {} +

A próxima questão é que você está inserindo as strings $f , $g e $h diretamente em sua expressão regular sed ou perl. Isso está errado: essas variáveis não contêm uma expressão regular com o delimitador ( / em ambos os casos) entre aspas. Com sed, você precisaria fazer uma passagem de citações nas cadeias de caracteres, adicionando uma barra invertida antes de qualquer /*.\[ em $f e antes de qualquer \&/ em $g e $h . Com o Perl, existe uma maneira mais simples: passar os valores pelo ambiente e não se esqueça de dizer ao Perl que o que você tem é uma string e não um regexp.

export f g h
find to_process/ -type f -exec perl -i -e '
    if ($n==0) {s/\Q$ENV{f}/$ENV{g}/; $n=1;}
    elsif ($n==1) {s/\Q$ENV{f}/$ENV{h}/; $n=2}}
' {} +
    
por 02.10.2011 / 00:43