Как сместить номера имен файлов без столкновений?

Рассмотрим этот список файлов:

$ touch index-{1,2,3,4,5,6,7,8,9}.txt 

Если я хочу сместить их так, чтобы они начинались с нуля, это относительно легко :

 $ rename --verbose 's/^index-([1-9])\.txt$/$1/; $_="index-" . ($_ - 1) . ".txt"' index-*.txt 

Это работает, потому что man bash указывает, что globs отсортированы по алфавиту, поэтому он переименует index-1.txt в index-0.txt перед переименованием index-2.txt в index-1.txt .

Это ломается, если вы хотите переместиться, или если номера имеют разную длину :

 $ touch index-{10,11}.txt $ rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' index-*.txt index-10.txt not renamed: index-11.txt already exists index-11.txt renamed as index-12.txt index-1.txt not renamed: index-2.txt already exists 

Возможные долгосрочные исправления:

  • rename параметр, чтобы попытаться изменить порядок операций до тех пор, пока не произойдет столкновений.
  • rename опцию сначала переместить файлы во временный каталог, а затем переместите их обратно с новым именем.
  • rename вариант переименования файлов в два этапа, используя уникальные имена на первом шаге, которые не будут сталкиваться с оригинальными или новыми именами.
  • Способ естественной сортировки + разворот шаров Bash.

Есть ли простой способ обойти это?

  • Нестандартная сортировка вывода ls
  • Как сортировать и присоединяться в соответствии с номером / счетчиком внутри файла?
  • Сортировка вывода `ps`
  • Как отсортировать по 2 столбцам и сохранить верхнюю строку из каждой группы?
  • Как сортировать по двум полям чисел
  • «Sort -h» не работает в Redhat 5.9. Любой способ его обновления?
  • Сортировка файлов css по свойству цвета
  • мы можем получить отсортированный выход grep
  • 4 Solutions collect form web for “Как сместить номера имен файлов без столкновений?”

    Решение @ l0b0's переписано для лучшей надежности:

     printf '%s\0' index-*.txt | sort --zero-terminated --field-separator - --key 2rn | xargs -0r rename --verbose ' s/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' 

    Не стесняйтесь включать в свое решение, и после этого я удалю свой ответ.

    Обратите внимание, что эти и @ l0bo решения являются специфичными для GNU, а не Unix (GNU's Un Unix).

    В Unix есть небольшая известная стандартная команда, которая может помочь нам найти общее решение в поиске, в каком порядке должна быть выполнена серия переименований: tsort

    Скажем, у нас есть список переименований, которые нужно сделать в файле renames.txt (предполагая, что для их демонстрации не должно быть пробелов):

     da ef be ac 

    Поскольку d следует переименовать в a , это означает, что a необходимо переименовать до d . Таким образом, у нас есть частичный порядок сортировки, который будет обратным порядку переименования файлов.

    tsort – это инструмент для вывода полного порядка сортировки из списка частичных заказов сортировки. Он вернется с ошибкой, если будет цикл, который поможет нам обнаружить случаи, когда нет решения. Если мы применим tsort на этом входе, это дает нам:

     b d e a f c 

    Который говорит, что b следует переименовать после d после e . Мы можем использовать GNU tac (некоторые системы также имеют tail -r ), чтобы отменить этот порядок:

     c f a e d b 

    И присоединитесь к нему с нашим списком переименований:

     tsort renames.txt | tac | awk ' NR==FNR { ren[$1] = $2 next } $1 in ren { print "mv -i --", $1, ren[$1] }' renames.txt - 

    который дает нам:

     mv -i -- ac mv -i -- ef mv -i -- da mv -i -- be 

    которые мы можем затем передать в sh для выполнения.

    Обратите внимание, однако, что это не является надежным, поскольку мы не проверяем статус выхода tsort выше для обнаружения циклов, а имена файлов не должны содержать никаких специальных символов оболочки.

    Роубилизация остается как упражнение для читателя 😉

    Это работает для конкретного случая выше:

     rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' $(printf '%s\n' index-*.txt | sort --field-separator - --key 2n | tac) 

    Но:

    1. он не обрабатывает пробелы в именах файлов и
    2. для произвольных имен файлов было бы намного сложнее.

    Не прямой ответ на ваш вопрос, поскольку вы упоминаете bash, но с zsh вы можете указать порядок сортировки и численный сортировку с помощью классификаторов глобусов.

     $ echo index-*.txt index-12.txt index-1.txt index-2.txt index-4.txt $ echo index-*.txt(n) index-1.txt index-2.txt index-4.txt index-12.txt $ echo index-*.txt(nOn) index-12.txt index-4.txt index-2.txt index-1.txt 

    Или вы можете определить свой собственный порядок сортировки с помощью функции и использовать index-*(o+that-function) .

    Также обратите внимание, что zsh имеет собственную встроенную функцию rename zmv :

     $ zmv -fvQ 'index-(<->).txt(n)' 'index-$(($1-1)).txt' mv -- index-1.txt index-0.txt mv -- index-2.txt index-1.txt mv -- index-4.txt index-3.txt mv -- index-12.txt index-11.txt $ zmv -fvQ 'index-(<->).txt(nOn)' 'index-$(($1+1)).txt' mv -- index-11.txt index-12.txt mv -- index-3.txt index-4.txt mv -- index-1.txt index-2.txt mv -- index-0.txt index-1.txt 
    Linux и Unix - лучшая ОС в мире.