Как сделать эквивалент "grep что-то * -Rin" в списке файлов tar.gz?
У меня есть куча файлов tar.gz, и я хочу сделать "grep что-то * -Rin", как если бы они не были tar.gzed. Я хочу, чтобы они сохранялись в неизменном виде, но сгребаем их на лету и нахожу вхождения моего grep с префиксным файлом и номером строки.
Что-то вроде:
grep mytoken1 *.tar.gz -Rin
и получить что-то вроде:
my1.tar.gz, dir1 / file2: 123: mytoken1 находится в этой строке my2.tar.gz,dir2/file3:233:mytoken1 также находится в этой другой строке [...]
Есть ли способ сделать это?
5 ответов
zgrep (или, как мы полагаем, grep с флагом -Z) позволит вам создавать сжатые файлы grep, и я думаю, что многое скажет вам о том, что вы хотите, но это не даст вам имя файла, если немного больше не смотреть на заголовок:(
Найден в скрипте Unix для поиска в файле.tar или.gz:
Сценарий:
for file in $(tar -tzf file.tar.gz | grep '\.txt'); do
tar -Oxzf file.tar.gz "$file" | grep -B 3 --label="$file" -H "string-or-regex"
done
будет уважать границы файлов и сообщать имена файлов. | grep '\.txt
часть может быть адаптирована к вашим потребностям или отброшена.
(-z
говорит tar
это gzip
сжат. -t
перечисляет содержимое. -x
экстракты. -O
перенаправляет на стандартный вывод, а не на файловую систему. Старшая tar
с может не иметь -O
или же -z
флаг, и будут хотеть флаги без -
например tar tz file.tar.gz
)
Если ваш grep не поддерживает эти флаги, то можно использовать awk:
#!/usr/bin/awk -f
BEGIN { context=3; }
{ add_buffer($0) }
/pattern/ { print_buffer() }
function add_buffer(line)
{
buffer[NR % context]=line
}
function print_buffer()
{
for(i = max(1, NR-context+1); i <= NR; i++) {
print buffer[i % context]
}
}
function max(a,b)
{
if (a > b) { return a } else { return b }
}
Это не будет объединять смежные совпадения, в отличие от grep -B, и, таким образом, может повторять строки, которые находятся в пределах 3 строк двух разных совпадений.
Одним из способов было бы использовать этот быстрый взлом:
#!/usr/bin/ruby
=begin
Quick-and-dirty way to grep in *.tar.gz archives
Assumption:
each and every file read from any of the supplied tar archives
will fit into memory. If not, the data reading has to be rewritten
(a proxy that reads line-by-line would have to be inserted)
=end
require 'rubygems'
gem 'minitar'
require 'zlib'
require 'archive/tar/minitar'
if ARGV.size < 2
STDERR.puts "#{File.basename($0)} <regexp> <file>+"
exit 1
end
regexp = Regexp.new(ARGV.shift, Regexp::IGNORECASE)
for file in ARGV
zr = Zlib::GzipReader.new(File.open(file, 'rb'))
Archive::Tar::Minitar::Reader.new(zr).each do |e|
next unless e.file?
data = e.read
if regexp =~ data
data.split(/\n/).each_with_index do |l, i|
puts "#{file},#{e.full_name}:#{i+1}:#{l}" if regexp =~ l
end
end
end
end
Нельзя сказать, что я бы порекомендовал его для больших архивов, так как каждый файл из архива читается в память (фактически, дважды).
Если вы хотите немного более эффективную для памяти версию, вам придется либо использовать другую реализацию e.read
цикл... или, может быть, с другим языком в целом.;)
Я мог бы сделать его немного более эффективным, если вы действительно заинтересованы... но он определенно не сравнится с C или другими скомпилированными языками с точки зрения сырой скорости.
Я думаю, что это будет очень сложно.
Фактически tar представляет собой объединение всех включаемых файлов с добавлением заголовков. Так что в основном grep-in-tar
функция может быть написана, чтобы справиться с этим и предоставить информацию о файле и номере строки (базовый grep с чтением заголовка и вычитанием номера строки). Я не слышал о такой программе.
Проблема с gzip. Это формат сжатия, поэтому вам нужно распаковать его, если вы хотите получить доступ к содержимому.
gunzip -c files.tgz | grep-in-tar
был бы способ сделать то, что вы хотите. На данный момент вы можете попробовать gunzip -c files.tgz | grep -Rin
но он просто скажет, что двоичный файл совпадает.
Модульный подход к *nix tools означает, что не существует простого способа сделать это эффективно с помощью grep / tar / zcat. В идеале вы хотите распаковать файлы только один раз и обработать каждый файл tar за один проход. Вот моя попытка tgz-grep:
#!/usr/bin/python
import re,sys,tarfile
exp=re.compile(sys.argv[1])
tarfiles=sys.argv[2:]
for tfile in tarfiles:
tar=tarfile.open(tfile, mode='r|gz')
for file in tar:
name=file.name
count=0
for line in tar.extractfile(file):
count += 1
if exp.search(line):
print "%s,%s:%d:%s" % (tfile, name, count, line),
Примечание: это не делает рекурсию каталога (-R) или нечувствительность к регистру (-i), или другие параметры, поддерживаемые GNU grep, но добавить их будет непросто.