Добавление строки в файл, только если он еще не существует

157

Мне нужно добавить следующую строку в конец файла конфигурации:

include "/configs/projectname.conf"

в файл с именем lighttpd.conf

Я пытаюсь использовать sedэто, но не могу понять, как это сделать.

Как бы я вставить его, если линия еще не существует?

Бенджамин Делл
источник
Если вы пытаетесь редактировать ini-файл, этот инструмент crudiniможет быть хорошим вариантом (но пока не для lighthttpd)
rubo77

Ответы:

292

Просто будь проще :)

grep + echo должно хватить:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
  • -q будь спокоен
  • -x соответствовать всей линии
  • -F шаблон представляет собой простую строку
  • https://linux.die.net/man/1/grep

Редактировать: включены предложения @cerin и @ thijs-wouters .

drAlberT
источник
2
Я бы добавил параметр -q в grep для подавления вывода: grep -vq ...
Дейв Кирби
1
К вашему сведению, использование -v и && не помогает, а || без -v работает.
bPizzi
3
Это работает только для очень простых линий. В противном случае, grep интерпретирует большинство не-буквенных символов в вашей строке как шаблоны, в результате чего она не обнаруживает строку в файле.
Cerin
2
Прекрасное решение. Это также работает для запуска более сложных выражений, конечно. Мой использует echoдля запуска catмногострочного heredoc в файл конфигурации.
Эрик Л.
2
Добавьте, -sчтобы игнорировать ошибки, когда файл не существует, создавая новый файл только с этой строкой.
Фрэнк
78

Это было бы чистым, читаемым и повторно используемым решением, использующим grepи echoдобавляющим строку в файл, только если он еще не существует:

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

Если вам нужно соответствовать всю строку, используйте grep -xqF

Добавьте, -sчтобы игнорировать ошибки, когда файл не существует, создавая новый файл только с этой строкой.

rubo77
источник
5
Если это файл, где вам нужны права суперпользователя:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
nebffa
Это на самом деле не работает, когда строка начинается с -.
hyperknot
@zsero: хорошая мысль! Я добавил --команду grep, чтобы она больше не интерпретировала $ LINE, начинающуюся с тире, как опции.
rubo77
13

Вот sedверсия:

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

Если ваша строка находится в переменной:

string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file
Приостановлено до дальнейшего уведомления.
источник
Для команды, которая заменяет частичную строку на полную / обновленную запись файла конфигурации или добавляет строку по мере необходимости: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
Хорошо, это мне очень помогло +1. не могли бы вы, пожалуйста, объясните свое выражение sed
Рахул
Я хотел бы видеть аннотированное объяснение этого решения. Я использовал в sedтечение многих лет, но в основном только sкоманда. В holdи patternкосмические свопы за мной.
обычно
11

Если вы пишете в защищенный файл, ответы @drAlberT и @ rubo77 могут не сработать, так как никто не может сделать sudo >>. Таким же простым решением было бы использовать tee --append(или, на MacOS, tee -a):

LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE"  || echo "$LINE" | sudo tee --append "$FILE"
hamx0r
источник
6

Если однажды кому-то еще придется иметь дело с этим кодом как с «унаследованным кодом», то этот человек будет благодарен, если вы напишите менее экзотерический код, такой как

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
Марсело Вентура
источник
1
Извините, если вы не знаете shell, тогда идите и администрируйте Windows. Решения, использующие && и || являются нормальным синтаксисом оболочки и должны быть понятны любому компетентному администратору. Там нет ничего эзотерического там вообще.
Грэм Николлс
3
Спасибо за ваш комментарий Грэм! Да, это нормальный синтаксис оболочки. И да, любой компетентный админ должен это понимать. Тем не менее, он работает как таковой, основываясь на побочном эффекте алгоритма вычисления выражения в оболочке. Таким образом, вы можете называть все, что хотите, кроме прямой - что было моей первоначальной точкой зрения.
Марсело Вентура
6

Другое решение sed - всегда добавлять его в последнюю строку и удалять уже существующую.

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

«Правильно экранированный» означает ставить регулярное выражение, соответствующее вашей записи, т.е. экранировать все элементы управления регулярным выражением от вашей фактической записи, то есть ставить обратную косую черту перед ^ $ / *? + ().

это может произойти сбой в последней строке вашего файла или если нет висячей новой строки, я не уверен, но это может быть решено с помощью некоторого изящного ветвления ...

Robin479
источник
1
Хороший однострочник, короткий и удобный для чтения. Отлично работает для обновления etc / hosts:sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
Noam Manos
Уменьшенная версия:sed -i -e '/<entry>/d; $a <entry>'
Жером Пуйлер
@Jezz Я думаю, что это не удастся, если запись уже находится в конце файла, потому что dкоманда перезапустит программу sed и тем самым пропустит append, если удаление произошло в последней строке.
Robin479
@ Robin479 Damned, append должны быть выполнены перед dдалить: sed -i -e '$a<entry>' -e '/<entry>/d'. Это почти так же, как ваш первоначальный ответ.
Жером Пуйлер
3

использовать awk

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file
ghostdog74
источник
Это «файл файл» или просто «файл»?
webdevguy
Это file fileпотому , что он делает два прохода над одним файлом. NR==FNRверно на первом проходе, но не на втором. Это обычная идиома Awk.
tripleee
1

Ответы, использующие grep, неверны. Вам нужно добавить опцию -x, чтобы она соответствовала всей строке, в противном случае строки, как #text to addи раньше, будут совпадать, если вы хотите добавить точноtext to add .

Таким образом, правильное решение выглядит примерно так:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
Тийс Ваутерс
источник
1

Используя sed: он будет вставлен в конце строки. Вы также можете передать переменные, как обычно, конечно.

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

Использование oneliner sed

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

Использование echo может не работать под root, но будет работать так. Но это не позволит вам автоматизировать процесс, если вы захотите это сделать, поскольку он может запросить пароль.

У меня была проблема, когда я пытался редактировать из корня для конкретного пользователя. Просто добавление $usernameпредыдущего было исправлением для меня.

grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
  sudo -u $user_name echo "port=9033" >> light.conf
else
    echo "already there"    
fi
Ракиб Фиха
источник
Добро пожаловать в stackoverflow! Пожалуйста, внимательно прочитайте вопрос, прежде чем публиковать ответ - ОП спросил, как вставить, используя sed
Markoorn
-1

Мне нужно было отредактировать файл с ограниченными правами на запись, так что нужно sudo. работа с ответом ghostdog74 и использование временного файла:

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
источник
Это не выглядит правильно, он потеряет другие строки из файла, если строка конфигурации отсутствует.
tripleee