#bash #awk #grep #anchor #newline
Вопрос:
У меня есть два файла:
- $hashfile: Хэши и ./относительный/путь/к/файлу/имена, оба в одной строке, разделенные 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, похоже, нет решения.