Можно ли кэшировать вывод команды в Linux из CLI?

Я ищу реализацию команды "cacheme", которая " memoizes" выводит все, что есть в ARGV. Если он никогда не запускал его, он запускает его и несколько запоминает результат. Если он запустит его, он просто скопирует вывод файла (или даже лучше, как выход, так и ошибку в & 1 и 2 соответственно).

Предположим, что кто-то написал эту команду, он будет работать следующим образом.

$ time cacheme sleep 1    # first time it takes one sec
real   0m1.228s
user   0m0.140s
sys    0m0.040s

$ time cacheme sleep 1    # second time it looks for stdout in the cache (dflt expires in 1h)
#DEBUG# Cache version found! (1 minute old)

real   0m0.100s
user   0m0.100s
sys    0m0.040s

Этот пример немного глуп, потому что он не имеет выхода. В идеале это было бы протестировано на script подобно sleep1-and-echo-hello-world.sh.

Я создал небольшой script, который создает файл в/tmp/с hash с полным именем команды и именем пользователя, но я уверен, что что-то уже существует.

Знаете ли вы об этом?

Ответ 1

Улучшенное решение выше, а также добавление возраста истечения в качестве необязательного аргумента.

#!/bin/sh
# save as e.g. $HOME/.local/bin/cacheme
# and then chmod u+x $HOME/.local/bin/cacheme
VERBOSE=false
PROG="$(basename $0)"
DIR="${HOME}/.cache/${PROG}"
mkdir -p "${DIR}"
EXPIRY=600 # default to 10 minutes
# check if first argument is a number, if so use it as expiration (seconds)
[ "$1" -eq "$1" ] 2>/dev/null && EXPIRY=$1 && shift
[ "$VERBOSE" = true ] && echo "Using expiration $EXPIRY seconds"
CMD="[email protected]"
HASH=$(echo "$CMD" | md5sum | awk '{print $1}')
CACHE="$DIR/$HASH"
test -f "${CACHE}" && [ $(expr $(date +%s) - $(date -r "$CACHE" +%s)) -le $EXPIRY ] || eval "$CMD" > "${CACHE}"
cat "${CACHE}"

Ответ 2

Как насчет этой простой оболочки script (не тестировался)?

#!/bin/sh

mkdir -p cache

cachefile=cache/cache

for i in "[email protected]"
do
    cachefile=${cachefile}_$(printf %s "$i" | sed 's/./\\&/g')
done

test -f "$cachefile" || "[email protected]" > "$cachefile"
cat "$cachefile"

Ответ 3

Решение, которое я выбрал в рубине, - это. Кто-нибудь видит оптимизацию?

#!/usr/bin/env ruby

VER = '1.2'
$time_cache_secs = 3600
$cache_dir = File.expand_path("~/.cacheme")

require 'rubygems'
begin
  require 'filecache'           # gem install ruby-cache
rescue Exception => e
  puts 'gem filecache requires installation, sorry. trying to install myself'
  system  'sudo gem install -r filecache'
  puts  'Try re-running the program now.'
  exit 1
end

=begin
  # create a new cache called "my-cache", rooted in /home/simon/caches
  # with an expiry time of 30 seconds, and a file hierarchy three
  # directories deep
=end
def main
  cache = FileCache.new("cache3", $cache_dir, $time_cache_secs, 3)
  cmd = ARGV.join(' ').to_s   # caching on full command, note that quotes are stripped
  cmd = 'echo give me an argment' if cmd.length < 1

  # caches the command and retrieves it
  if cache.get('output' + cmd)
    #deb "Cache found!(for '#{cmd}')"
  else
    #deb "Cache not found! Recalculating and setting for the future"
    cache.set('output' + cmd, `#{cmd}`)
  end
  #deb 'anyway calling the cache now'
  print(cache.get('output' + cmd))
end

main

Ответ 4

Я реализовал простое кэширование script для bash, потому что я хотел ускорить построение графика из командной строки оболочки в gnuplot. Он может использоваться для кэширования вывода любой команды. Кэш используется до тех пор, пока аргументы одинаковы, а файлы, переданные в аргументах, не изменились. Система отвечает за очистку.

#!/bin/bash

# hash all arguments
KEY="[email protected]"

# hash last modified dates of any files
for arg in "[email protected]"
do
  if [ -f $arg ]
  then
    KEY+=`date -r "$arg" +\ %s`
  fi
done

# use the hash as a name for temporary file
FILE="/tmp/command_cache.`echo -n "$KEY" | md5sum | cut -c -10`"

# use cached file or execute the command and cache it
if [ -f $FILE ]
then
  cat $FILE
else
  [email protected] | tee $FILE
fi

Вы можете назвать script cache, установить исполняемый флаг и поместить его в PATH. Затем просто прикрепите любую команду с помощью cache, чтобы использовать ее.

Ответ 5

Реализация существует здесь: https://bitbucket.org/sivann/runcached/src Кэширует исполняемый путь, выводит, выводит код, запоминает аргументы. Настраиваемое выключение. Реализовано в bash, C, python, выберите то, что вам подходит.

Ответ 6

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

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

Демо-версия:

# Define function normally, then decorate it with bc::cache
$ maybe_sleep() {
  sleep "[email protected]"
  echo "Did I sleep?"
} && bc::cache maybe_sleep

# Initial call invokes the function
$ time maybe_sleep 1
Did I sleep?

real    0m1.047s
user    0m0.000s
sys     0m0.020s

# Subsequent call uses the cache
$ time maybe_sleep 1
Did I sleep?

real    0m0.044s
user    0m0.000s
sys     0m0.010s

# Invocations with different arguments are cached separately
$ time maybe_sleep 2
Did I sleep?

real    0m2.049s
user    0m0.000s
sys     0m0.020s

Есть также функция сравнения, которая показывает издержки кэширования:

$ bc::benchmark maybe_sleep 1
Original:       1.007
Cold Cache:     1.052
Warm Cache:     0.044

Таким образом, вы можете увидеть, что издержки чтения/записи (на моей машине, использующей tmpfs) составляют примерно 1/20 секунды. Эта утилита поможет вам решить, стоит ли кэшировать тот или иной вызов или нет.

Ответ 7

Улучшено после устранения ошибки:

  • Трубы выводятся в команду "tee", которая позволяет просматривать их в реальном времени, а также сохранять в кеше.
  • Сохраните цвета (например, в таких командах, как "ls --color"), используя "script --flush --quiet/dev/null --command $ CMD".
  • Избегайте вызова "exec", используя также скрипт
  • Используйте bash и [[
    #!/usr/bin/env bash

    CMD="[email protected]"
    [[ -z $CMD ]] && echo "usage: EXPIRY=600 cache cmd arg1 ... argN" && exit 1

    # set -e -x

    VERBOSE=false
    PROG="$(basename $0)"

    EXPIRY=${EXPIRY:-600}  # default to 10 minutes, can be overriden
    EXPIRE_DATE=$(date -Is -d "-$EXPIRY seconds")

    [[ $VERBOSE = true ]] && echo "Using expiration $EXPIRY seconds"

    HASH=$(echo "$CMD" | md5sum | awk '{print $1}')
    CACHEDIR="${HOME}/.cache/${PROG}"
    mkdir -p "${CACHEDIR}"
    CACHEFILE="$CACHEDIR/$HASH"

    if [[ -e $CACHEFILE ]] && [[ $(date -Is -r "$CACHEFILE") > $EXPIRE_DATE ]]; then
        cat "$CACHEFILE"
    else
        script --flush --quiet --return /dev/null --command "$CMD" | tee "$CACHEFILE"
    fi