Создание канала из `ls` в `mv`

Я пытаюсь сделать для себя небольшую утилиту в командной строке - для данного текущего рабочего каталога я хотел бы найти последний добавленный файл и 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

1
22.06.2020, 18:16
3 ответа

Поскольку вы, кажется, предполагаете, что ваши имена файлов не содержат символы новой строки (, вы выбираете один из них, используя 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'
4
18.03.2021, 23:25

Я бы сделал так:

mv -- "$(ls -t|head -n1)" new_filename

(здесь предполагается, что имя самого нового файла в текущем каталоге не содержит символов новой строки ).

2
18.03.2021, 23:25

Да, с -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, мы используем ./*, чтобы определить, с какой строки начинается второй файл в списке.

0
18.03.2021, 23:25

Теги

Похожие вопросы