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?

Benjamin Dell
fonte
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 .

drAlberT
fonte
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.

rubo77
fonte
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
Pausado até novo aviso.
fonte
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"
hamx0r
fonte
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
Marcelo Ventura
fonte
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 ...

Robin479
fonte
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
ghostdog74
fonte
É '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
Thijs Wouters
fonte
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
Rakib Fiha
fonte
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
webdevguy
fonte
Isso não parece correto, ele perderá as outras linhas do arquivo quando a linha de configuração estiver ausente.
tripleee