Как рекурсивно переименовать файлы и папки с помощью iconv из Bash

Я безуспешно пытался рекурсивно переименовать файлы и папки с помощью iconv, файлы переименовываются правильно, а папки - нет.

Что я использую для файлов (идеально работает):

find . -name * -depth \ -exec bash -c 'mv "$1" "${1%/*}/$(iconv -f UTF8 -t ASCII//TRANSLIT <<< ${1##*/})"' -- {} \;

Что я пробовал для файлов и папок (сбой: только переименовывать папки):

find . -exec bash -c 'mv "$1" "$(iconv -f UTF8 -t ASCII//TRANSLIT <<< $1)"' -- {} \;

ИСХОДНАЯ проблема: я просто хочу массово переименовать множество файлов, чтобы сделать их «дружественными для Интернета», думаю, что нужно удалить пробелы, странные символы и т. д., в настоящее время у меня есть

find . -name '*' -depth \
| while read f ; 
do 
mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr -s ' ' _|tr -d "'"|tr -d ","|tr - _|tr "&" "y"|tr "@" "a")" ;
done 

Есть ли способ сделать описанные выше вещи tr и iconv за один раз? поскольку я говорю о переименовании около 300 000 файлов, я хотел бы по возможности избежать повторного поиска.

Если нужно, работаю с Bash 4.2.24.

Заранее спасибо.


person RandomGuy42    schedule 17.07.2012    source источник
comment
Если у вас есть Perl, вы, вероятно, можете использовать convmv (инструмент для преобразования кодировка в именах файлов).   -  person Michał Górny    schedule 18.07.2012
comment
Просто для ясности, вы поменяли местами команды файла и файла/папки? -type f исключит каталоги.   -  person chepner    schedule 18.07.2012
comment
@chepner ups, плохо, я пробовал разные настройки. Без -type f переименовываются только папки, но не файлы.   -  person RandomGuy42    schedule 18.07.2012


Ответы (2)


Я думаю, что следующее делает все, что вы хотите, за один проход.

# Update: if this doesn't work, use read -d '' instead
find . -print0 | while IFS= read -d '$\000' f ;
do 
  orig_f="$f"
  # Below is pure bash. You can replace with tr if you like
  # f="$( echo $f | tr -d ,\' | tr "$'&'@- " "ya__" )"
  f="${f// /_}"  # Replace spaces with _
  f="${f//\'}"   # Remove single quote
  f="${f//-/_}"  # Replace - with _
  f="${f//,}"    # Remove commas
  f="${f//&/y}"  # Replace ampersand with y
  f="${f//@/a}"  # Replace at sign with a
  f=$( iconv -f UTF8 -t ASCII//TRANSLIT <<< "$f" )
  new_dir="$(dirname $f)"
  new_f="$(basename $f)"
  mkdir -p "$new_dir"
  mv -i "$orig_f" "$new_dir/$new_f"
done 

Команда find (никаких реальных опций не требуется, кроме -print0 для обработки имен файлов с пробелами) отправит имена файлов, разделенные нулем, в цикл while (и кто-нибудь там исправит мои ошибки, без сомнения). Длинный список назначений, использующих раскрытие параметров, удаляет/заменяет различные символы; Я включаю то, что я считаю эквивалентным конвейером, используя tr в качестве комментария. Затем мы пропускаем имя файла через iconv, чтобы решить проблемы с набором символов. Наконец, мы разделяем имя на его компоненты пути и имени файла, так как нам, возможно, придется создать новый каталог перед выполнением mv.

person chepner    schedule 17.07.2012
comment
Я использую эту строку: mv -i $f $(dirname $f)/$(basename $f|tr -s ' ' _|tr -d '|tr -d ,|tr - _|tr & y|tr @ а) ; Чтобы успешно переименовать каталог и файлы без создания дополнительного, поскольку у меня около 10 000 каталогов, этот подход создаст (в худшем случае) еще 10 000, верно? - person RandomGuy42; 18.07.2012
comment
Это правда. Это действительно зависит от того, хотите ли вы обрабатывать имена каталогов с помощью tr и iconv. Если нет, вы можете отредактировать мой код, чтобы использовать исходный путь в команде mv, как вы делаете сейчас. В противном случае вам нужно быть готовым к созданию новых каталогов. - person chepner; 18.07.2012
comment
@chepner Кажется, я не могу заставить это работать на Ubuntu 10.04. Если я правильно понимаю, -print0разделяет имена файлов нулями, тогда read -d '$\000' заканчивается каждым нулем, но когда я запускаю его в дереве файлов, ничего не происходит. Если я заменю тело do ... done только на echo $f, я получу только пустые строки. Я начал с прямого копирования/вставки вашего кода, поэтому не думаю, что это опечатка. Это странно (GNU bash, версия 4.1.5) - person Thorsen; 21.07.2012
comment
Кажется, я исправил использование read -d в другом потоке. Если вы используете read -d '', похоже, это работает. - person chepner; 21.07.2012
comment
Не следует ли поменять местами вывод find на tac -s '', чтобы папки переименовывались после их содержимого? В противном случае я получаю ошибки с вложенными папками. - person iago-lito; 26.12.2016
comment
Я предложил исправление в этом альтернативном ответе. Если вы не возражаете, я бы предпочел опубликовать это как редактирование вашего сообщения, чтобы оно было более понятным. - person iago-lito; 29.12.2016

Вот обновление, которое я предлагаю после ответа Чепнера, чтобы избежать вложенных ошибок. Измените вывод find на tac, чтобы воздействовать на содержимое папок перед самими папками. Таким образом, больше не нужно mkdir:

echo "renaming:"
find . -print0 | tac -s '' | while IFS= read -d '' f ;
do
    Odir=$(dirname "$f")   # original location
    Ofile=$(basename "$f") # original filename
    newFile=$Ofile
    # remove unwanted characters
    newFile=$(echo $newFile | tr -d ",'\"?()[]{}\\!")
    newFile="${newFile// /_}"  # Replace spaces with _
    newFile="${newFile//&/n}"  # Replace ampersand with n
    newFile="${newFile//@/a}"  # Replace at sign with a
    newFile=$( iconv -f UTF8 -t ASCII//TRANSLIT <<< "$newFile" )
    if [[ "$Ofile" != "$newFile" ]]; then # act if something has changed
      echo "$Odir/$Ofile to"
      echo "$Odir/$newFile"
      mv -i "$Odir/$Ofile" "$Odir/$newFile"
      echo ""
    fi
done
echo "done."

Наслаждаться ;)

person iago-lito    schedule 29.12.2016