Удалить пробелы, дефисы и подчеркивания в именах файлов?

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

Я использую следующую команду с пользовательскими действиями Thunar для slugify filenames:

for file in %N; do mv "$file" "$(echo "$file" | tr -s ' ' | tr ' AZ' '-az' | tr -s '-' | tr -c '[:alnum:][:cntrl:].' '-')"; done 

Но эта команда заменяет только пробелы символами дефиса / дефисов и нижних регистров.

Я использовал следующую команду в терминале для удаления пробелов из тысяч имен файлов в папке, и она работала довольно быстро:

  rename "s/ //g" * 

Опять же, он удаляет только пробелы, а не дефисы / дефисы и символы подчеркивания.

В идеале я не хочу никаких пробелов, дефис / дефис и подчеркивания в своих файлах. И было бы замечательно, если бы команда могла использоваться с пользовательскими действиями Thunar для выбранных файлов.

Версия rename которая поставляется с пакетом perl , поддерживает регулярные выражения:

 rename "s/[-_ ]//g" * 

С другой стороны,

 rename -i "s/[-_ ]//g" * 

Флаг -i rename использование интерактивного режима, предлагая, если цель уже существует, вместо молчания.

Переименовать Perl иногда называют prename .

Переименование Perl и переименование util-linux

В Debian-подобных системах переименование perl похоже на значение по умолчанию, и вышеприведенные команды должны работать.

В некоторых дистрибутивах по умолчанию используется утилита rename из util-linux. Эта утилита полностью несовместима с rename Perl.

  • Все: во- первых, проверьте, доступно ли rename Perl под именем prename .

  • Debian: переименование Perl должно быть по умолчанию. Он также доступен как prename . Однако исполняемый файл rename находится под контролем /etc/alternatives и, следовательно, может быть изменен на что-то другое.

  • archlinux: Запустите pacman -S perl-rename и команда доступна как perl-rename . Для более удобного имени создайте псевдоним. (Кончик шляпы: ChiseledAbs)

  • Mac OSX В соответствии с этим ответом rename может быть установлено на OSX с использованием homebrew через:

     brew install rename 
  • Прямая загрузка: rename также доступно в Perl Monks:

      wget 'http://www.perlmonks.org/?displaytype=displaycode;node_id=303814' -O rename 

Я бы заменил все эти команды tr с помощью команды подстановки sed , например:

 for file in %N; do mv "$file" "$(echo "$file" | sed 's/[ _-]//g')" done 

Не считая mv , вам совсем не нужен внешний процесс для этого – вы можете просто их обмануть .

 ifsqz() ( LC_ALL=C sqz=$1 isf() { [ -e "$1" ] || [ -L "$1" ] ; } set -- * ; set -f for f do isf "$f" || break IFS=$sqz; set -- $f; IFS= isf "$*" || mv -- "$f" "$*" done ) 

Тем не менее, это означает mv вызов в файл, и, вероятно, лучше rename . Хотя это должно работать, учитывая только POSIX mv в $PATH и оболочке POSIX.

Итак, я придумал какую-то сумасшедшую демонстрацию для этого. Набор тестов генерируется следующим образом:

 tee - - - - <<CGEN |\ dd cbs=90 conv=unblock |\ sed 'G;$!N'";s/^/touch -- '/;s/$/'/" |sh $( #BEGIN CGEN LC_ALL=C i= n='"$((i=((i=i+1)==10||i==39||i==47)>0?(i+1):i))"' printf '%b -_ ---___' $( IFS=0; eval \ printf '"\\\\%04o\\\\%04o "' "$( printf "$n"' "$i" '%s $( printf %.252d #END ))")) CGEN 

Во-первых, я буду первым, кто признает, что приведенная выше команда дает результаты, которые легче получить другими способами. Но другие средства, скорее всего, не продемонстрируют, что можно сделать с помощью $IFS и немного (больного?) Воображения.

Итак, первый бит довольно прямолинейный:

  • tee выдает 5 экземпляров своего ввода – heredocument под названием CGEN

  • dd блокирует свой вход с помощью строк новой строки на 90 байтов на блок и на трубы, которые …

  • sed объединяет 2 из этих блоков на двух \n электронных символах, ' одинарные кавычки» и добавляет штриховую привязку touch -- для каждого линейного цикла перед тем, как выходить из системы …

  • sh который затем выполняет весь ввод в виде команд оболочки

#CGEN бит, хотя … Ну, ненадолго …

  • нижний printf печатает 252 0s

  • следующий из последних принимает 252 '' аргументы нулевой строки и для каждого печатает содержимое $n за которым следует строка " $i "

  • eval интерпретирует аргументы следующего printf перед тем, как он напечатает результаты этой интерпретации, как восьмеричные числа, добавленные двумя обратными слэшами части

  • последний printf печатает значения байтов для тех восьмеричных 2 за раз, за ​​которыми следует строка -_ ---___ для каждой пары

  • $n инициализируется уравнением, которое будет увеличивать $i на единицу для каждой оценки, за исключением того, что оно пропускает значения 10, 39 или 47 – (которые представляют собой \n ewline, ' одиночная кавычка и / slash в десятичном значении ASCII соответственно)

Конечным результатом является каталог, содержащий множество действительно уродливых имен файлов, содержащих каждый байт в моей кодировке от 1 до 255, за исключением одиночной кавычки (только пропущен, чтобы избежать еще одной инструкции sed s/// ) и / slash. Эти имена файлов выглядят следующим образом:

 (set -- *; printf '%s\n\n##############\n\n%s\n' "${9}" "${34}") | cat -A ---___ww -_ ---___xx -_ ---___yy -_ ---___zz -_ ---___{{ -_ ---___|| -_ ---$ $ ___}} -_ ---___~~ -_ ---___^?^? -_ ---___M-^@M-^@ -_ ---___M-^AM-^A -_ ---___M-^BM-^B -_ ---___M-^CM-^C$ $ ##############$ $ -_ ---___M-ZM-Z -_ ---___M-[M-[ -_ ---___M-\M-\ -_ ---___M-]M-] -_ ---___M-^M-^ -_ ---___M-_M-_ -_$ $ ---___M-`M-` -_ ---___M-aM-a -_ ---___M-bM-b -_ ---___M-cM-c -_ ---___M-dM-d -_ ---___M-eM-e -_ ---___$ 

Теперь я получу некоторые данные по этим файлам:

 chksqz() ( LC_ALL=C sqz=$1 set -- * ; set -f ; IFS= ; tc="$*" printf '#%s\n' \ "There are $# files in this test directory." \ "All filenames combined contain a total of ${#tc} bytes." IFS=$sqz ; set -- $* ; IFS= ; sc="$*" printf "%s '$sqz'" \ "#Of which ${#sc} bytes are not"\ " and $((${#tc}-${#sc})) bytes are" set +f ; unset IFS printf ".\n#%s\n#Total:\t%d\n#Other:\t%d\n#'$sqz':\t%d\n" \ "And to confirm these figures:" \ $( printf %s * | wc -c printf %s * | tr -d "$sqz" | wc -c printf %s * | tr -dc "$sqz" | wc -c )) chksqz '_ -' 

ВЫВОД

 #There are 101 files in this test directory. #All filenames combined contain a total of 17744 bytes. #Of which 2692 bytes are not '_ -' and 15052 bytes are '_ -'. #And to confirm these figures: #Total: 17744 #Other: 2692 #'_ -': 15052 

ОК. Теперь, наконец, к действию:

 ifsqz '_ -' chksqz '_ -' 

ВЫВОД

 #There are 101 files in this test directory. #All filenames combined contain a total of 2692 bytes. #Of which 2692 bytes are not '_ -' and 0 bytes are '_ -'. #And to confirm these figures: #Total: 2692 #Other: 2692 #'_ -': 0 

Успех! Вы можете убедиться сами:

 ls ???????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ??????????????????????????? ??????????????????????????? ??????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????? ?????????????????????? ???????????????????????? ?????????????????????????? ?????????????????????????? ?????????????????????????? ?????????????????????????? ??????????????????????????? ??????????????????????????? ??????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ???????????????????????????? ?????????????????????????? ???????????????????????? ???????????????????? ?????????????????? ???????????????????????????? ?? ???????????????????????????? ?????????????????????????? ???????????????????????????? ???????????????????????????? ????????????????????!!""## ??????????????????!!""##$$ ????????????????!!""##$$%% ????????????!!""##$$%%&&(( ????????!!""??##$$%%&&(()) $$%%&&(())**??++,,..0011 %%&&(())**++??,,..00112233 &&(())**++,,??..0011223344 ))**++,,..??0011223344556 **++,,..00??11223344556677 22334455667788??99::;;<<==>> 445566778899??::;;<<==>>??@@ 5566778899::;;??<<==>>??@@AA 6778899::;;<<??==>>??@@AABB 8899::;;<<==??>>??@@AABBCCDD \\]]^^``aa??bbccddeeffgghh ]]^^``aabbc??cddeeffgghhii ^^``aabbccdd??eeffgghhiijj ??@@AABBCCDDEE??FFGGHHIIJJKK AABBCCDDEEFF??GGHHIIJJKKLLM BBCCDDEEFFGG??HHIIJJKKLLMMNN CCDDEEFFGGHHII??JJKKLLMMNNOO EEFFGGHHIIJJ??KKLLMMNNOOPPQQ ffgghhiijjkk??llmmnnooppqqrr gghhiijjkkllmm??nnooppqqrrss iijjkkllmmnn??ooppqqrrsstt jjkkllmmnnoo??ppqqrrssttuuvv kkllmmnnooppqq??rrssttuuvvww LLMMNNOOPPQQRR??SSTTUUVVWWXX MNNOOPPQQRRSS??TTUUVVWWXXYY OOPPQQRRSSTT??UUVVWWXXYYZZ[[ PPQQRRSSTTUUVV??WWXXYYZZ[[\\ RRSSTTUUVVWW??XXYYZZ[[\\]] ssttuuvvwwxx??yyzz{{||}}~~?? ttuuvvwwxxyyz??z{{||}}~~???? uuvvwwxxyyzz{{??||}}~~?????? wwxxyyzz{{||??}}~~?????????? xxyyzz{{||}}~~?????????????? YYZZ[[\\]]^^??``aabbccddee ZZ[[\\]]^^``??aabbccddeeff 

если у вас есть perl, вы обычно переименовываете. ты можешь сделать:

 > type rename rename is /usr/bin/rename 

и показать, как написан этот сценарий:

 > cat /usr/bin/rename | head -n 5 #firt 5 lines for example #!/usr/bin/perl -w # # This script was developed by Robin Barker (Robin.Barker@npl.co.uk), # from Larry Wall's original script eg/rename from the perl source. # 

Этот скрипт не поддерживает флаг -i (это версия в моей системе), но, возможно, ваша поддержка. Как насчет аргументов. Сначала это регулярные выражения с форматом PCRE, он работает как фильтр, модифицирует имя ввода для имени вывода. Список имен ввода, которые вы даете звездочкой '*'. например, вы делаете:

 > cd /tmp > rename 's/ //g' * 

в реальном '*' можно расширить до:

 > rename 's/ //g' file1 file2 file3 othe files found in current directory 

Когда у вас действительно большие файлы отсчетов, вы попадаете в ловушку. shell расширит вашу линию дольше, чем система примет. то вы можете сделать обходной путь, используя find или xargs. использование 'find' является проблемой, потому что rename будет вызываться многократно равным количеству файлов в каталоге. лучше использовать xargs с опцией -r. один вызов переименования изменяет многие файлы. например:

 > ls | xargs -r rename 's/ //g' #thats all, names will be appended at the end of this command. 

последняя проблема, что это значит:

 's/ //g' 

это регулярное выражение для имен модификаций. после первого '/' есть пробел. это обнаружено и заменяется строкой после второй '/'. Но есть пустая строка, заканчивающаяся третьим «/», тогда пространство заменяется ничем. Опция 'g' делает это выражение повторяющимся. выражение будет ходить для всего имени от начала до конца и обнаруживает все пробелы.

Но что, если у вас есть символ табуляции или другой «белый» характер? есть замена для этого '\ s'. какие другие ненужные персонажи? просто добавьте его в выражение. Все закрывается скобками, например:

 's/[\s_-]//g' 

это все. вы видите сходство? Я думаю, вы должны прочитать man perlrequick и man perlretut, это объясняет вам (я надеюсь), как работает регулярное выражение. вы можете использовать команду rename в своем собственном скрипте, если вам это нужно.

Следующий цикл sh shell удалит все пробелы, символы подчеркивания и тире из имен файлов в текущем каталоге, стараясь не перезаписывать существующие файлы:

 for f in *; do test -f "$f" || continue nf=$( echo "$f" | tr -d ' _-' ) ! test -e "$nf" && echo mv "$f" "$nf" done 

Для bash и ksh и немного более подробный с логикой:

 for f in *; do if [[ -f "$f" ]]; then nf=$( tr -d ' _-' <<<"$f" ) if [[ ! -e "$nf" ]]; then echo mv "$f" "$nf" fi fi done 

Удалите echo когда вы уверены, что он делает то, что вы хотите.

Команда tr удаляет ( -d ) любой символ в заданном наборе символов ( ' _-' ). Важно иметь тире в самом начале или конце набора, или он будет интерпретироваться как диапазон символов.