Как заменить пробелы в именах файлов, используя скрипт bash


264

Кто-нибудь может порекомендовать безопасное решение для рекурсивной замены пробелов символами подчеркивания в именах файлов и каталогов, начиная с заданного корневого каталога? Например:

$ tree
.
|-- a dir
|   `-- file with spaces.txt
`-- b dir
    |-- another file with spaces.txt
    `-- yet another file with spaces.pdf

будет выглядеть так:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf

9
Что вы хотите, чтобы происходило, если в той же директории есть файл с именем foo barи другой файл foo_bar?
Марк Байерс

Хороший вопрос. Я не хотел бы перезаписывать существующие файлы или потерять какие-либо данные. Это должно оставить это без изменений .. в идеале печатать предупреждение, но это, вероятно, требует слишком много.
Армандино

Ответы:


312

Используйте rename(иначе prename) скрипт Perl, который уже может быть в вашей системе. Сделайте это в два этапа:

find -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find -name "* *" -type f | rename 's/ /_/g'

Основано на ответе Юргена и способно обрабатывать несколько слоев файлов и каталогов в одной границе, используя версию Revision 1.5 1998/12/18 16:16:31 rmb1 /usr/bin/rename(скрипт Perl):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;

5
Нет необходимости в двух шагах: используйте поиск в глубину: найдите dir -depth
Jürgen Hötzel

3
О, я только что прочитал renameсправочную страницу (я не знал инструмент), и я думаю, что вы можете оптимизировать свой код, изменив s/ /_/gна y/ /_/;-)
Майкл Крелин - хакер

1
Конечно, вы не получите от этого повышения производительности. Это больше об использовании правильного инструмента. И весь этот вопрос о микро-оптимизации более или менее. Разве это не весело, в конце концов? ;-)
Майкл Крелин - хакер

15
Если вы используете это на OS X, вам нужноbrew install rename
loeschg

7
Это не работает в Centos 7, так как команда переименования совершенно другая (это бинарный файл, а не скрипт perl), и она не принимает данные из stdin.
CpnCrunch

357

Я использую:

for f in *\ *; do mv "$f" "${f// /_}"; done

Хотя это не рекурсивно, это довольно быстро и просто. Я уверен, что кто-то здесь мог обновить это, чтобы быть рекурсивным.

${f// /_}Часть использует механизм расширения параметров Bash, чтобы заменить образец в пределах параметра с переданной строкой. Соответствующий синтаксис есть ${parameter/pattern/string}. См .: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html или http://wiki.bash-hackers.org/syntax/pe .


9
Просто и работать в Mac. (Mac не имеет rename, и его слишком сложно установить с помощью brew ..)
JonnieJS

6
отличный ответ. я использовал for d in *\ *; do mv "$d" "${d// /}"; doneне под счетом.
Юн Ли

1
В отличие от ответа 'find -name', этот работал на моей OS X! Спасибо, сэр!
Хулио Фаерман

1
Для справки, это может легко стать рекурсивным в bash для использования shopt -s globstarи for f in **/*\ *; do .... Эта globstarопция является внутренней для bash, тогда как renameкоманда является распространенным инструментом Linux, а не частью bash.
Готи

2
${f// /_}является расширение переменной Bash для поиска и замены . - fэто переменная из forцикла для каждого файла, который содержит пробел. - Первое //означает «заменить все» (не останавливаться при первом появлении). - Тогда `/ _` означает" заменить пробел подчеркиванием "
Ари

101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

не удалось сделать это правильно, потому что я не думал о каталогах.


1
Работает как шарм. Спасибо, Майкл.
Армандино

Денис, хороший улов, легко исправляется, если поставить IFS=''перед собой read. Кроме того, что я могу сказать другими комментариями, sortшаг может быть отброшен в пользу -depthопции find.
Майкл Крелин - хакер

1
Не работает, если имя файла содержит \ (обратную косую черту). Можно исправить, добавив -rопцию для чтения.
jfg956

8
Это должно быть в 50 раз, когда я захожу на эту страницу, чтобы скопировать и использовать ваше решение. Спасибо очень много . Я предпочитаю ваш ответ, так как я на Mac и у меня нет renameкоманды, предложенной Деннисом.
Алекс Константин

@AlexConstantin, не macportsимеют rename? Я никогда не удосужился узнать, потому что я не думаю, что задача оправдывает полезность. А если у вас его нет macports, вам следует подумать об их установке;)
Майкл Крелин - хакер


12

Находка / переименование решение. переименование является частью util-linux.

Сначала нужно спуститься на глубину, потому что имя файла с пробелами может быть частью каталога с пробелами:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"

Я не получаю никаких изменений, когда я управляю твоим.
Приостановлено до дальнейшего уведомления.

Проверка установки util-linux: $ rename - переименование версии (util-linux-ng 2.17.2)
Юрген Хётцель

Grepping / usr / bin / rename (скрипт Perl) показывает «Revision 1.5 1998/12/18 16:16:31 rmb1»
приостановлено до дальнейшего уведомления.

Хм ... куда пропал ваш бинарный файл util-linux? Этот путь к файлу должен принадлежать util-linux. Вы не используете систему GNU-Linux?
Юрген Хётцель

1
который меняет только один пробел в моем беге, так что «иди скажи огонь на горе» становится «огонь на горе».
brokkr

6

Баш 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done

Похоже, что это сделает mv для себя, если имя файла или каталога не имеет места в нем (mv: не может переместить a' to a subdirectory of itself, / a ')
armandino

не имеет значения просто удалите сообщение об ошибке, перенаправив на /dev/null.
ghostdog74

ghostdog, порождающий mvпятьдесят пять тысяч раз только для переименования четырех файлов, может быть немного непосильным, даже если вы не заполняете пользователя сообщениями.
Майкл Крелин - хакер

krelin, даже команда find найдет те 55000 файлов, которые вы упомянули, чтобы найти их с пробелами, а затем переименовать. На заднем плане все еще проходит через все. Если хотите, начальная проверка пробелов перед переименованием сделает это.
ghostdog74

Я говорил о нересте мв, не проходя. Не будет for file in *' '*или некоторые из них сделать лучшую работу?
Майкл Крелин - хакер

6

Вы можете использовать это:

    find . -name '* *' | while read fname 

do
        new_fname=`echo $fname | tr " " "_"`

        if [ -e $new_fname ]
        then
                echo "File $new_fname already exists. Not replacing $fname"
        else
                echo "Creating new file $new_fname to replace $fname"
                mv "$fname" $new_fname
        fi
done

3

Рекурсивная версия ответов Naidim.

find . -name "* *" | awk '{ print length, $0 }' | sort -nr -s | cut -d" " -f2- | while read f; do base=$(basename "$f"); newbase="${base// /_}"; mv "$(dirname "$f")/$(basename "$f")" "$(dirname "$f")/$newbase"; done

2

Вот (довольно подробное) решение find -exec, которое записывает предупреждения «файл уже существует» в stderr:

function trspace() {
   declare dir name bname dname newname replace_char
   [ $# -lt 1 -o $# -gt 2 ] && { echo "usage: trspace dir char"; return 1; }
   dir="${1}"
   replace_char="${2:-_}"
   find "${dir}" -xdev -depth -name $'*[ \t\r\n\v\f]*' -exec bash -c '
      for ((i=1; i<=$#; i++)); do
         name="${@:i:1}"
         dname="${name%/*}"
         bname="${name##*/}"
         newname="${dname}/${bname//[[:space:]]/${0}}"
         if [[ -e "${newname}" ]]; then
            echo "Warning: file already exists: ${newname}" 1>&2
         else
            mv "${name}" "${newname}"
         fi
      done
  ' "${replace_char}" '{}' +
}

trspace rootdir _

2

Этот делает немного больше. Я использую его для переименования загруженных торрентов (без специальных символов (не ASCII), пробелов, нескольких точек и т. Д.).

#!/usr/bin/perl

&rena(`find . -type d`);
&rena(`find . -type f`);

sub rena
{
    ($elems)=@_;
    @t=split /\n/,$elems;

    for $e (@t)
    {
    $_=$e;
    # remove ./ of find
    s/^\.\///;
    # non ascii transliterate
    tr [\200-\377][_];
    tr [\000-\40][_];
    # special characters we do not want in paths
    s/[ \-\,\;\?\+\'\"\!\[\]\(\)\@\#]/_/g;
    # multiple dots except for extension
    while (/\..*\./)
    {
        s/\./_/;
    }
    # only one _ consecutive
    s/_+/_/g;
    next if ($_ eq $e ) or ("./$_" eq $e);
    print "$e -> $_\n";
    rename ($e,$_);
    }
}

1

Я нашел вокруг этого сценария, это может быть интересно :)

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS

Сбой файлов с символами новой строки в их имени.
ghoti


1

Для тех, кто борется с этим с помощью macOS, сначала установите все инструменты:

 brew install tree findutils rename

Затем, когда необходимо переименовать, создайте псевдоним для GNU find (gfind) as find. Затем запустите код @Michel Krelin:

alias find=gfind 
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done   

0

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

find ./ -name "* *" -type f -d 1 | perl -ple '$file = $_; $file =~ s/\s+/_/g; rename($_, $file);


0

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

#!/bin/bash
cd /vzwhome/c0cheh1/dev_source/UB_14_8
for file in *
do
    echo $file
    cd "/vzwhome/c0cheh1/dev_source/UB_14_8/$file/Configuration/$file"
    echo "==> `pwd`"
    for subfile in *\ *; do [ -d "$subfile" ] && ( mv "$subfile" "$(echo $subfile | sed -e 's/ /_/g')" ); done
    ls
    cd /vzwhome/c0cheh1/dev_source/UB_14_8
done

0

Для файлов в папке с именем / files

for i in `IFS="";find /files -name *\ *`
do
   echo $i
done > /tmp/list


while read line
do
   mv "$line" `echo $line | sed 's/ /_/g'`
done < /tmp/list

rm /tmp/list

0

Мое решение проблемы - bash-скрипт:

#!/bin/bash
directory=$1
cd "$directory"
while [ "$(find ./ -regex '.* .*' | wc -l)" -gt 0 ];
do filename="$(find ./ -regex '.* .*' | head -n 1)"
mv "$filename" "$(echo "$filename" | sed 's|'" "'|_|g')"
done

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


0

В macOS

Так же, как выбранный ответ.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.