Как добавить некоторую строку в конец файла, только если его еще нет?

10

Я хотел бы отредактировать файл на месте, добавив строку, только если она еще не существует, чтобы сделать мой сценарий пуленепробиваемым.

Обычно я бы сделал что-то вроде:

cat >> ~/.bashrc <<EOF
export PATH=~/.composer/vendor/bin:\$PATH
EOF

Это также возможно сделать через ansible ( line+ insertafter=EOF+ regexp), но это другая история.

В vi / ex я мог бы сделать что-то вроде:

ex +'$s@$@\rexport PATH=\~/.composer/vendor/bin:$PATH@' -cwq ~/.bashrc

но тогда как я могу проверить, что линия уже существует (и, следовательно, ничего не делать) в идеале, не повторяя ту же строку?

Или, может быть, есть какой-то более простой способ сделать это в редакторе Ex?

kenorb
источник
Вы не можете передать это на аутсорсинг? grep -Fq 'export PATH=~/.composer/vendor/bin:$PATH' ~/.bashrc || ex ...(или cat, в этом отношении)?
Муру
Я считаю, что это может быть что-то похожее на этот подход , но наоборот (когда нет строки, сделайте что-нибудь).
Кенорб
ex ~/.bashrc -c "if search('export PATH=\~\/.composer\/vendor\/bin:\$PATH')>0 | norm quit | endif | norm Aexport PATH=~/.composer/vendor/bin:$PATH"
Алекс Кролл
1
Обратите внимание, что exportэто команда , поэтому остальная часть строки является словом оболочки, а НЕ присваиванием. Следовательно, в отличие от присваивания переменной (которая не использует export), вам нужны двойные кавычки, иначе они будут разбиты на пробел . Также см. Как правильно добавить путь к PATH .
Уайлдкарт
1
Я нашел фактическую ссылку, которую я искал вчера (хотя вышеупомянутые ссылки также хороши). Нужны ли кавычки для присваивания локальной переменной?
Wildcard

Ответы:

6

Если вы хотите пуленепробиваемый , вы, вероятно, хотите что-то более портативное, чем сложные варианты выше Я хотел бы придерживаться функций POSIXex и сделать это глупо-простым: просто удалите строку, если она есть (независимо от того, сколько раз она там есть), добавьте ее в конец файла, затем сохраните и выйдите:

ex -sc 'g/^export PATH=\~\/\.composer\/vendor\/bin:\$PATH$/d
$a
export PATH=~/.composer/vendor/bin:$PATH
.
x' ~/.bashrc

Я закрепил регулярное выражение для дополнительной надежности, хотя я думаю, что почти невозможно случайно сопоставить это регулярное выражение. (Привязка означает использование, ^и $поэтому регулярное выражение будет соответствовать, только если оно соответствует всей строке.)

Обратите внимание, что +cmdсинтаксис не требуется для POSIX, а также не является общей возможностью разрешения нескольких -cаргументов.


Есть множество других простых способов сделать это. Например, добавьте строку в конец файла, а затем пропустите последние две строки файла через внешний фильтр UNIX uniq:

ex -sc '$a
export PATH=~/.composer/vendor/bin:$PATH
.
$-,$!uniq
x' input

Это очень чисто и просто, а также полностью POSIX-совместимо. Он использует функцию Escapeex для внешней фильтрации текста и POSIX-утилиту оболочкиuniq

Суть в том, что exон предназначен для редактирования файлов на месте, и это, безусловно, самый переносимый способ сделать это неинтерактивным способом. viФактически, он предшествует даже оригиналу - название viдля «визуального редактора», а невизуальный редактор, на котором он был построен, был ex .)


Для еще большей простоты (и для сокращения его до одной строки) используйте printfкоманду для отправки ex:

printf %s\\n '$a' 'export PATH=~/.composer/vendor/bin:$PATH' . '$-,$!uniq' x | ex input
Wildcard
источник
Связанный: vi.stackexchange.com/q/6269/4676
подстановочный
2

Возможное решение - создать функцию для этого:

function! AddLine()

    let l:res = 0
    g/export PATH=\~\/.composer\/vendor\/bin:\\\$PATH/let l:res = l:res+1

    if (l:res == 0)
        normal G
        normal oexport PATH=~/.composer/vendor/bin:\$PATH
    endif

endfunction

Сначала мы создаем локальную переменную, l:resкоторая будет использоваться для подсчета количества вхождений строки.

Мы используем globalкоманду: каждый раз, когда строка соответствует, l:resувеличивается.

Наконец, если l:resвсе еще 0, это означает, что строка не появляется в файле, поэтому мы переходим к последней строке благодаря G, и мы создаем новую строку, oв которой мы пишем строку для добавления.

Примечание 1 Метод, который я использовал для установки значения, l:resнемного тяжел и может быть заменен на метод, предложенный @Alex в его ответе, что-то вроде:

let l:res = search('export PATH=\~\/.composer\/vendor\/bin:\\\$PATH')

Примечание 2 Также, чтобы сделать функцию более гибкой, вы можете использовать аргументы:

function! AddLine(line)

    let l:line =  escape(a:line, "/~$" )

    let l:res = 0
    exe "g/" . l:line . "/let l:res = l:res+1"

    if (l:res == 0)
        normal G
        exe "normal o" . a:line
    endif

endfunction
  • Обратите внимание на хитрость, заключающуюся escape()в добавлении \перед символами символа a, который может вызвать проблемы при поиске.
  • Также обратите внимание, что \при вызове функции вам все равно придется избегать вызова самостоятельно (мне не удалось избежать \автоматически):

    call AddLine("export PATH=~/.composer/vendor/bin:\\$PATH")
    
statox
источник
2

Пытаться:

ex ~/.bashrc -c "if search('export PATH=\~\/.composer\/vendor\/bin:\$PATH')>0 | norm quit | endif | norm Aexport PATH=~/.composer/vendor/bin:$PATH"

Обратите внимание, что :appendне работает внутри if endif-блок в режиме Ex (см. VimDoc ), поэтому я должен положить norm A...снаружи.

Алекс Кролл
источник
1

Я обычно использую:

perl -i -p0e '$_.="export PATH=~/bin:\$PATH\n" unless m!~/bin:!' ~/.bashrc
JJoao
источник
1

Для того, чтобы упростить и избежать присутствия многих \, будет добавлена ​​строка export PATH=mybin:PATH.

Основная стратегия: заменить mybinтекст «non- » (весь файл) текстом + mybin-path-definition:

 vim  +'s#\v%^((.|\n)(mybin:)@!)*%$#&\rexport PATH=mybin:PATH#e' -cx bashrc

или с более дидактическим отступом :

s#                                    substitute
   \v                                  very magical to redude \

   %^                                  file begin
   ((.|\n)(mybin:)@!)*                 multi-line str without "mybin:"
                                         (any char not followed by "mybin")*
   %$                                  end of file
 #                                    by

Если у нас есть совпадение:

  1. &имеет полный текст bashrcи
  2. & не содержит /mybin:/

так:

 #                                    by ...
   &                                   all file
   export PATH=mybin:PATH              and the new path
 #e                                   don't complain if patt not found

-cx                                   save and leave 

ОБНОВЛЕНИЕ : Наконец, мы можем сделать скрипт vim:

$ cat bashrcfix

#!/usr/bin/vim -s
:e ~/fakebashrc
:s#\v%^((.|\n)(mybin:)@!)*%$#&\rexport PATH=mybin:PATH#e
:x

chmod 755 и установите его. Применение:bashrcfix


будьте осторожны: в \vрежиме =означает необязательный

JJoao
источник
1

Большинство из этих ответов кажутся сложными, как насчет старых добрых команд оболочки:

grep composer/vender ~/.bashrc || cat >> ~/.bashrc <<EOF
export PATH=~/.composer/vendor/bin:\$PATH
EOF

Это может быть функция Bash:

addBashrcPath() {
  if ! grep "$1" ~/.bashrc >/dev/null 2>&1; then
    printf 'export PATH=%s:$PATH' "$1" >> ~/.bashrc
  fi
}

addBashrcPath "~/.composer/vendor/bin"

РЕДАКТИРОВАТЬ : код выхода grep важен, и в этом случае его необходимо обратить вспять ! grep; grep -vне выйдет, как ожидалось в этой ситуации. Спасибо, @joeytwiddle за совет.

Sukima
источник
Я не думаю, что grep -vдает вам код выхода, который вы хотите. Но ! grepдолжен это сделать.
Joeytwiddle
Если это так, то вам даже не нужно -vпросто !.
Суким
Точно. Вы можете отредактировать свой ответ, чтобы сделать это исправление.
Joeytwiddle
1
1. Следует использовать, -Fчтобы не grepинтерпретировать символы как регулярные выражения, и 2. Использовать -qвместо перенаправления на /dev/null. Смотрите мой предыдущий комментарий на вопрос .
Муру
Хорошие моменты. Любопытно, насколько портативный -qи -Fварианты? Я думал, что это >/dev/nullбыло более универсальным среди различных платформ (Sun против HP против Mac OS X против Ubuntu против FreeBSD против ...)
Sukima