Як замінити пробіли в іменах файлів за допомогою скрипту 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?
Марк Байєрс

Гарне питання. Я не хотів би перезаписувати наявні файли або втрачати будь-які дані. Це повинно залишати його незмінним .. в ідеалі надруковуючи попередження, але це, мабуть, дуже багато.
армандіно

Відповіді:


311

Використовуйте 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
Звичайно, ви не збираєтеся отримувати підвищення продуктивності від цього. Це більше про використання правильного інструменту. І все це питання про мікрооптимізацію більш-менш. Зрештою, це не весело? ;-)
Майкл Крелін - хакер

14
Якщо ви запускаєте це на 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, і це занадто важко, щоб встановити це з варити.)
JonnieJS

6
приголомшлива відповідь. я використав for d in *\ *; do mv "$d" "${d// /}"; doneне забитий.
Йон Лі

1
На відміну від відповіді "find-name", цей працював на моїй ОС X! Дякую вам сер!
Хуліо Фаерман

1
Для довідки, це може легко стати рекурсивним в bash для використання shopt -s globstarта for f in **/*\ *; do .... globstarВаріант є внутрішнім Баш, в той час як renameкоманда є поширеним інструментом Linux , а не частина Баша.
ghoti

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)
Jürgen Hötzel,

Grepping / usr / bin / rename (сценарій Perl) виявляє "Revision 1.5 1998/12/18 16:16:31 rmb1"
Призупинено до подальшого повідомлення.

Гм ... куди пішов ваш бінарний файл util-linux? Цей шлях до файлу повинен належати util-linux. Ви не є системою GNU-Linux?
Юрген Хетцель

1
що міняє лише один простір у моєму пробігу, тож «іди розпалюй вогонь на горі» стає «вогнем go_tell на горі».
brokkr

6

bash 4.0

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

Схоже, це зробить mv собі, якщо ім’я файлу чи каталогу не має місця в ньому (mv: не можна перемістити a' to a subdirectory of itself, a / a ')
armandino

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

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

krelin, навіть пошук перейде через ті 55000 файлів, які ви згадали, щоб знайти ті з пробілами, а потім перейменувати. На задньому кінці все ще проходить все. Якщо ви хочете, це зробить початкова перевірка пробілів перед перейменуванням.
ghostdog74

Я говорив про нересту mv, не переживаючи. Чи не 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

Рекурсивна версія відповідей Найдіма.

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

Ось (досить багатослівне) рішення -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) як 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

Для файлів у папці з назвою / файли

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.