Dołączanie wiersza do pliku tylko wtedy, gdy jeszcze nie istnieje

157

Muszę dodać następujący wiersz na końcu pliku konfiguracyjnego:

include "/configs/projectname.conf"

do pliku o nazwie lighttpd.conf

Chcę użyć seddo tego, ale nie wiem, jak to zrobić.

Jak wstawiłbym go tylko wtedy, gdy linia jeszcze nie istnieje?

Benjamin Dell
źródło
Jeśli próbujesz edytować plik ini, narzędzie crudinimoże być dobrą opcją (ale jeszcze nie dla lighthttpd)
rubo77

Odpowiedzi:

291

Po prostu nie komplikuj :)

grep + echo powinno wystarczyć:

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

Edycja: włączone sugestie @cerin i @ thijs-wouters .

drAlberT
źródło
2
Dodałbym przełącznik -q do grep, aby stłumić wyjście: grep -vq ...
Dave Kirby
1
FYI, użycie -v i && nie wydaje się działać, podczas gdy || bez -v działa.
bPizzi
3
Działa to tylko w przypadku bardzo prostych linii. W przeciwnym razie grep interpretuje większość znaków innych niż alfa w twoim wierszu jako wzorce, powodując, że nie wykrywa wiersza w pliku.
Cerin,
2
Piękne rozwiązanie. Oczywiście działa to również w przypadku wyzwalania bardziej skomplikowanych wyrażeń. Mój używa the echodo wyzwolenia cata wielowierszowego heredoc do pliku konfiguracyjnego.
Eric L.
2
Dodaj, -saby zignorować błędy, gdy plik nie istnieje, tworząc nowy plik zawierający tylko tę linię.
Frank
78

Byłoby to czyste, czytelne i wielokrotnego użytku rozwiązanie wykorzystujące grepi echododające wiersz do pliku tylko wtedy, gdy jeszcze nie istnieje:

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

Jeśli chcesz dopasować całą linię, użyj grep -xqF

Dodaj, -saby zignorować błędy, gdy plik nie istnieje, tworząc nowy plik zawierający tylko tę linię.

rubo77
źródło
5
Jeśli jest to plik, w którym potrzebujesz uprawnień roota:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
nebffa
To faktycznie nie działa, gdy wiersz zaczyna się od -.
hyperknot
@zsero: dobra uwaga! Dodałem --polecenie grep, więc nie będzie już interpretować $ LINE zaczynającego się od myślnika jako opcji.
rubo77
13

Oto sedwersja:

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

Jeśli twój ciąg jest w zmiennej:

string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file
Wstrzymano do odwołania.
źródło
W przypadku polecenia, które zastępuje częściowy ciąg pełnym / zaktualizowanym wpisem w pliku konfiguracyjnym lub dodaje wiersz w razie potrzeby: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
Świetnie, bardzo mi to pomogło +1. czy mógłbyś, proszę, wyjaśnić swoje wyrażenie sed?
Rahul
Chciałbym zobaczyć objaśnienie tego rozwiązania z adnotacjami. Używam od sedlat, ale głównie po prostu spolecenie. Zamiany holdi patternmiejsca są poza mną.
północne
11

Jeśli piszesz do chronionego pliku, odpowiedzi @drAlberT i @ rubo77 mogą nie działać dla ciebie, ponieważ nie można sudo >>. Podobnie proste rozwiązanie zatem byłoby użycie tee --append(lub na MacOS tee -a)

LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE"  || echo "$LINE" | sudo tee --append "$FILE"
hamx0r
źródło
6

Jeśli pewnego dnia ktoś inny będzie musiał traktować ten kod jako „stary kod”, osoba ta będzie wdzięczna, jeśli napiszesz mniej egzotyczny kod, taki jak

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
Marcelo Ventura
źródło
1
Przepraszamy, jeśli nie znasz powłoki, przejdź do administrowania systemem Windows. Rozwiązania wykorzystujące && i || to normalna składnia powłoki i powinna być zrozumiała dla każdego kompetentnego administratora. W ogóle nie ma tam nic ezoterycznego.
Graham Nicholls
3
Dzięki za komentarz Graham! Tak, to normalna składnia powłoki. I tak, każdy kompetentny administrator powinien to zrozumieć. Jednak działa jako taki w oparciu o efekt uboczny algorytmu oceny wyrażeń w powłoce. Możesz więc nazwać to wszystko, co lubisz, z wyjątkiem prostych - co było moim pierwotnym celem.
Marcelo Ventura
6

innym rozwiązaniem seda jest zawsze dołączanie go do ostatniej linii i usuwanie wcześniej istniejącego.

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

„Właściwie zmieniona ucieczka” oznacza umieszczenie wyrażenia regularnego, które pasuje do twojego wpisu, tj. wymknięcie się wszystkim kontrolkom wyrażenia regularnego z twojego aktualnego wpisu, tj. umieszczenie ukośnika odwrotnego przed ^ $ / *? + ().

to może się nie powieść w ostatnim wierszu twojego pliku lub jeśli nie ma wiszącego nowego wiersza, nie jestem pewien, ale można sobie z tym poradzić przez sprytne rozgałęzienie ...

Robin479
źródło
1
Ładny, jednoliniowy, krótki i łatwy do odczytania. Działa świetnie przy aktualizowaniu etc / hosts:sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
Noam Manos
Mniejsza wersja:sed -i -e '/<entry>/d; $a <entry>'
Jérôme Pouiller
@Jezz Myślę, że to się nie powiedzie, jeśli wpis znajduje się już na końcu pliku, ponieważ dpolecenie zrestartuje program seda i tym samym pominie append, jeśli usunięcie nastąpiło w ostatniej linii.
Robin479
@ Robin479 Damned, append muszą być wykonywane przed dsuĹ: sed -i -e '$a<entry>' -e '/<entry>/d'. To prawie to samo, co Twoja pierwotna odpowiedź.
Jérôme Pouiller
3

użyj awk

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file
ghostdog74
źródło
Czy to „plik plik” czy tylko „plik”?
webdevguy
Dzieje się tak, file fileponieważ wykonuje dwa przejścia przez ten sam plik. NR==FNRjest prawdziwe w pierwszym przebiegu, ale nie w drugim. To powszechny idiom w Awk.
tripleee
1

Odpowiedzi za pomocą grep są błędne. Musisz dodać opcję -x, aby dopasować całą linię, w przeciwnym razie wiersze takie jak #text to addbędą nadal pasować, gdy chcesz dodać dokładnie text to add.

Więc poprawne rozwiązanie to coś takiego:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
Thijs Wouters
źródło
1

Korzystanie z seda: wstawi na końcu wiersza. Oczywiście możesz również przekazywać zmienne jak zwykle.

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

Korzystanie z oneliner sed

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

Używanie echa może nie działać w systemie root, ale będzie działać w ten sposób. Ale nie pozwoli ci zautomatyzować rzeczy, jeśli chcesz to zrobić, ponieważ może poprosić o hasło.

Miałem problem, gdy próbowałem edytować z poziomu roota dla konkretnego użytkownika. Samo dodanie $usernamepoprzedniego było dla mnie poprawką.

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
źródło
Witamy w stackoverflow! Przeczytaj uważnie pytanie przed wysłaniem odpowiedzi - OP zapytał, jak wstawić za pomocą seda
Markoorn
-1

Musiałem edytować plik z tak ograniczonymi uprawnieniami do zapisu sudo. działając na podstawie odpowiedzi ghostdog74 i używając pliku tymczasowego:

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
źródło
To nie wygląda na poprawne, spowoduje utratę innych linii z pliku, gdy brakuje wiersza konfiguracji.
tripleee