Как отсортировать массив zsh по дате изменения?

Как отсортировать массив zsh по дате изменения?

Как отсортировать массив 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}#*:})

Формат %Nzstat (для получения временных меток с разрешением в наносекунды) требует 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).

Связанный контент