Я пытаюсь сделать для себя небольшую утилиту в командной строке - для данного текущего рабочего каталога я хотел бы найти последний добавленный файл и mv
его в имя, заданное аргументом
. Я пытался добиться этого с помощью:
лс -тр | хвост -n 1 | xargs -0 -J % mv % SQL_warning_2.png
...однако bash сообщает:
mv: rename Screenshot 2020-06-22 at 17.53.23.png
to SQL_warning_2.png: No such file or directory
Я предполагаю, что это означает, что xargs создает отдельные аргументы через пробелы в имени файла: Скриншот 2020-06- 22 at 17.53.23.png
, но я не знаю флага -print0
для ls
.
Если некоторые из моих предположений неверны, прошу меня извинить — я очень плохо знаком с xargs
Поскольку вы, кажется, предполагаете, что ваши имена файлов не содержат символы новой строки (, вы выбираете один из них, используя tail
), вы можете использовать просто
ls -tr | tail -n 1 | xargs -I % mv -- % SQL_warning_2.png
-I %
заставит xargs
вызывать утилиту один раз для каждой прочитанной строки , с заменой %
текстом строки. Напротив, -J %
разбивает строку на пробелы:
$ echo 1 2 3 | xargs -J {} printf '"%s"\n' {}
"1"
"2"
"3"
$ echo 1 2 3 | xargs -I {} printf '"%s"\n' {}
"1 2 3"
Кажется, вы хотите переименовать последний измененный файл в текущем каталоге. Это можно сделать с помощью оболочки zsh
следующим образом:
mv -- *(.om[1]) SQL_warning_2.png
Это приведет к вызову mv
с последним измененным именем обычного файла в качестве первого аргумента. Это om[1]
в квалификаторе glob, который сортирует список имен по метке времени mtime (от самого нового к самому старому )и выбирает первое имя. Предыдущая точка выбирает только обычные файлы (, а не каталоги и т. д.)
Изbash
:
zsh -c 'mv -- *(.om[1]) SQL_warning_2.png'
Я бы сделал так:
mv -- "$(ls -t|head -n1)" new_filename
(здесь предполагается, что имя самого нового файла в текущем каталоге не содержит символов новой строки ).
Да, с -0
, xargs
ожидает список с разделителями NUL -на стандартном вводе. Здесь вы передаете ему часть имени самого нового файла после последних символов новой строки в нем, за которым следует символ новой строки (, этот символ новой строки добавляется ls
, а не часть имени файла ).
В этом вводе нет NUL, поэтому xargs
принимает весь ввод как один аргумент для передачи в mv
, который содержит этот символ новой строки, поэтому он будет работать правильно, только если имя самого нового файла содержит один и только один символ новой строки, и это был последний символ в имени файла.
Здесь вам нужно убедиться, что ls
выводит список с разделителями NUL -вместо списка с разделителями -новой строки, но я не знаю ни одной ls
реализации, которая может.
Или вам нужно вернуться к стандартному xargs
формату ввода (без -0
), где аргументы разделены пробелами (, список которых зависит от xargs
реализации и, возможно, локали )или символы новой строки, и где "..."
, '...'
и \
используются для экранирования тех и других (иначе, чем операторы цитирования одной и той же оболочки ). Поскольку некоторые реализации xargs
пытаются интерпретировать свой ввод как текст, но имена файлов могут содержать любые байтовые значения (, отличные от NUL и /
), вам также потребуется выполнить эту обработку в локали C.
export LC_ALL=C
ls -td./* | awk -v q="'" '
{gsub(/"/, "\"\\\"\"")} # escape "
NR == 1 {printf "\"%s", $0; next}
/\// {exit} # a / means the start of the second file
{printf "\"\\\n\"%s", $0} # escape newlines
END {if (NR) print "\""}' |
xargs -J % mv % newname
Как видите,надежное использование xargs
— сплошная боль. Некоторые реализации xargs
также имеют очень низкий предел размера аргументов или строк ввода. Обратите внимание, что -J
не является переносимым расширением BSD -.
Работа с произвольными именами файлов с помощью утилит на основе строки -, таких как ls
, также очень сложна.
Лучше всего использовать zsh
, который сам может сортировать список файлов по времени модификации, как показал @Kusalananda:
mv -- *(.om[1]) newname
В bash
вы также можете сделать:
IFS= read -rd / newest < <(ls -td./*) && newest=${newest%.}
newest=${newest%?} # strip newline
[ -n "$newest" ] && mv "$newest" newname
(также будет работать в ksh93 или zsh ). Как и в предыдущем подходе xargs
, мы используем ./*
, чтобы определить, с какой строки начинается второй файл в списке.