Разделить имена файлов и переименовать

Какую команду rename можно использовать, которая удалит настоящие имена файлов в каталоге и заменит их на буквенно-цифровые имена файлов?

Например, gg0001 , gg0002 и т. Д.? И я бы хотел, чтобы команда работала над файлами с любым типом расширения. И я хотел бы, чтобы расширение было сохранено.

Я бы предпочел использовать rename Perl.

Я пробовал эту команду, но она просто добавляет «gg» к именам файлов, тогда как я хочу, чтобы исходные имена файлов были заменены:

rename 's/^/gg_/' *

Вам действительно не нужна команда rename для этого, вы можете сделать это прямо в оболочке:

 c=0; for i in *; do let c++; mv "$i" "gg_$(printf "%03d" $c)${i#"${i%.*}"}"; done 

printf "%03d" $c будет печатать переменную $c , заполняя ее тремя ведущими 0s по мере необходимости. let c++ увеличивает счетчик ( $c ). ${i#"${i%.*}"} Извлекает расширение. Подробнее об этом здесь .

Я бы не использовал для этого rename . Я не думаю, что он может делать расчеты. Действительные конструкторы Perl, такие как s/.*/$c++/e терпят неудачу, и я не вижу другого способа, чтобы он выполнял вычисления. Это означает, что вам все равно нужно запустить его в цикле и добавить что-то еще для счетчика. Что-то вроде:

 c=0;for i in *; do let c++; rename -n 's/.*\.(.*)/'$c'.$1/' "$i"; done 

Но нет преимущества перед использованием более простого подхода mv .

При условии отсутствия ' одиночных кавычек» в именах файлов вы можете сделать это следующим образом:

 i=0; set -- for f in * do set -- "$@" "$f" gg "$((i+=1))" "${f#"${f%.*}"}" done printf "mv '%s' '%s%03d%s'\n" "$@" | sh 

Таким образом, если имя файла содержит . то он и все, что следует за ним, сохраняются, иначе поле пусто, а printf ничего не печатает.

Я полагаю, что даже если в именах файлов есть одиночные кавычки, это можно сделать …

 i=0; set -- for f in * do set -- "$@" "$((i+=1))" gg "$i" "$i#\"\${$i%.*}\"" done printf 'mv "${%d}" "%s%03d${%s}"\n' "$@" | sh -s -- * 

… что, вероятно, намного безопаснее всего.

Но вы знаете, подумав об этом, я пришел, чтобы заподозрить, что они слишком много работают. Единственная причина, по которой я связываю любой список, – это избегать выполнения проверок нулевого заполнения. Я предпочитаю создавать список в одной подоболочке и передавать его с помощью одной write() в одну другую подоболочку, когда это необходимо.

Но это говорит, что нулевое заполнение не может быть слишком сложным. Я немного поработал с ним, и я придумал это небольшое заявление о математике:

 $((z/=(m*=((i+=1)<$m?1:10))/$m)) 

Если он помещен в цикл итерации, он будет увеличивать $i один раз за элемент, продвигать множитель $m на экспоненциальный откат для каждого коэффициента из десяти и десятину $z для каждого из них. Идея состоит в том, чтобы получить значение в $z перед запуском цикла, который содержит максимально возможное приращение $i . Это просто сделано:

 set -- *; max=$# z=1 until [ "${#z}" -gt "${#max}" ]; do : "$((z*=10))"; done 

Это полностью автоматизированный способ – он автоматически накладывается на любое максимальное число, которое он может увеличивать. Но, честно говоря, $z просто должен быть числом, начинающимся с 1, и следовать только нулями, поэтому:

 set -- *; z=1000 

… будет помещаться в поле тысяч. Во всяком случае, вот небольшая демонстрация, которую я использовал при тестировании:

 time dash <<\CMD str=100000 z=1 m=1 i=0 until [ "${#z}" -gt "${#str}" ]; do : "$((z*=10))"; done while : "$((z/=(m*=((i+=1)<$m?1:10))/$m))" && case "$i" in (*[2-8]*|*9*[10]*|*1*[19]*) continue;; ([019]*) set -- "$@" "z: $zm: $mi: $i" "${z#?}$i";;esac do [ "$i" = "$str" ] && printf %s\\n "$@" && break; done CMD 

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

ВЫВОД

 z: 100000 m: 10 i: 1 000001 z: 100000 m: 10 i: 9 000009 z: 10000 m: 100 i: 10 000010 z: 10000 m: 100 i: 99 000099 z: 1000 m: 1000 i: 100 000100 z: 1000 m: 1000 i: 999 000999 z: 100 m: 10000 i: 1000 001000 z: 100 m: 10000 i: 9999 009999 z: 10 m: 100000 i: 10000 010000 z: 10 m: 100000 i: 99999 099999 z: 1 m: 1000000 i: 100000 100000 dash <<<'' 0.32s user 0.00s system 99% cpu 0.320 total 

$m должен быть инициализирован на первом этапе, на котором вы хотите, чтобы начальные нули начинались срываться – я просто сделал m=1 выше. $z , как уже упоминалось, сохраняет верхний порядок величины. И $i подсчитывает петли, поэтому i=0 , вероятно, безопасная ставка. В любом случае, остальное просто происходит. Идея состоит в том, чтобы просто лишить ведущий 1 от $z и спрятать его в любом месте.

 set -- $(seq 1000); m=1 i=0 z=10000 while [ "$#" -gt 0 ] && : "$((z/=(m*=((i+=1)<$m?1:10))/$m))" do printf 'mv "%s" "lead_string_%s"\n' "$1" "${z#1}$i${1#"${1%.*}"}" shift; done 

Я использую printf только для того, чтобы показать, на что это похоже, но, конечно, все дело в том, чтобы избежать печати данных и вместо этого передать его в текущей оболочке в качестве подготовленного аргумента с разделителями. Во всяком случае, это выглядит так:

 mv "1" "lead_string_0001" mv "2" "lead_string_0002" mv "3" "lead_string_0003" ... mv "9" "lead_string_0009" mv "10" "lead_string_0010" mv "11" "lead_string_0011" ... mv "15" "lead_string_0015" ... mv "96" "lead_string_0096" ... mv "99" "lead_string_0099" mv "100" "lead_string_0100" mv "101" "lead_string_0101" mv "102" "lead_string_0102" ... mv "998" "lead_string_0998" mv "999" "lead_string_0999" mv "1000" "lead_string_1000" 

И не имеет значения, является ли это циклом for или while, который вы используете. Итак, с файлами, которые вы могли бы просто сделать:

 m=1 i=0 z=10000 for f in *; do : "$((z/=(m*=((i+=1)<$m?1:10))/$m))" mv -- "$f" "lead_string_${z#1}$i${f#"${f%.*}"}" done 

Этот работал для меня. (Но команда rename используемая здесь, является perl rename ).

 ls -1 -c | xargs rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e' 

Однако вышеприведенное предполагает, что в именах нет файлов с пробелами. В противном случае он ломается. Кроме того, синтаксический вывод ls действительно не очень хорошая идея.

Однако, поскольку он ломается, когда есть пробелы в именах файлов, попробуем исправить вышеуказанные проблемы.

 find -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e' 

Вышеупомянутый синтаксис работает, но у него все еще есть проблемы. Проблема в том, когда у вас есть вспомогательные каталоги, он также переименует вспомогательные каталоги, что и есть то, что мы хотим.

Чтобы исправить эту проблему, мы могли бы перефразировать нашу команду, как предлагает @terdon в своих комментариях. Последняя модифицированная команда выглядит так:

 find -maxdepth 1 -type f -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e' 

тестирование

У меня есть следующие файлы внутри моей папки. Не переименовывать меня – это подкаталог, который мы не хотим переименовывать.

 ls Don't rename me file1 file with spaces hello 

Теперь я выполняю команду, и я получаю результат ниже.

 find -maxdepth 1 -type f -print0 | xargs -0 rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e' ./file1 renamed as gg0000 ./hello renamed as gg0001 ./file with spaces renamed as gg0002 

Флаг -n может быть удален, если мы удовлетворены тем, что мы видим как окончательный вывод.

Рекомендации

https://stackoverflow.com/a/23207377/1742825

Просто увеличивайте переменную на каждой итерации. Поскольку rename наборы use strict , вам нужно будет использовать глобальную переменную $::i (или не использовать no strict или объявлять переменную с our ).

 rename 'our $i; s/.*/gg_$i/; ++$i' * 

Если вы хотите поместить номер с ведущими нулями, подсчитайте количество файлов. Список файлов находится в @ARGV .

 rename 'our $i; my $width = length(scalar(@ARGV)); s/.*/sprintf("gg_%0${width}d", $i++)/e' * 

Вы можете проверить, определена ли ваша переменная счетчика для выполнения кода только на первой итерации.

 rename 'our ($i, $width); if (!defined $width) {$width = length(scalar(@ARGV)); $i = 0;} s/.*/sprintf("gg_%0${width}d", $i++)/e' * 

Во всех вышеперечисленных случаях, если вы передаете имена файлов с частью каталога (т. s/.*/…/ Если вы передаете имена файлов, содержащие / – не происходит здесь с * ), измените s/.*/…/ на s/[^/]*\z/…/ .