Pares de valores de chave completos

2

Histórico:

Eu quero escrever um script bash (MacOS 10.9.5) que insere valores associados a algumas strings. No script, definirei cada um dos possíveis valores associados.

Por exemplo, posso definir o texto associado ao texto do link yahoo como www.yahoo.com como a variável

XX_yahoo="www.yahoo.com"

O prefixo XX_ é adicionado para evitar a colisão de nomes com variáveis existentes. Meu script então é substituir todas as ocorrências de

\MakeLink[yahoo]{}

com

\MakeLink[yahoo]{www.yahoo.com}

, combinando o texto do link entre os colchetes da macro \MakeLink com uma variável existente. Se uma variável para algum texto não foi fornecida, usamos o título do texto do link. Assim,

\MakeLink[foo bar]{}

deve se tornar

\MakeLink[foo bar]{Foo Bar}

O script abaixo lida com o caso de onde

  • o texto do link não tem espaço e
  • a variável de texto do link não foi definida

Perguntas:

Como o número de valores possíveis do texto do link pode variar em milhares e pode ter espaços, minhas perguntas são:

  1. Essa é a melhor abordagem para isso? Seria melhor usar um array para as variáveis?
  2. Como devo lidar com o caso em que o texto do link tem um espaço? Por exemplo, eu gostaria de poder ter

    \MakeLink[the google]{}
    

    ser substituído por

    \MakeLink[the google]{www.google.com}.
    

Notas

  • Ok, suponha que haverá somente uma ocorrência de \MakeLink por linha.
  • A macro MakeTitleCase precisa ser aprimorada para ter uma lista de palavras para as quais o caso não deve ser alterado (como seria em um título), mas posso abordar isso mais tarde.

Problemas conhecidos com a solução existente:

  • Há um problema em como eu estou correspondendo a \MakeLink , pois a correspondência ainda ocorre mesmo se a barra invertida for omitida. Veja a última linha do primeiro parágrafo no caso de teste.
  • Se eu tiver um ? no arquivo, parece que sed tem um problema.
  • Não sabe como lidar com casos em que o texto do link contém um espaço.

Script

#!/bin/bash

## Can't have a backslash in the values of these variables, which is ok for my purposes.
XX_yahoo="www.yahoo.com"
XX_google="www.google.com"

function MakeTitleCase {
    echo $(echo "$1" | awk '{for(j=1;j<=NF;j++){ $j=toupper(substr($j,1,1)) substr($j,2) }}1')
}


while read -d $'\n' LINE; do
    ## Extract target which is the text within the square brackets of "\MakeLink[target]{}"
    TARGET=$(echo ${LINE} | sed -e 's?\]{}.*??' -e 's?\MakeLink\[??')
    TEMP=XX_${TARGET}
    if [ -z "${!TEMP}" ]; then
        REPLACEMENT=$(MakeTitleCase "${TARGET}")
    else
        REPLACEMENT=${!TEMP}
    fi

    ## Incorrect handling of leading backslash for the match.
    echo "${LINE}" | sed "s?\MakeLink\[${TARGET}\]{}?\\MakeLink\[${TARGET}\]{${REPLACEMENT}}?";
done 

exit 0

Arquivo de entrada de amostra:

A very popular site on the internet was
\MakeLink[yahoo]{} but was surpassed by
\MakeLink[google]{} due to its  
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{} has had to deal with
\MakeLink[antitrust issues]{}.

Saída atual:

A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
\MakeLink[search engine]{Search Engine}.

Due to its dominance
\MakeLink[the google]{The Google} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.

Saída desejada:

Somente a alteração acima é o texto associado a the google e que o MakeLink[search engine]{} não deve ser alterado, pois não há uma barra invertida inicial.

A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.
    
por Peter Grill 01.12.2015 / 14:54

3 respostas

2

Resposta semelhante à da choroba (eu escrevi isso sem ver a sua, eu juro!), mas lida com a capa do título sem codificar:

#!/usr/bin/perl
use strict;
use warnings;

my %links = (
    yahoo => "www.yahoo.com",
    google => "www.google.com",
);
$links{"the $_"} = $links{$_} for keys %links;

while (<>) {
    s{\MakeLink\[(.+?)\]\{\}}{
        sprintf "\MakeLink[%s]{%s}", 
            $1, 
            exists $links{$1} ? $links{$1}
                              : join " ", map {ucfirst lc} split " ", $1;
    }eg;
    print;
}

Executando:

$ perl link.pl input
A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its  
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.
    
por 01.12.2015 / 17:04
3

Perl para o resgate:

#!/usr/bin/perl
use warnings;
use strict;

my %replace = ( yahoo              => 'www.yahoo.com',
                google             => 'www.google.com',
                'search engine'    => 'Search Engine',
                'the google'       => 'The Google',
                'antitrust issues' => 'Antitrust Issues',
              );

while (<>) {
    s/\MakeLink\[(.*?)\]\{\}/\MakeLink[$1]{$replace{$1}}/g;
    print;
}

Você cria uma tabela de substituições de hash e a utiliza na substituição. Você pode criar tabelas de hash em versões bash recentes, mas não pode usá-las diretamente no sed, portanto não há nenhuma contraparte direta do bash + sed.

    
por 01.12.2015 / 15:07
1

Eu não analisei o seu script, mas vejo alguns lugares em que você está passando por problemas de citação (caracteres que têm um significado especial quando você não quer que eles sejam):

  • read -d $'\n' LINE (uma maneira complicada de escrever read LINE ) analisa falhas de barra invertida, portanto, ele efetivamente come barras invertidas. Torne read -r LINE . Esse comando também desativa espaços iniciais e finais; para evitar isso, torne-o IFS= read -r LINE .
  • Você está substituindo variáveis em scripts sed. O conteúdo dessas variáveis é analisado como um script sed, não uma sequência de pesquisa ou texto de substituição da maneira que você pretende. Este é o problema com ? no arquivo: quando aparece em $TARGET , sed vê um ? . Para corrigir isso, adicione caracteres de barra invertida antes de todos os caracteres que são especiais em sed (e tenha cuidado que em um regexp e em um texto de substituição, você precisa escapar de diferentes caracteres!).

Na verdade ... não faça o que escrevi acima. Eu estava apenas explicando o que deu errado; mas você deve reescrever completamente o seu roteiro, porque você está usando uma chave de fenda para martelar um prego.

Você está usando o bash, que possui matrizes associativas. Usar variáveis com um nome construído é uma invasão conveniente quando nada melhor está disponível, mas é mais difícil de usar do que uma estrutura de dados adequada. A menos que as variáveis XX_yahoo realmente venham do ambiente, use uma matriz associativa.

typeset -A targets
targets[yahoo]='www.yahoo.com'

Enquanto é possível analisar um arquivo linha por linha no shell com while read … , não é realmente apropriado para arquivos grandes (é lento) ou arquivos com sintaxe não trivial (como você descobriu, é difícil analisar as coisas corretamente quando você está indo e voltando entre o shell e ferramentas externas como sed). Sua tarefa é um material básico para um script awk (ou perl, como mostrado em outras respostas).

E se você for usar o awk de qualquer maneira, é melhor definir o array associativo diretamente no awk.

Código não testado.

#!/bin/awk -f
BEGIN {
    targets[yahoo]="www.yahoo.com";
    targets[google]="www.google.com";
}
function MakeTitleCase(text) {
    split(text, words);
    text = "";
    for (w in words) {
        text = text toupper(substr(w,1,1)) substr(w,2)
    }
    return text;
}

/^ *\MakeLink\[[^][{}]*\]{}/ {
    target_start = index($0, "[") + 1;
    target_end = index($0, "]") - 1;
    target = substr($0, target_start, target_end - target_start);
    if (target in targets) {
        replacement = targets[target];
    } else {
        replacement = MakeTitleCase(target);
    }
    $0 = substr($0, 1, target_start-1) replacement substr($0, target_end);
}

1
    
por 02.12.2015 / 02:13