Сопоставьте строку в конце строки в файле

#bash #awk #grep #anchor #newline

Вопрос:

У меня есть два файла:

  1. $hashfile: Хэши и ./относительный/путь/к/файлу/имена, оба в одной строке, разделенные 2 пробелами
  2. $badfiles: ./относительный/путь/к/файлу/имена, которые мне нужно найти в $hashfile, чтобы получить соответствующий хэш

Вот выдержка из хэш-файла $:

 c2c99b59f3303cafac85c2c6df6653cc  ./vm-mount.sh
058a8fb0b9366f248be32b7390e94595  ./Jerusalem_Canon EOS R5_20210601_031.jpg~
23eba1c54846de5244312047e2709f9a  ./rsync-back.sh
ff3f08f7bf45f8e9ef8b33192db3ce9a  ./vm-backup.sh
11e0d980f3b2219f65da97a0318e7dce  ./Jerusalem_Canon EOS R5_20210601_031.jpg
49fb1fb660dce09acd87861a228c899d  ./vm-test.sh
 

Вот пример файлов $badfiles, содержащих шаблоны поиска:

 ./Jerusalem_Canon EOS R5_20210601_031.jpg
./file.txt
 

Мне нужно найти в файле $hashfile шаблоны внутри файлов $badfiles и записать соответствующие строки, содержащие хэш, в третий файл $new.

До сих пор я использовал следующее:

 grep -Ff "$badfiles" "$hashfile" > "$new"
 

Однако это будет соответствовать обоим:

 058a8fb0b9366f248be32b7390e94595  ./Jerusalem_Canon EOS R5_20210601_031.jpg~
11e0d980f3b2219f65da97a0318e7dce  ./Jerusalem_Canon EOS R5_20210601_031.jpg
 

Затем я добавил $ в конце каждой строки в $badfiles и изменил команду grep на:

 grep -f "$badfiles" "$hashfile" > "$new"
 

Это сработало с небольшой тестовой папкой, но я обеспокоен тем, что поиск по шаблону, который не будет интерпретироваться как фиксированная строка, может привести к хаосу в больших файловых системах. У меня есть более 300 000 имен файлов и хэшей, некоторые из которых используют специальные символы, такие как «‘:,;<>()[]? — короче говоря, любой символ, который будет принят файловой системой Linux ext4 и/или Windows NTFS.

Есть какие-нибудь идеи?

ИЗМЕНИТЬ: Решение

По-видимому, grep не предоставляет простого решения для включения новой строки в поиск по фиксированной строке. @anubhava предложил лучшее решение с использованием awk:

 awk 'NR == FNR {a[$0]; next}
{b=$0; sub(/^S s /, "", b)}
b in a' "$badfiles" "$hashfile" > "$new"
 

Примечание: $badfiles, $hashfiles и $new-это переменные, содержащие имена файлов.

Приведенный выше синтаксис лучше всего описан здесь в разделе «Обработка двух файлов». NR содержит номера строк, считанные до сих пор из всех файлов, в то FNR время как номера строк, считанные до сих пор из текущего файла, сохраняются. Поэтому, когда awk закончит чтение $badfiles и прочитает первую строку $hashfile, NR содержит сумму всех прочитанных до сих пор строк и FNR равна 1, так как это первая строка нового файла. {a[$0]; next} считывает файл $badfiles в массив, ; next предотвращает выполнение программой последующих условий и действий до тех пор, пока не будут прочитаны все $badfiles, то есть пока NR == FNR не будет установлено значение false.

При чтении $hashfile $0 (прочитанная строка) присваивается b ( b=$0 ). sub(/^S s /, "", b) заменяет один или несколько пробелов ( S ) в начале строки ( ^ ), за которыми следует один или несколько пробелов ( s ) "" (пустой строкой) в переменной b . При этом внутри переменной остается только ./путь/к/файлу b .

Последняя строка b in a' "$badfiles" "$hashfile" > "$new" смотрит, найдена ли переменная b a , и, если да, копирует строку в $hashfile в файл $new. Если все строки в $badfiles содержат соответствующую запись в $hashfile, соответствующие строки $hashfile с хэшами копируются в $new.

Поскольку хэш-значение перед именем файла имеет фиксированную длину, оператор awk можно упростить следующим образом:

 awk 'NR == FNR {a[$0]; next}
{b=substr($0,35)}
b in a' "$badfiles" "$hashfile" > "$new"
 

Приведенное выше substr() утверждение занимает входную строку $0 и удаляет первые 34 символа, считая от 1. Затем подстрока b начинается с позиции 35. Это очень похоже на извлечение подстрок, например, в bash ${mystring:34} . Обратите внимание, что извлечение подстроки bash начинается с 0.

Теперь я использую вариант этой команды awk для создания нового хэш-файла, содержащего все хэши файлов, кроме перечисленных в $deletedfiles :

 awk 'NR == FNR {a[$0]; next}
{b=substr($0,35)}
!(b in a)' "$deletedfiles" "$hashfile" > "$new"
 

С помощью приведенной выше команды каждая строка b (из $hashfile), НЕ найденная в $deletedfiles, копирует соответствующую строку из $hashfile в $new. Следует обратить особое внимание на пустой файл $deletedfiles: если $deletedfiles-пустой файл, то файл $new тоже будет пустым! Ожидаемый результат заключается в том, что файл $new идентичен хэш-файлу$.

Это решение работает очень хорошо (и быстро), даже с 200 000-300 000 именами файлов в одном хэш-файле.

Комментарии:

1. Может sed быть, ваш файл шаблона для преобразования всех странных символов в точки? Не лучшее решение, но может упростить проблему.

Ответ №1:

Это awk решение должно сработать для вас:

 awk 'FNR == NR {srch[$0]; next} 
{s = $0; sub(/^[^[:blank:]] [[:blank:]] /, "", s)}
s in srch' badfiles hashfile

11e0d980f3b2219f65da97a0318e7dce  ./Jerusalem_Canon EOS R5_20210601_031.jpg
 

Это решение сначала сохраняет все строки из badfiles массива srch . Затем из hashfile него удаляется текст до первого пробела, а затем печатается каждая строка из того же файла, если оставшаяся часть найдена в srch массиве.

Комментарии:

1. awk: cmd. line:1: FNR == NR {srch[./xxhash_replace_tst.sh]; next} awk: cmd. line:1: ^ syntax error awk: cmd. line:1: FNR == NR {srch[./xxhash_replace_tst.sh]; next} awk: cmd. line:1: ^ unterminated regexp awk: cmd. line:2: error: Unmatched ( or (: /xxhash_replace_tst.sh; sub(/ Спасибо @anubhava. Я попробовал ваше предложение, но оно привело к ошибкам. Он оценивает строку поиска как регулярное выражение. Это не то, чего я хочу. Строки внутри плохих файлов должны приниматься как фиксированные строки, как это делает опция grep-F, но с новой строкой.

2. Почему вы сделали srch[./xxhash_replace_tst.sh]; это вместо srch[$0]; того, как я предлагал?

3. Извините, пытаюсь разобраться с форматированием в комментариях. Я использовал srch[$0], то, что вы видите выше, — это ошибки, которые я получаю при обработке плохих файлов. На самом деле он пытается интерпретировать точку (.). Каждый путь в плохих файлах начинается с ./ . ./xxhash_replace_tst.sh Это строка внутри плохих файлов.

4. В моей команде awk нет интерпретации точек. Команда, которую я опубликовал, является проверенной и рабочей командой в gnu-awk, а также в BSD-awk.

5. Я использую ваш код в небольшой тестовой папке, где я имитирую битовую гниль в файлах. Это прекрасно работает. Смотрите здесь: 5f2aacccec64dba5a79016fed368fc9e ./files.txt 11e0d980f3b2219f65da97a0318e7dce ./"Jerusalem: shot with 'Canon EOS R5' [20210601] 031".jpg . Мне было интересно, как это будет масштабироваться до больших хэш-файлов с несколькими сотнями тысяч строк (файлов и хэшей)? grep очень быстро работал с этими файлами. Спасибо, что поделились решением. Жаль, что с grep, похоже, нет решения.