Как отсортировать массив zsh по дате изменения?
files=( ~/a ~/b ~/c )
# how to sort files by date?
PS: Вот мой точный вариант использования ( fz
почти fzf
)
v () {
local files
files=()
command rg '^>' ~/.viminfo | cut -c3- | while read line
do
[ -f "${line/\~/$HOME}" ] && files+="$line"
done
test -f ~/.emacs.d/.cache/recentf && {
command rg --only-matching --replace '$1' '^\s*"(.*)"$' ~/.emacs.d/.cache/recentf | while read line
do
[ -f "$line" ] && files+="$line"
done
}
files="$(<<<"${(F)files}" fz --print0 --query "$*")" || return 1
files="${files//\~/$HOME}"
local ve="$ve"
test -z "$ve" && ! isSSH && ve=nvim
"${ve:-vim}" -p ${(0@)files}
: '-o opens in split view, -p in tabs. Use gt, gT, <num>gt to navigate tabs.'
}
решение1
Будет намного проще, если вы отсортируете список при его составлении.. Но если вы не можете…
Классический подход заключается в добавлении критерия сортировки к данным, затем в их сортировке, а затем в удалении добавленного хлама. Создайте массив, содержащий временные метки и имена файлов, таким образом, чтобы это было недвусмысленно и с временными метками в формате, который можно сортировать лексикографически. Отсортируйте массив (используяo
флаг расширения параметра), затем удалите префикс. Вы можете использоватьstat
модуль для получения времени модификации файла.
zmodload zsh/stat
for ((i=1; i<$#files; i++)); do times[$i]=$(stat -g -F %020s%N +mtime -L -- $files[$i]):$files[$i]; done
sorted=(${${(o)times}#*:})
Формат %N
zstat (для получения временных меток с разрешением в наносекунды) требует zsh ≥5.6. Если ваш zsh старше, удалите его, и код все равно будет работать, но сравнивая временные метки с разрешением в 1 секунду. Многие файловые системы имеют субсекундное разрешение, но я не думаю, что вы сможете получить его с stat
модулем zsh в старых версиях zsh.
Если ваш zsh слишком старый, вы можете получить более точные временные метки с помощью stat
утилитыGNU coreutils. Если он у вас есть, у вас, вероятно, есть и другие GNU coreutils, поэтому я буду использовать их. GNU coreutils обычно присутствуют в невстраиваемом Linux, но могут отсутствовать в BSD или macOS. В macOS вы можете установить их с помощью brew
. Если GNU coreutils не являются частью базовой операционной системы, вам может потребоваться изменить stat
на gstat
, sort
на gsort
и cut
на gcut
.
if (($#files)); then
sorted=(${(0@)"$(stat --printf='%040.18Y:%n\0' "$files[@]" | sort -rz | cut -z -d':' -f2-)"})
else
sorted=()
fi
Альтернативный подход zsh заключается в построении шаблона, включающего все файлы в $files
и более. Отсортируйте файлы, соответствующие этому шаблону, затем отфильтруйте его, чтобы включить только нужные файлы. Вам нужно построить весь шаблон для more_files
, что не всегда может быть практично.
more_files=(~/*(Om))
sorted=(${more_files:*files})
решение2
Вы можете использовать подход, аналогичныйВон тот:
zmodload zsh/stat
array=(file1 file2...)
# store mtimes in an associative array:
typeset -A mtime
stat -nLF %s.%N -A mtime +mtime -- $array
# sort using the Oe glob qualifier
sorted_array=(/(e['reply=($array)']nOe['REPLY=$mtime[$REPLY]'])
( %N
для наносекунд требуется zsh 5.6 или новее).
решение3
Как отметил Джефф, будет проще создать отсортированный массив, например:
set -A files $(ls -trd -- ~/a ~/b ~/c)
Здесь разделение вывода ls
по $IFS
символу (по умолчанию содержит SPC, TAB, LF и NUL). Чтобы разделить только по LF (чтобы иметь возможность работать с именами файлов, которые содержат символы SPC или TAB (но не LF, очевидно)), вы можете установить IFS=$'\n'
или использовать f
флаг расширения параметра:
files=(${(f)"$(ls -trd -- $files)"})
(здесь также используется синтаксис в стиле zsh array=(...)
вместо синтаксиса в стиле ksh88, set -A array ...
поскольку этот синтаксис в любом случае уже специфичен для zsh).