Como anexar várias linhas a um arquivo, se essas linhas não existirem nesse arquivo?

1

Como anexar várias linhas a um arquivo, se essas linhas não existirem nesse arquivo?

Por exemplo, para adicionar vários aliases globais a /etc/bash.bashrc , uso um documento :

cat <<-"BASHRC" >> /etc/bash.bashrc
    alias rss="/etc/init.d/php*-fpm restart && systemctl restart nginx.service"
    alias brc="nano /etc/bash.bashrc"
BASHRC

Eu fui criticado que esta operação não inclui uma maneira de verificar se as linhas já estão lá, e se reescrever o heredocument por engano, eu poderia causar redundância, bem como conflito.

Atualizar

Por favor, certifique-se de não perder a mensagem de recompensa.

    
por Arcticooling 13.01.2018 / 11:45

5 respostas

2

Script de shell simples para adicionar linhas do arquivo newdata to datafile . Deve ser fácil alterar newdata para um aqui-doc. Isso realmente não é muito eficaz, pois chama grep para cada (nova) linha de entrada:

target=datafile
while IFS= read -r line ; do
    if ! grep -Fqxe "$line" "$target" ; then
        printf "%s\n" "$line" >> "$target"
    fi
done < newdata 

Para cada linha, usamos grep para ver se ela já existe no arquivo de destino, -F para correspondência de sequência fixa (sem expressões regulares), -x para correspondência de linha completa e -q para suprimir saída de linhas combinadas. grep retorna um código de erro falso se não encontrar uma linha correspondente, então acrescente ao arquivo de destino se o resultado negado for verdadeiro.

Mais efetivamente, em awk . Isso depende de awk poder manipular linhas arbitrárias como chaves para um array.

$ awk 'FNR == NR { lines[$0] = 1; next } ! ($0 in lines) {print}' datafile newdata 

A primeira parte FNR == NR { lines[$0] = 1; next } carrega todas as linhas do primeiro arquivo de entrada como chaves na matriz (associativa) lines . A segunda parte ! ($0 in lines) {print} é executada nas seguintes linhas de entrada e imprime a linha se não estiver na matriz, ou seja, nas "novas" linhas.

A saída resultante contém apenas as novas linhas, por isso precisa ser anexada ao arquivo original, por exemplo, com sponge :

$ awk 'FNR == NR { lines[$0] = 1; next } ! ($0 in lines) {print}' datafile newdata | sponge -a datafile

Ou poderíamos ter awk anexando as linhas à linha final, isso exige apenas passar o nome do arquivo para awk :

$ target=datafile 
$ awk -vtarget="$target" 'FNR == NR { lines[$0] = 1; next } 
                        ! ($0 in lines) {print >> target}' "$target" newdata

Para usar um aqui-doc com awk , precisaremos adicionar - (stdin) como um arquivo de origem explícito, além de definir o redirecionamento, portanto awk ... "$target" - <<EOF

    
por 17.01.2018 / 15:48
3

Esta solução é provavelmente um pouco diferente do que você tinha em mente, mas eu verificaria se um alias estava realmente definido e apenas o adicionaria ao bashrc, se não fosse. Supondo que você esteja usando uma versão relativamente atual do Bash ...

# instead of 'alias x=y' form put them in an associative array
declare -A aliases
aliases['a']='apple'
aliases['ba']='banana'

for key in "${!aliases[@]}"; do
    if ! alias "$key" > /dev/null 2>&1; then
        printf "alias %s='%s'\n" "$key" "${aliases[$key]}" >> /etc/bash.bashrc
    fi
done

unset aliases key

Fonte este . ./scriptname em vez de executá-lo como um script normal.

Observação: Se você tiver vários aliases candidatos e tiver receio de convertê-los manualmente para o formato de matriz associativa, edite o arquivo com vim e execute esse comando: :%s/\valias *([^=]+)\=['"]?([^'"]+)['"]?$/aliases['']=''/ .

OU coloque seus aliases de candidatos (e nada mais) em um arquivo (por exemplo, /tmp/aliases.txt ) e substitua as linhas aliases[..]='..' no script por:

while IFS= read -r a; do 
    eval "$a"; 
done < <(sed -E "s/alias  *([^=]+)=['\"]?([^'\"]+)['\"]?$/aliases['']=''/" /tmp/aliases.txt)

É claro que existem outras maneiras além de usar AAs, mas elas são limpas e fáceis de se trabalhar ... e eu estou todo neste momento. :)

    
por 13.01.2018 / 12:28
1

Uma solução genérica para correspondências exatas (não otimizada para desempenho); o arquivo input contém as linhas a serem verificadas:

awk 'FNR==NR { lines[NR]=$0; next; };
    { for(i=1;i<=length(lines);i++) if ($0==lines[i]) matches[i]=1; print; };
    END { for(i=1;i<=length(lines);i++)
        if (matches[i]==0) print lines[i]; }' input file

Teste:

:> cat input
alias rss="/etc/init.d/php*-fpm restart && systemctl restart nginx.service"
alias brc="nano /etc/bash.bashrc"

:> cat file
a
b
c
alias rss="/etc/init.d/php*-fpm restart && systemctl restart nginx.service"
d

Saída do comando awk :

a
b
c
alias rss="/etc/init.d/php*-fpm restart && systemctl restart nginx.service"
d
alias brc="nano /etc/bash.bashrc"
    
por 13.01.2018 / 18:14
0

A ordem das linhas anexadas é importante?

Se não, você pode tentar essa solução:

comm -1 -3 <(sort /etc/bash.bashrc) <(sort aliases) >> /etc/bash.bashrc

assumindo que você salvou linhas anteriormente para adicionar no arquivo aliases .

comm é o GNU coreutil (você pode ver a página de manual para ele), que permite comparar dois arquivos classificados linha a linha.

    
por 19.01.2018 / 19:47
0

Hmmmm. Que tal isso ...

Coloque os aliases para adicionar no arquivo novo . Então ...

cp bashrc bashrc.tmp && comm -23 <(sort -u new) <(sort -u bashrc.tmp) >> bashrc && rm -f bashrc.tmp

Classificamos os dois novos apelidos e o conteúdo do bashrc (colocamos em um arquivo temporário para evitar a condição de corrida quando acrescentamos bashrc ) e os executamos em comm . comm faz comparações linha a linha de arquivos classificados e mostra linhas únicas e comuns. Nós suprimimos as colunas 2 e 3 ( -23 ), então o resultado é simplesmente aquelas linhas que são exclusivas de new e nós as adicionamos a bashrc . É isso.

(Esta é uma abordagem totalmente diferente da minha outra resposta, concentrando-se em sua busca por mais de um "one-liner".)

    
por 17.01.2018 / 23:50