Anexando uma linha a um arquivo apenas se ele ainda não existir


157

Preciso adicionar a seguinte linha ao final de um arquivo de configuração:

include "/configs/projectname.conf"

para um arquivo chamado lighttpd.conf

Estou pensando em usar sedpara fazer isso, mas não consigo descobrir como.

Como inseri-lo apenas se a linha ainda não existir?


Se você está tentando editar um arquivo ini a ferramenta crudinipode ser uma opção boa (mas não para lighthttpd até o momento)
rubo77

Respostas:


292

Basta mantê-lo simples :)

grep + echo deve ser suficiente:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

Edit: incorporou @cerin e @ thijs-wouters sugestões .


2
Eu adicionaria a opção -q ao grep para suprimir a saída: grep -vq ...
Dave Kirby

1
Para sua informação, usar -v e && não parece ser suficiente, enquanto || sem -v funciona.
precisa saber é

3
Isso funciona apenas para linhas muito simples. Caso contrário, o grep interpreta a maioria dos caracteres não alfa na sua linha como padrões, fazendo com que não detecte a linha no arquivo.
Cerin

2
Bela solução. Isso também funciona para acionar expressões mais complicadas, é claro. O meu usa o echopara acionar um catheredoc de várias linhas em um arquivo de configuração.
Eric L.

2
Adicione -spara ignorar erros quando o arquivo não existir, criando um novo arquivo com apenas essa linha.
21418 Frank

78

Essa seria uma solução limpa, legível e reutilizável, usando grepe echoadicionando uma linha a um arquivo apenas se ele ainda não existir:

LINE='include "/configs/projectname.conf"'
FILE='lighttpd.conf'
grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"

Se você precisar coincidir com toda a linha, use grep -xqF

Adicione -spara ignorar erros quando o arquivo não existir, criando um novo arquivo com apenas essa linha.


5
Se for para um arquivo em que você precisa de permissões de root:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
nebffa

Na verdade, isso não funciona quando a linha começa com a -.
hyperknot

@zsero: bom ponto! Eu adicionei --o comando grep, para que ele não interprete mais um $ LINE começando com um traço como opções.
rubo77

13

Aqui está uma sedversão:

sed -e '\|include "/configs/projectname.conf"|h; ${x;s/incl//;{g;t};a\' -e 'include "/configs/projectname.conf"' -e '}' file

Se sua string estiver em uma variável:

string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file

Para um comando que substitui uma string parcial pela entrada do arquivo de configuração completa / atualizada ou anexa a linha conforme necessário:sed -i -e '\|session.*pam_mkhomedir.so|h; ${x;s/mkhomedir//;{g;tF};a\' -e 'session\trequired\tpam_mkhomedir.so umask=0022' -e '};:F;s/.*mkhomedir.*/session\trequired\tpam_mkhomedir.so umask=0022/g;' /etc/pam.d/common-session
bgStack15

Bom, isso me ajudou muito +1. você poderia explicar sua expressão sed?
Rahul

Eu adoraria ver uma explicação anotada dessa solução. Eu uso sedhá anos, mas principalmente apenas o scomando. As trocas de espaço holde patternestão além de mim.
nortally

11

Se estiver gravando em um arquivo protegido, as respostas de @drAlberT e de rubo77 podem não funcionar para você, pois não é possível sudo >>. Uma solução igualmente simples , então, seria usar tee --append(ou, no MacOS tee -a):

LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE"  || echo "$LINE" | sudo tee --append "$FILE"

6

Se, um dia, alguém tiver que lidar com esse código como "código herdado", essa pessoa ficará agradecida se você escrever um código menos exotérico, como

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi

1
Desculpe, se você não conhece o shell, administre o Windows. As soluções usando && e || são uma sintaxe normal do shell e devem ser entendidas por qualquer administrador competente. Não há nada esotérico lá.
Graham Nicholls

3
Obrigado pelo seu comentário Graham! Sim, é uma sintaxe normal do shell. E sim, qualquer administrador competente deve entender isso. No entanto, ele funciona como tal com base no efeito colateral do algoritmo de avaliação de expressão no shell. Então você pode chamar do jeito que quiser, exceto direto - que era o meu ponto original.
Marcelo Ventura


6

outra solução sed é sempre anexá-la na última linha e excluir uma pré-existente.

sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"

"escapado corretamente" significa colocar uma regex que corresponda à sua entrada, ou seja, escapar de todos os controles regex da sua entrada real, ou seja, colocar uma barra invertida na frente de ^ $ / *? + ().

isso pode falhar na última linha do seu arquivo ou, se não houver uma nova linha pendente, não tenho certeza, mas isso pode ser resolvido por algumas ramificações bacanas ...


1
One-liner agradável, curto e fácil de ler. Funciona muito bem para atualizar etc / hosts:sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
Noam Manos

Uma versão menor:sed -i -e '/<entry>/d; $a <entry>'
Jérôme Pouiller

@ Jezz Eu acho que isso falhará, se a entrada já estiver no final do arquivo, porque o dcomando reiniciará o programa sed e, assim, pulará o append, se a exclusão estiver na última linha.
precisa saber é o seguinte

@ Robin479 Damned, append tem que ser executado antes delete: sed -i -e '$a<entry>' -e '/<entry>/d'. É quase o mesmo que sua resposta original.
Jérôme Pouiller

3

use awk

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file

É 'arquivo de arquivo' ou apenas 'arquivo'?
Webdevguy

É file fileporque ele faz duas passagens sobre o mesmo arquivo. NR==FNRé verdade na primeira passagem, mas não na segunda. Este é um idioma comum do Awk.
tripleee

1

As respostas usando grep estão erradas. Você precisa adicionar uma opção -x para coincidir com a linha inteira, caso contrário, linhas como #text to addainda corresponderão ao procurar adicionar exatamente text to add.

Portanto, a solução correta é algo como:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

1

Usando sed: Ele será inserido no final da linha. Você também pode passar variáveis ​​como de costume, é claro.

grep -qxF "port=9033" $light.conf
if [ $? -ne 0 ]; then
  sed -i "$ a port=9033" $light.conf
else
    echo "port=9033 already added"
fi

Usando oneliner sed

grep -qxF "port=9033" $lightconf || sed -i "$ a port=9033" $lightconf

Usar o eco pode não funcionar como root, mas funcionará assim. Mas isso não permitirá que você automatize as coisas, se estiver procurando fazê-lo, pois pode solicitar senha.

Eu tive um problema ao tentar editar da raiz para um usuário específico. Apenas adicionar o $usernameantes foi uma correção para mim.

grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
  sudo -u $user_name echo "port=9033" >> light.conf
else
    echo "already there"    
fi

Bem-vindo ao stackoverflow! Por favor, leia a pergunta com atenção antes de postar uma resposta - o OP perguntou como inserir usando o sed #
Markoorn 27/02/19

-1

Eu precisava editar um arquivo com permissões de gravação restritas, conforme necessário sudo. trabalhando a partir da resposta de ghostdog74 e usando um arquivo temporário:

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file > /tmp/file
sudo mv /tmp/file file

Isso não parece correto, ele perderá as outras linhas do arquivo quando a linha de configuração estiver ausente.
tripleee
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.