Замените точки символами подчеркивания в именах файлов, оставив расширение без изменений

8

У меня есть скрипт bash, который я пытаюсь получить, чтобы заменить точки в именах файлов и заменить их подчеркиванием, оставив расширение без изменений (я на Centos 6, кстати). Как видно из приведенного ниже вывода, сценарий работает, когда необходимо заменить точку, но в тех случаях, когда единственной точкой является расширение, сценарий все равно пытается переименовать файл, а не игнорировать его. Кто-нибудь может указать, как я должен справиться с этим лучше? Спасибо за любую помощь.

Мой (неисправный) скрипт:

#!/bin/bash

for THISFILE in *
do
  filename=${THISFILE%\.*}
  extension=${THISFILE##*\.}
  newname=${filename//./_}
  echo "mv $THISFILE ${newname}.${extension}"
  #mv $THISFILE ${newname}.${extension}
done

Пример ввода:

1.3MN-Pin-Eurotunnel-Stw505.51.024-EGS-130x130.jpg
Wear-Plates.jpg

Вывод:

mv 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg
mv Wear-Plates_jpg.Wear-Plates_jpg Wear-Plates_jpg.Wear-Plates_jpg
bsod99
источник
1
Как насчет сложных случаев, таких как tar.gzфайлы? Вы бы хотели, чтобы они решили file.tar.gz, а не file_tar.gz.
IQAndreas

Ответы:

10

Я считаю, что эта программа будет делать то, что вы хотите. Я протестировал его, и он работает в нескольких интересных случаях (например, без расширения вообще):

#!/bin/bash

for fname in *; do
  name="${fname%\.*}"
  extension="${fname#$name}"
  newname="${name//./_}"
  newfname="$newname""$extension"
  if [ "$fname" != "$newfname" ]; then
    echo mv "$fname" "$newfname"
    #mv "$fname" "$newfname"
  fi
done

Главная проблема, с которой вы столкнулись, заключалась в том, что ##расширение не делало то, что вы хотели. Я всегда рассматривал расширение параметров оболочки в bash как нечто черное. Объяснения в руководстве не совсем ясны, и в них отсутствуют какие-либо подтверждающие примеры того, как расширение должно работать. Они также довольно загадочны.

Лично я написал бы небольшой сценарий, в sedкотором игралась бы имя так, как я хотел, или написал бы небольшой сценарий, perlкоторый просто делал все это. Один из тех, кто ответил, принял этот подход.

Еще одна вещь, на которую я хотел бы обратить внимание - это использование цитат. Каждый раз, когда я что-то делаю с помощью сценариев оболочки, я напоминаю людям, что нужно быть очень осторожными с их цитированием. Огромный источник проблем в сценариях оболочки - оболочка, интерпретирующая вещи, которые она не должна делать. И правила цитирования далеко не очевидны. Я полагаю, что этот скрипт не содержит проблем с цитированием.

всевозможный
источник
Это действительно хорошо делает свою работу :) Спасибо за объяснение относительно расширения оболочки.
bsod99
В дополнение к замене точки (.) На (_), как я могу удалить пробелы в именах файлов.
ученик
Это очень хороший сценарий. Единственные улучшения, которые я вижу, это сделать так, чтобы оно рекурсивно изменяло дерево каталогов и позволяло вам заменить 1 доллар на 2 доллара, но они незначительны. (Или, как говаривал мой учитель, «
оставь
4

Используйте for thisfile in *.*.*(то есть, переберите файлы с двумя или более точками в имени). Не забудьте процитировать ваши переменные и использовать --для обозначения конца опций, как вmv -- "$thisfile" "$newname.$extension"

С зш.

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'
Стефан Шазелас
источник
Возможно, я неправильно реализовал ваше предложение, но в результате mv - . . * _ . *
bsod99
3

Как насчет этого:

perl -e '
         @files = grep {-f} glob "*";
         @old_files = @files;
         map {
              s!(.*)\.!$1/!;
              s!\.!_!g;
              s!/!.!
             } @files;
         rename $old_files[$_] => $files[$_] for (0..$#files)
        '

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: попробуйте сначала фиктивный каталог, я не проверял!

Джозеф Р.
источник
Кратко, содержательно, почти только для записи! Yay Perl! смешок
Всемогущий
Запись только? Могу поспорить, это первый раз, когда Perl назвали так ! ( снова посмеивается )
Джозеф Р.
Сделал попытку улучшить читабельность там. Надеюсь, что это менее "только для записи" :)
Джозеф Р.
1
Да, это помогает куче. Жаль, что единственный разумный символ, который можно использовать в качестве заполнителя, есть /.
Всезнающий
2

Похоже, некоторые хорошие ответы уже доступны, но вот еще одно использование trи sed:

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done
JC Yamokoski
источник
Как sedрешить, к чему .*применить максимальный вкус? Я чувствую себя намного лучше об этом , если второй .*был [^_]*.
Всезнающий
Я также считаю, что это относится к случаю «без расширения» и имени файла с \tсимволами в нем.
Всезнающий
Это было определенно просто быстрое решение. Спасибо за ваш вклад ... Я исправил регулярное выражение, чтобы учесть вашу первую рекомендацию. Я посмотрю, смогу ли я настроить его, чтобы учесть другие обстоятельства, которые вы упомянули.
JC Yamokoski
echo -n "$file"должно сработать. :-) Да, и "вокруг $( ... )выражения (ака "$( ... )") будет делать это.
Всезнающий
1

Эта версия позволяет вам явно выбрать количество точек, которые вы хотите сохранить, начиная с правой стороны.

Кроме того, он заменяет и / или удаляет другие символы в дополнение к точкам, а символ замены -вместо подчеркивания, но это можно легко изменить.

#!/bin/sh
# Rename files by replacing Unix-unfriendly characters.

usage () {
    cat <<EOF
usage: $0 [OPTIONS] [--] [FILE [FILE...]]
Rename files by replacing Unix-unfriendly characters.

Options:
 -p N              preserve last N dots in filename, or keep all
                   dots if N < 0 (default: 1)
       --help      show this help and exit
EOF
}

error () {
    printf "%s\n" "$1" 1>&2
}

delete_chars="()[]{}*?!^~%\\\<>&\$#|'\`\""
replace_chars=" _.,;-"

unixify_string () (
    printf '%s\n' "$1" \
        | tr -d "$delete_chars" \
        | tr -s "$replace_chars" - \
        | to_lower \
        | sed 's/^-\(.\)/\1/; s/\(.\)-$/\1/'
)

to_lower () {
    sed 's/.*/\L&/'
}

split () (
    # split '.x.x.x.x'  0 -> '/x.x.x.x.x
    # split '.x.x.x.x'  1 -> '/x.x.x.x/x
    # split '.x.x.x.x'  2 -> '/x.x.x/x/x
    # split '.x.x.x.x' -1 -> '/x/x/x/x/x
    nf=$(printf '%s\n' "$1" | tr -d -C . | wc -c)
    if [ $2 -lt 0 ]; then
        keep=0
    else
        keep=$((nf-$2))
    fi
    IFS=. i=0 out= sep=
    for part in $1; do
        out="$out$sep$part"
        if [ -z "$out" -o $i -ge $keep ]; then
            sep=/
        else
            sep=.
        fi
        i=$(($i+1))
    done
    printf '%s\n' "$out"
)

unixify () (
    IFS=/ out= sep=
    for part in $(split "$1" $2); do
        out="$out$sep$(unixify_string "$part")"
        sep=.
    done
    printf '%s\n' "$out"
)

rename_maybe () (
    dir="$(dirname "$1")"
    name="$(basename "$1")"
    newname="$(unixify "$name" $2)"
    if [ "$newname" != "$name" ]; then
        mv -i "$dir/$name" "$dir/$newname"
    fi
)

# command line arguments

short_opts=p:
long_opts=help

args="$(LC_ALL=C getopt -n "$0" -s sh -o $short_opts -l $long_opts -- "$@")"
if [ $? -eq 0 ]; then
    eval set -- "$args"
else
    exit 1
fi

p=
while [ $# -gt 0 ]; do
    case "$1" in
        --help)
            usage; exit 0 ;;
        -p)
            p="$2"; shift
            if ! [ "$p" -eq "$p" ] 2> /dev/null; then
                error "$0: option requires integer argument -- 'p'"
                exit 1
            fi ;;
        --)
            shift; break ;;
        -*)
            error "$0: illegal option -- '$1'"
            exit 1 ;;
        *)
            break
    esac
    shift
done

# defaults
p=${p:-1}

# echo p=$p
# echo "$@"
# echo n=$#
# exit

if [ $# -lt 1 ]; then
    error "$0: required non-option argument missing"
    exit 1
fi

for file in "$@"; do
    rename_maybe "$file" $p
done
Эрнест А
источник