С расширением подсказки % N
дает вам это (текущий сценарий / исходный-файл / функция).
Итак, вы можете сделать:
print -P %N
или получить его от $ {(%): -% N}
.
См. Также $ funcsourcetrace
для файлов стека вызовов.
Существует так много случаев, когда то, что вы хотите, не будет работать, что вы не найдете универсальный инструмент, который дает действительно хорошие результаты:
locate myfile
) wget http://news.example.com/headlines
) дата
) pwgen
) Если вы решаете, к каким командам применить инструмент, то вам нужен инструмент сборки: инструмент, который запускает команды, если их вывод не своевременно. Почтенный make будет не очень хорош: приходится определять зависимости вручную, в частности нужно тщательно разделять кеши для разных команд и вручную отзывать кеши при смене команды, а хранить нужно каждый кеш в отдельном файле, что неудобно. Одна из многих альтернатив может быть более подходящей для этой задачи, возможно, SCons, которая поддерживает анализ зависимостей на основе контрольной суммы и временной метки, имеет механизм кэширования поверх этого и может быть настроены путем написания кода Python.
Это скорее выдумка, чем реальный ответ, но он слишком длинный для комментария. Если это неуместно, я удалю это. Просто дай мне знать. пожимает плечами
Во-первых, я думаю, что основная проблема в том, что вы думаете об этом в терминах "команда --> результат". Если бы это были «файл(ы) --> результат», вы могли бы просто использовать make
. Если есть только небольшое фиксированное количество команд, которые ведут из файла(ов) к результатам, вы все равно можете использовать make
: напишите цель make
для каждой команды.
Если вы настаиваете на том, что это должна быть «произвольная команда --> результат», первое, что приходит на ум, это какой-то REPL или оболочка на языке-X. В эти дни нет недостатка в этих вещах, кажется, новые появляются каждые две недели или около того. Суть в том, что они позволят вам работать с структурированными данными, а не только со строкой (командой) и набором файлов.
Получение контрольной суммы dev
+ inode
+ size
+ mtime
кажется разумным. Вы всегда можете выполнить полное сравнение, если беспокоитесь о ложных срабатываниях (примечание: полное сравнение всегда быстрее, чем использование SHA-* для каждого файла и сравнение результатов). Для серверной части вы можете использовать SQLite, но вам понадобится какой-то механизм для истечения срока действия старых записей.
Было бы проще, если бы вы могли указать больше ограничений на то, какими могут быть команды и/или файлы. Стремление к полностью общему кэшированию «команда -> результат», но при этом отслеживание изменений во входных файлах кажется слишком амбициозным.
Я обнаружил этот вопрос, когда писал свой сценарий примерно для той же цели, и ваша идея о кэшировании файлов с их dev+inode+size+mtime показалась мне очень полезной, поэтому я добавил ее. Ваша идея и моя реализация отличаются, так как я поздно наткнулся на эту страницу и решил не переписывать все:
Сценарий для простоты хранит записи кэша в одном файле YAML. Вы по-прежнему можете поделиться этим файлом на нескольких машинах, но есть риск RCE, плюс вам нужно будет написать блокирующую оболочку из-за TOCTOU в файле YAML.
Вероятно, это будет работать только в Linux и, если повезет, в других Unix-системах.
Используйте на свой страх и риск. Содержимое вашего кэша не будет защищено.
Сначала запустите gem install chronic_duration
.
#!/usr/bin/env ruby
# Usage: memoize [-D DATABASE] [-T TIMEOUT] [-F] [--] COMMAND [ARG]...
# or memoize [-D DATABASE] --cleanup
#
# OPTIONS
# -D DATABASE Store entries in YAML format in DATABASE file.
# -T TIMEOUT Invalidate memoized entries older than TIMEOUT.
# -F Track file changes (dev+inode+size+mtime).
# --cleanup Remove all stale entries.
require 'date'
require 'optparse'
require 'digest'
require 'yaml'
require 'chronic_duration'
require 'open3'
MYSELF = File.basename(__FILE__)
DEFAULT_DBFILE = "#{Dir.home}/.config/memoize.yml"
DEFAULT_TIMEOUT = '1 week'
def fc(fpath) # File characteristic
return [:dev, :ino, :size, :mtime].map do |s|
Digest::SHA1.digest(Integer(File.stat(fpath).send(s)).to_s.b)
end.join
end
def cmdline_checksum(cmdline, fchanges)
pre_cksum_bytes = "".b
cmdline.each do |c|
characteristic = (File.exists?(c) and fchanges) ? fc(c) : c
pre_cksum_bytes += Digest::SHA1.digest(characteristic)
end
return Digest::SHA1.digest(pre_cksum_bytes)
end
def timed_out?(entry)
return (entry[:timestamp] + Integer(entry[:timeout])) < Time.now
end
def pluralize(n, singular, plural)
return (n % 100 == 11 || n % 10 != 1) ? plural : singular
end
fail "memoize: FATAL: this is a script, not a library" unless __FILE__ == $0
$dbfile = DEFAULT_DBFILE
$timeout = DEFAULT_TIMEOUT
$fchanges = false
$cleanup = false
$retcode = 0
$replay = false
ARGV.options do |o|
o.version = '2018.06.23'
o.banner = "Usage: memoize [OPTION]... [--] COMMAND [ARG]...\n"+
"Cache results of COMMAND and replay its output"
o.separator ""
o.separator "OPTIONS"
o.summary_indent = " "
o.summary_width = 17
o.on('-D=DATABASE', "Default: #{DEFAULT_DBFILE}") { |d| $dbfile = d }
o.on('-T=TIMEOUT', "Default: #{DEFAULT_TIMEOUT}") { |t| $timeout = t }
o.on('-F', "Track file changes (dev+inode+size+mtime)") { $fchanges = true }
o.on('--cleanup', "Remove all stale entries") { $cleanup = true }
end.parse!
begin
File.open($dbfile, 'a') {}
File.chmod(0600, $dbfile)
end unless File.exists?($dbfile)
db = (YAML.load(File.read($dbfile)) or {})
cmdline = ARGV
cksum = cmdline_checksum(cmdline, $fchanges)
entry = {
cmdline: cmdline,
timestamp: Time.now,
timeout: '1 week',
stdout: "",
stderr: "",
retcode: 0,
}
if $cleanup
entries = db.keys.select{|k| timed_out?(db[k]) }
c = entries.count
entries.each do |k|
db.delete(k)
end
STDERR.puts "memoize: NOTE: #{c} stale #{pluralize(c, "entry", "entries")} removed"
File.open($dbfile, 'w') { |f| f << YAML.dump(db) }
exit
end
$replay = db.key?(cksum) && (not timed_out?(db[cksum]))
if $replay
entry = db[cksum]
else
Open3.popen3(*cmdline) do |i, o, e, t|
i.close
entry[:stdout] = o.read
entry[:stderr] = e.read
entry[:retcode] = t.value.exitstatus
end
entry[:timestamp] = Time.now
entry[:timeout] = Integer(ChronicDuration.parse($timeout))
db[cksum] = entry
end
$retcode = entry[:retcode]
STDOUT.write(entry[:stdout]) # NOTE: we don't record or replay stream timing
STDERR.write(entry[:stderr])
STDOUT.flush
STDERR.flush
File.open($dbfile, 'w') { |f| f << YAML.dump(db) }
exit! $retcode