Часто файлы после неправильного монтирования или после распаковки с неправильной кодировкой получаются с битыми именами, к примеру вот как выглядит альбом "Зачем снятся сны" Гражданской Обороны, если его записать на флешку в системе с UTF-8, а прочитать в системе с KOI8-R:
Если посмотреть этот вывод командой hexdump, становится видно, что имена побились безвозвратно - знаки вопроса (?) действительно имеют код «\x3F», и потому то, что было на их месте не подлежит восстановлению.
Попробуем переименовать файлы из XXX.mp3, в NN.mp3, где XXX - любые символы, а NN - это номер файла. Для начала, создадим список из имён файлов с номером строки:
Далее используем следующий скрипт:
$ /bin/ls | sed = |\ sed -rn 'N;s/[a-z0-9_.]*$/\n&/i;s/^([0-9]+)\n([^\n]+)\n(.*)/mv -v "\2\3" "\1\3"/ep'
Здесь обработка имени происходит в несколько этапов:
Во первых мы объединяем имя файла и его номер командой N.
Затем мы отделяем расширение файла. Расширение файла нам пригодится для того что-бы эти файлы не путать, кроме того, обычно расширением является последовательность вполне допустимых символов, которые можно и нужно сохранить. Я считаю допустимыми символы [a-z0-9_.].
s/[a-z0-9_.]*$/\n&/i
Обратите внимание на модификатор i
, он нужен для того, что-бы малые и большие буквы не различались. Часто многие девайсы называют файлы большими буквами (руки бы оторвать кодерам этих девайсов...).
После отделения расширения происходит собственно переименование командой mv:
s/^([0-9]+)\n([^\n]+)\n(.*)/mv -v "\2\3" "\1\3"/ep
Обратите внимание на модификатор +
во втором выражении, он нужен для того, что-бы mv не пыталась переименовывать файлы с допустимыми именами. Команда mv выполняется с ключом -v
и модификатором ep
, для того, что-бы мы видели какие файлы переименовываются.
-printf
.
Если имена файлов совсем не читаемые, мы их и читать не будем! Смотрите:
$ find . -type f -printf "%h/%i\n" ./1753457 ./1753458 ./1753463 ./1753461 ./1753464 ./1753468 ./1753453 ./1753470 ./1753465 ./1753462 ./1753467 ./1753450 ./1753456 ./1753452 ./1753469 ./1753455 ./1753454 ./1753451 ./1753459 ./1753460 ./1753466
Что это за хрень? Всё просто: это каталог где лежит файл, а вместо его имени записан его inode. Путь мне тоже нужен, т.к. find просматривает не только каталог, но и его подкаталоги.
-maxdepth 1
А теперь делаем так-же как в прошлом скрипте, однако файлы ищем не по имени, а по их inode:
Всё-бы ничего, только мы теперь не знаем, что это за файлы, и чем их открывать :( Для исправления этого существует команда file:
$ /bin/ls | sed -rn 's/^[0-9]+$/file &/ep' 1753450: MP3 file with ID3 version 2.3.0 tag 1753451: MP3 file with ID3 version 2.3.0 tag 1753452: MP3 file with ID3 version 2.3.0 tag 1753453: MP3 file with ID3 version 2.3.0 tag 1753454: MP3 file with ID3 version 2.3.0 tag 1753455: MP3 file with ID3 version 2.3.0 tag 1753456: MP3 file with ID3 version 2.3.0 tag 1753457: MP3 file with ID3 version 2.3.0 tag 1753458: MP3 file with ID3 version 2.3.0 tag 1753459: MP3 file with ID3 version 2.3.0 tag 1753460: ASCII text, with CRLF line terminators 1753461: MP3 file with ID3 version 2.3.0 tag 1753462: MP3 file with ID3 version 2.3.0 tag 1753463: MP3 file with ID3 version 2.3.0 tag 1753464: MP3 file with ID3 version 2.3.0 tag 1753465: MP3 file with ID3 version 2.3.0 tag 1753466: JPEG image data, JFIF standard 1.02 1753467: MP3 file with ID3 version 2.3.0 tag 1753468: MP3 file with ID3 version 2.3.0 tag 1753469: MP3 file with ID3 version 2.3.0 tag 1753470: MP3 file with ID3 version 2.3.0 tag
А теперь ещё раз переименуем:
Пример 4.12. Добавление расширения к имени файла.
#!/bin/sed -rnf /^[0-9]+$/ { s//file &/e s/^([0-9]+): (.*)/\2\n\1/ /^MP3 file/ s/$/.mp3/ /^ASCII text/ s/$/.txt/ /^JPEG image data/ s/$/.jpg/ /\n.*\./ s/^[^\n]+\n([0-9]+)(.*)/mv -v \1 \1\2/ep }
запускается этот скрипт так:
$ /bin/ls | ./add_ext.sed
Конечно ему надо дать права на исполнение:
$ chmod +x add_ext.sed
Внутри скрипта я анализирую вывод file, и в зависимости от этого прибавляю расширение. Если расширение добавлено, то я переименовываю.
/^[0-9]+$/
Вот ещё один пример: требуется сравнить 2 каталога. Сделать это просто, достаточно найти все файлы (не каталоги), в первом каталоге, а затем сравнить их с одноимёнными файлами в другом каталоге
Скрипт очень простой, но он иллюстрирует некоторые тонкие моменты интеграции sed и bash'а.
Пример 4.13. Сравнение каталогов.
#!/bin/sh find $1 -type f | sed -rn " s%^$1/(.*)%cmp '&' '$2/\1';echo \$?%pe /^0$/b p q"
Тут всё просто: благодаря модификатору pe команда сначала выводится, а потом выполняется, что-бы было видно как идёт процесс (можно ещё одну sed добавить, что-бы сделать красиво), а вот результат (это число, 0 если файлы одинаковые) никуда не выводится, а проверяется. Если файлы одинаковые, мы переходим к проверки новой пары, а вот если разные - то мы выводим код возврата и прерываем скрипт. При этом cmp выводит диагностическое сообщение, мы его видим на терминале, т.к. оно идёт в поток ошибок.
s/'/'\"'\"'/gв самом начале sed-скрипта.
Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.