Как различать файлы, игнорируя комментарии (строки начинающиеся с #)?

55

У меня есть два файла конфигурации, оригинальный от менеджера пакетов и один измененный мной. Я добавил несколько комментариев для описания поведения.

Как мне запустить diffфайлы конфигурации, пропуская комментарии? Комментируемая строка определяется как:

  • необязательный начальный пробел (табуляции и пробелы)
  • знак хеша ( #)
  • ничего другого персонажа

(Простейший) регулярное выражение пропуская первое требование будет #.*. Я попробовал --ignore-matching-lines=RE( -I RE) вариант GNU diff 3.0, но не смог заставить его работать с этим RE. Я тоже пытался .*#.*и .*\#.*без удачи. Буквальное размещение строки ( Port 631) as REничего не соответствует и не помогает помещать RE между косыми чертами.

Как предполагается в инструменте «diff», аромат регулярных выражений кажется недостаточным? Я попробовал grep -G:

grep -G '#.*' file

Кажется, это соответствует комментариям, но не работает diff -I '#.*' file1 file2.

Итак, как использовать эту опцию? Как я могу заставить diffпропустить определенные строки (в моем случае, комментарии)? Пожалуйста, не предлагайте grepфайл и сравнивать временные файлы.

Lekensteyn
источник
12
Эта -Iопция заставляет блок игнорироваться, только если все его строки соответствуют регулярному выражению. Таким образом, вы можете игнорировать изменения только для комментариев таким образом, но не изменения комментариев, которые близки к изменениям без комментариев.
Жиль "ТАК - перестань быть злым"
@ Жиль: Спасибо, теперь я понимаю, почему diff -Iне ведет себя так, как я ожидал. Я обновил свой ответ примером, который прояснил мне это поведение.
Лекенштейн

Ответы:

49

По словам Жиля, -Iопция игнорирует только строку, если внутри этого набора ничего не найдено, кроме совпадения -I. Я не получил его полностью, пока не проверил.

Тест

В моем тесте участвуют три файла:
Файл test1:

    text

Файл test2:

    text
    #comment

Файл test3:

    changed text
    #comment

Команды:

$ # comparing files with comment-only changes
$ diff -u -I '#.*' test{1,2}
$ # comparing files with both comment and regular changes
$ diff -u -I '#.*' test{2,3}
--- test2       2011-07-20 16:38:59.717701430 +0200
+++ test3       2011-07-20 16:39:10.187701435 +0200
@@ -1,2 +1,2 @@
-text
+changed text
 #comment

Альтернативный способ

Поскольку пока нет ответа, объясняющего, как правильно использовать эту -Iопцию, я предоставлю альтернативу, которая работает в оболочке bash:

diff -u -B <(grep -vE '^\s*(#|$)' test1)  <(grep -vE '^\s*(#|$)' test2)
  • diff -u - унифицированный дифференциал
    • -B - игнорировать пустые строки
  • <(command)- функция bash, называемая подстановкой процесса, которая открывает дескриптор файла для команды, что устраняет необходимость во временном файле
  • grep - команда для печати строк (не) соответствующих шаблону
    • -v - показать несовпадающие строки
    • E - использовать расширенные регулярные выражения
    • '^\s*(#|$)' - регулярное выражение, соответствующее комментариям и пустым строкам
      • ^ - сопоставить начало строки
      • \s* - сопоставлять пробелы (табуляции и пробелы), если таковые имеются
      • (#|$) соответствует хеш-метке или, альтернативно, концу строки
Lekensteyn
источник
6

Пытаться:

diff -b -I '^#' -I '^ #' file1 file2

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

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

Мы можем прочитать в diffutilsруководстве:

Однако, -Iтолько игнорирует вставку или удаление строк, которые содержат регулярное выражение, если каждая измененная строка в блоке (каждая вставка и каждое удаление) соответствует регулярному выражению.

Другими словами, для каждого невосполнимого изменения diffпечатается полный набор изменений в его окрестности, включая игнорируемые. Вы можете указать более одного регулярного выражения для игнорируемых строк, используя более одного -Iпараметра. diffпытается сопоставить каждую строку с каждым регулярным выражением, начиная с последнего заданного.

Это поведение также хорошо объясняется Армель здесь .

Связанный: Как я могу выполнить diff, который игнорирует все комментарии?

kenorb
источник
2

После поиска по сети, альтернативный способ Лекенштейна - лучший, который я нашел.

Но я хочу использовать вывод diff как патч ... и есть проблема, потому что номер строки записывается из-за "grep -v".

Поэтому я намерен улучшить эту командную строку:

diff -u -B <(sed 's/^[[:blank:]]*#.*$/ /' file1)  <(sed 's/^[[:blank:]]*#.*$/ /' file2)

Это не идеально, но номер строки хранится в файле патча.

Однако, если вместо строки комментария будет добавлена ​​новая строка ... комментарий будет производить Hunk FAILED при исправлении, как мы видим ниже.

File test1:
  text
  #comment
  other text
File test2:
  text
  new line here
  #comment changed
  other text changed

проверить сейчас нашу команду

$ echo -e "#!/usr/bin/sed -f\ns/^[[:blank:]]*#.*$/ /" > outcom.sed
$ echo "diff -u -B <(./outcom.sed \$1)  <(./outcom.sed \$2)" > mydiff.sh
$ chmod +x mydiff.sh outcom.sed
$ ./mydiff.sh file1 file2 > file.dif
$ cat file.dif
--- /dev/fd/63  2014-08-23 10:05:08.000000000 +0200
+++ /dev/fd/62  2014-08-23 10:05:08.000000000 +0200
@@ -1,2 +1,3 @@
 text
+new line

-other text
+other text changed

/ dev / fd / 62 & / dev / fd / 63 - это файл, созданный путем подстановки процесса. Строка между "+ новая строка" и "-other text" является символом пробела по умолчанию, определенным в нашем выражении sed для замены комментариев.

А теперь, что будет, когда мы применим этот патч:

$ patch -p0 file1 < file.dif 
patching file file1
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED -- saving rejects to file file1.rej

Решение состоит в том, чтобы не использовать унифицированный формат diff без -u

$ echo "diff -B <(./outcom.sed \$1)  <(./outcom.sed \$2)" > mydiff.sh
$ ./mydiff.sh file1 file2 > file.dif
$ cat file.dif
1a2
> new line
3c4
< other text
---
> other text changed
$ patch -p0 file1 < file.dif 
patching file file1
$ cat file1
text
new line
#comment
other text changed

теперь рабочий файл патча (без гарантии результата в очень сложном процессе сравнения).

syjust
источник
Ваш унифицированный diff не применяется из-за различий в контексте. Вы можете использовать, diff -U0 one twoчтобы отключить контекст. Для исправления есть множество инструментов, которые могут быть лучше подходят, такие как kdiff3.
Лекенштейн
Спасибо за -U0возможность отключить контекст. Примечание: kdiff3 - это графический инструмент. Мне нужен автоматический инструмент для управления атрибутами git merge.
syjust
vimdiffподдерживает трехстороннее слияние, возможно, стоит посмотреть.
Лекенштейн
если быть более точным, мне нужен инструмент-скрипт для автоматизации процесса слияния git с исключениями в сценарии sql. kdiff3 и vimdiff - это интерактивные инструменты, которые в моем случае непригодны.
syjust
1

Я обычно игнорирую этот беспорядок:

  • Генерация некомментированных версий с использованием grep -v "^#" | cat -sи различий тех или ...
  • Используя, vim -dчтобы посмотреть на файлы. Подсветка синтаксиса позволяет сделать различия между комментариями и комментариями, не являющимися комментариями, совершенно очевидными. Подсветка diff различий в строке, чтобы вы могли сразу увидеть, какие значения или части значений были изменены, делает это моим любимым.
Калеб
источник
0

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

egrep -v "^$|^[[:space:]]*#" /path/to/file

или вы можете сделать

sed -e '/^#.*/d' -e 's/#.*//g' | cat -s
человек, любящий учиться
источник