11

ARGVにあるものの出力を「メモ化」する「 cacheme」コマンドの実装を探しています。実行したことがない場合は、実行し、出力をいくらか記憶します。実行した場合は、ファイルの出力をコピーするだけです (さらに良いことに、出力とエラーの両方をそれぞれ &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

この例は、出力がないため、少しばかげています。sleep-1-and-echo-hello-world.shのようなスクリプトでテストするのが理想的です。

完全なコマンド名とユーザー名のハッシュを使用して /tmp/ にファイルを作成する小さなスクリプトを作成しましたが、何かが既に存在していると確信しています。

これについて何か知っていますか?

4

8 に答える 8

7

オプションの引数として有効期限も追加することで、上記のソリューションをいくらか改善しました。

#!/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="$@"
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}"
于 2016-01-03T06:02:38.293 に答える
5

gnuplot のパイプシェルコマンドからのプロットを高速化したかったので、bash 用の単純なキャッシュスクリプトを実装しました。任意のコマンドの出力をキャッシュするために使用できます。引数が同じで、引数に渡されたファイルが変更されていない限り、キャッシュが使用されます。システムはクリーンアップを担当します。

#!/bin/bash

# hash all arguments
KEY="$@"

# hash last modified dates of any files
for arg in "$@"
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
  $@ | tee $FILE
fi

スクリプトcacheに名前を付け、実行可能フラグを設定して、PATH. 次に、コマンドの前に を付けcacheて使用します。

于 2016-03-20T12:33:20.563 に答える
2

Bash用のメモ化ライブラリであるbash-cacheを作成しました。これは、あなたが説明しているように正確に機能します。これは特に Bash 関数をキャッシュするように設計されていますが、他のコマンドの呼び出しを関数でラップできることは明らかです。

これは、多くの単純なキャッシュ メカニズムでは見逃される多くのエッジ ケースの動作を処理します。元の呼び出しの終了コードを報告し、stdout と stderr を別々に保持し、出力の末尾の空白を保持します ($()コマンド置換は末尾の空白を切り捨てます)。

デモ:

# Define function normally, then decorate it with bc::cache
$ maybe_sleep() {
  sleep "$@"
  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 秒であることがわかります。このベンチマーク ユーティリティは、特定の呼び出しをキャッシュする価値があるかどうかを判断するのに役立ちます。

于 2018-05-09T16:16:50.573 に答える
1

この単純なシェル スクリプト (テストされていません) はどうですか?

#!/bin/sh

mkdir -p cache

cachefile=cache/cache

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

test -f "$cachefile" || "$@" > "$cachefile"
cat "$cachefile"
于 2012-08-10T12:05:02.663 に答える
0

私がルビーで思いついた解決策はこれです。誰かが最適化を見ていますか?

#!/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
于 2012-08-10T11:07:07.607 に答える