mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-19 14:12:46 +08:00
Add path mtime
(#9057)
This can be used to print the modification time, like `stat` with some options. The reason is that `stat` has caused us a number of portability headaches: 1. It's not available everywhere by default 2. The versions are quite different For instance, with GNU stat it's `stat -c '%Y'`, with macOS it's `stat -f %m`. So now checking a cache file can be done just with builtins.
This commit is contained in:
parent
d9ee5d3863
commit
5dfb64b547
|
@ -17,6 +17,7 @@ Synopsis
|
|||
path is GENERAL_OPTIONS [(-v | --invert)] [(-t | --type) TYPE]
|
||||
[-d] [-f] [-l] [-r] [-w] [-x]
|
||||
[(-p | --perm) PERMISSION] [PATH ...]
|
||||
path mtime GENERAL_OPTIONS [(-R | --relative)] [PATH ...]
|
||||
path normalize GENERAL_OPTIONS [PATH ...]
|
||||
path resolve GENERAL_OPTIONS [PATH ...]
|
||||
path change-extension GENERAL_OPTIONS EXTENSION [PATH ...]
|
||||
|
@ -234,6 +235,40 @@ Examples
|
|||
>_ path is -fx /bin/sh
|
||||
# /bin/sh is usually an executable file, so this returns true.
|
||||
|
||||
"mtime" subcommand
|
||||
-----------------------
|
||||
|
||||
::
|
||||
|
||||
path mtime [-z | --null-in] [-Z | --null-out] [-q | --quiet] [-R | --relative] [PATH ...]
|
||||
|
||||
``path mtime`` returns the last modification time ("mtime" in unix jargon) of the given paths, in seconds since the unix epoch (the beginning of the 1st of January 1970).
|
||||
|
||||
With ``--relative`` (or ``-R``), it prints the number of seconds since the modification time. It only reads the current time once at start, so in case multiple paths are given the times are all relative to the *start* of ``path mtime -R`` running.
|
||||
|
||||
If you want to know if a file is newer or older than another file, consider using ``test -nt`` instead. See :ref:`the test documentation <cmd-test>`.
|
||||
|
||||
It returns 0 if reading mtime for any path succeeded.
|
||||
|
||||
Examples
|
||||
^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
>_ date +%s
|
||||
# This prints the current time as seconds since the epoch
|
||||
1657217847
|
||||
|
||||
>_ path mtime /etc/
|
||||
1657213796
|
||||
|
||||
>_ path mtime -R /etc/
|
||||
4078
|
||||
# So /etc/ on this system was last modified a little over an hour ago
|
||||
|
||||
# This is the same as
|
||||
>_ math (date +%s) - (path mtime /etc/)
|
||||
|
||||
"normalize" subcommand
|
||||
-----------------------
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a basename -d 'G
|
|||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a dirname -d 'Give dirname for given paths'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a extension -d 'Give extension for given paths'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a change-extension -d 'Change extension for given paths'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a mtime -d 'Show modification time'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a normalize -d 'Normalize given paths (remove ./, resolve ../ against other components..)'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a resolve -d 'Normalize given paths and resolve symlinks'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -lt 2" -a filter -d 'Print paths that match a filter'
|
||||
|
@ -22,6 +23,7 @@ complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (
|
|||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] filter is" -s r -d "Filter readable paths"
|
||||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] filter is" -s w -d "Filter writable paths"
|
||||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] filter is" -s x -d "Filter executable paths"
|
||||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] mtime" -s R -l relative -d "Show seconds since the modification time"
|
||||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] sort" \
|
||||
-l key -x -a 'basename\t"Sort only by basename" dirname\t"Sort only by dirname" path\t"Sort by full path"'
|
||||
complete -f -c path -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] sort" -s u -l unique -d 'Only leave the first of each run with the same key'
|
||||
|
|
|
@ -33,10 +33,7 @@ if test $status -eq 0 -a (count $sysver) -eq 3
|
|||
set -l age $max_age
|
||||
|
||||
if test -f "$whatis"
|
||||
# Some people use GNU tools on macOS, and GNU stat works differently.
|
||||
# However it's currently guaranteed that the macOS stat is in /usr/bin,
|
||||
# so we use that explicitly.
|
||||
set age (math (date +%s) - (/usr/bin/stat -f %m $whatis))
|
||||
set age (path mtime -R -- $whatis)
|
||||
end
|
||||
|
||||
MANPATH="$dir" apropos "^$argv"
|
||||
|
|
|
@ -14,7 +14,7 @@ function __fish_print_eopkg_packages
|
|||
set -l cache_file $xdg_cache_home/.eopkg-installed-cache.$USER
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (math (date +%s) - (stat -c '%Y' $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 500
|
||||
if test $age -lt $max_age
|
||||
return 0
|
||||
|
@ -28,7 +28,7 @@ function __fish_print_eopkg_packages
|
|||
set -l cache_file $xdg_cache_home/.eopkg-available-cache.$USER
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (math (date +%s) - (stat -c '%Y' $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 500
|
||||
if test $age -lt $max_age
|
||||
return 0
|
||||
|
|
|
@ -12,7 +12,7 @@ function __fish_print_pacman_packages
|
|||
set -l cache_file $xdg_cache_home/.pac-cache.$USER
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (math (date +%s) - (stat -c '%Y' $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 250
|
||||
if test $age -lt $max_age
|
||||
return
|
||||
|
|
|
@ -2,21 +2,19 @@ function __fish_print_port_packages
|
|||
type -q -f port || return 1
|
||||
|
||||
# port needs caching, as it tends to be slow
|
||||
# BSD find is used for determining file age because HFS+ and APFS
|
||||
# don't save unix time, but the actual date. Also BSD stat is vastly
|
||||
# different from linux stat and converting its time format is tedious
|
||||
set -l xdg_cache_home (__fish_make_cache_dir)
|
||||
or return
|
||||
|
||||
set -l cache_file $xdg_cache_home/.port-cache.$USER
|
||||
if test -e $cache_file
|
||||
# Delete if cache is older than 15 minutes
|
||||
find "$cache_file" -ctime +15m | awk '{$1=$1;print}' | xargs rm
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 250
|
||||
if test $age -lt $max_age
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Remove trailing whitespace and pipe into cache file
|
||||
printf "all\ncurrent\nactive\ninactive\ninstalled\nuninstalled\noutdated" >$cache_file
|
||||
port echo all | awk '{$1=$1};1' >>$cache_file &
|
||||
|
|
|
@ -8,20 +8,12 @@ function __fish_print_rpm_packages
|
|||
set -l xdg_cache_home (__fish_make_cache_dir)
|
||||
or return
|
||||
|
||||
set -l fmt_mtime (
|
||||
if stat --version 2>/dev/null >/dev/null
|
||||
echo -- -c%Y # GNU
|
||||
else
|
||||
echo -- -f%m # BSD
|
||||
end
|
||||
)
|
||||
|
||||
if type -q -f /usr/share/yum-cli/completion-helper.py
|
||||
# If the cache is less than six hours old, we do not recalculate it
|
||||
set -l cache_file $xdg_cache_home/.yum-cache.$USER
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (math (date +%s) - (stat $fmt_mtime $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 21600
|
||||
if test $age -lt $max_age
|
||||
return
|
||||
|
@ -40,7 +32,7 @@ function __fish_print_rpm_packages
|
|||
set -l cache_file $xdg_cache_home/.rpm-cache.$USER
|
||||
if test -f $cache_file
|
||||
cat $cache_file
|
||||
set -l age (math (date +%s) - (stat $fmt_mtime $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 250
|
||||
if test $age -lt $max_age
|
||||
return
|
||||
|
|
|
@ -11,7 +11,7 @@ function __fish_print_xbps_packages
|
|||
if not set -q _flag_installed
|
||||
set -l cache_file $xdg_cache_home/.xbps-cache.$USER
|
||||
if test -f $cache_file
|
||||
set -l age (math (date +%s) - (stat -c '%Y' $cache_file))
|
||||
set -l age (path mtime -R -- $cache_file)
|
||||
set -l max_age 300
|
||||
if test $age -lt $max_age
|
||||
cat $cache_file
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -159,6 +160,7 @@ struct options_t { //!OCLINT(too many fields)
|
|||
bool perm_valid = false;
|
||||
bool type_valid = false;
|
||||
bool invert_valid = false;
|
||||
bool relative_valid = false;
|
||||
bool reverse_valid = false;
|
||||
bool key_valid = false;
|
||||
bool unique_valid = false;
|
||||
|
@ -179,6 +181,7 @@ struct options_t { //!OCLINT(too many fields)
|
|||
path_perm_flags_t perm = 0;
|
||||
|
||||
bool invert = false;
|
||||
bool relative = false;
|
||||
bool reverse = false;
|
||||
|
||||
const wchar_t *arg1 = nullptr;
|
||||
|
@ -307,6 +310,16 @@ static int handle_flag_perms(const wchar_t **argv, parser_t &parser, io_streams_
|
|||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
static int handle_flag_R(const wchar_t **argv, parser_t &parser, io_streams_t &streams,
|
||||
const wgetopter_t &w, options_t *opts) {
|
||||
if (opts->relative_valid) {
|
||||
opts->relative = true;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
path_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
static int handle_flag_r(const wchar_t **argv, parser_t &parser, io_streams_t &streams,
|
||||
const wgetopter_t &w, options_t *opts) {
|
||||
if (opts->reverse_valid) {
|
||||
|
@ -398,6 +411,7 @@ static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath co
|
|||
short_opts.append(L"fld");
|
||||
}
|
||||
if (opts->invert_valid) short_opts.append(L"v");
|
||||
if (opts->relative_valid) short_opts.append(L"R");
|
||||
if (opts->reverse_valid) short_opts.append(L"r");
|
||||
if (opts->unique_valid) short_opts.append(L"u");
|
||||
return short_opts;
|
||||
|
@ -413,6 +427,7 @@ static const struct woption long_options[] = {
|
|||
{L"perm", required_argument, nullptr, 'p'},
|
||||
{L"type", required_argument, nullptr, 't'},
|
||||
{L"invert", no_argument, nullptr, 'v'},
|
||||
{L"relative", no_argument, nullptr, 'R'},
|
||||
{L"reverse", no_argument, nullptr, 'r'},
|
||||
{L"unique", no_argument, nullptr, 'u'},
|
||||
{L"key", required_argument, nullptr, 1},
|
||||
|
@ -427,6 +442,7 @@ static const std::unordered_map<char, decltype(*handle_flag_q)> flag_to_function
|
|||
{'l', handle_flag_l}, {'d', handle_flag_d},
|
||||
{'l', handle_flag_l}, {'d', handle_flag_d},
|
||||
{'u', handle_flag_u}, {1, handle_flag_key},
|
||||
{'R', handle_flag_R},
|
||||
};
|
||||
|
||||
/// Parse the arguments for flags recognized by a specific string subcommand.
|
||||
|
@ -586,6 +602,36 @@ static bool filter_path(options_t opts, const wcstring &path) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static int path_mtime(parser_t &parser, io_streams_t &streams, int argc, const wchar_t **argv) {
|
||||
options_t opts;
|
||||
opts.relative_valid = true;
|
||||
int optind;
|
||||
int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
int n_transformed = 0;
|
||||
|
||||
time_t t = std::time(nullptr);
|
||||
|
||||
arg_iterator_t aiter(argv, optind, streams, opts.null_in);
|
||||
while (const wcstring *arg = aiter.nextstr()) {
|
||||
auto ret = file_id_for_path(*arg);
|
||||
|
||||
if (ret != kInvalidFileID) {
|
||||
if (opts.quiet) return STATUS_CMD_OK;
|
||||
n_transformed++;
|
||||
|
||||
if (!opts.relative) {
|
||||
path_out(streams, opts, to_string(ret.change_seconds));
|
||||
} else {
|
||||
path_out(streams, opts, to_string(t - ret.change_seconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n_transformed > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
static int path_normalize(parser_t &parser, io_streams_t &streams, int argc, const wchar_t **argv) {
|
||||
return path_transform(parser, streams, argc, argv, normalize_helper);
|
||||
}
|
||||
|
@ -868,6 +914,7 @@ static constexpr const struct path_subcommand {
|
|||
{L"extension", &path_extension},
|
||||
{L"filter", &path_filter},
|
||||
{L"is", &path_is},
|
||||
{L"mtime", &path_mtime},
|
||||
{L"normalize", &path_normalize},
|
||||
{L"resolve", &path_resolve},
|
||||
{L"sort", &path_sort},
|
||||
|
|
|
@ -198,3 +198,21 @@ test (path resolve link) = (pwd -P)/link
|
|||
and echo link resolves to link
|
||||
# CHECK: link resolves to link
|
||||
|
||||
|
||||
# path mtime
|
||||
# These tests deal with *time*, so we have to account
|
||||
# for slow systems (like CI).
|
||||
# So we should only test with a lot of slack.
|
||||
|
||||
echo bananana >> foo
|
||||
test (math abs (date +%s) - (path mtime foo)) -lt 20
|
||||
or echo MTIME IS BOGUS
|
||||
|
||||
sleep 2
|
||||
|
||||
set -l mtime (path mtime --relative foo)
|
||||
test $mtime -ge 1
|
||||
or echo mtime is too small
|
||||
|
||||
test $mtime -lt 20
|
||||
or echo mtime is too large
|
||||
|
|
Loading…
Reference in New Issue
Block a user