Substitua pontos por sublinhados nos nomes de arquivos, deixando a extensão intacta

8

Eu tenho um script bash que estou tentando substituir pontos nos nomes de arquivos e substituí-los por sublinhados, deixando a extensão intacta (estou no Centos 6 btw). Como você pode ver na saída abaixo, o script funciona quando há um ponto a substituir, mas nos casos em que o único ponto é a extensão, o script ainda tenta renomear o arquivo, em vez de ignorá-lo. Alguém pode apontar como devo lidar melhor com isso? Obrigado por qualquer ajuda.

Meu script (defeituoso):

#!/bin/bash

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

Entrada de amostra:

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

Resultado:

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
fonte
1
E os casos complicados, como tar.gzarquivos? Você gostaria que eles resolvessem file.tar.gz, não file_tar.gz.
precisa saber é o seguinte

Respostas:

10

Acredito que este programa fará o que você deseja. Eu testei e funciona em vários casos interessantes (como nenhuma extensão):

#!/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

O principal problema que você teve foi que a ##expansão não estava fazendo o que você queria. Eu sempre considerei a expansão de parâmetros do shell no bash algo como uma arte negra. As explicações no manual não são completamente claras e eles não têm exemplos de suporte de como a expansão deve funcionar. Eles também são bastante enigmáticos.

Pessoalmente, eu teria escrito um pequeno script sedque brincava com o nome do jeito que eu queria, ou um pequeno script perlque fazia a coisa toda. Uma das outras pessoas que responderam adotou essa abordagem.

Outra coisa que gostaria de destacar é o uso de citações. Toda vez que faço algo com scripts de shell, lembro às pessoas que tenham muito cuidado com suas citações. Uma enorme fonte de problemas nos scripts de shell é a interpretação de shell que não deveria. E as regras de cotação estão longe de serem óbvias. Acredito que esse script shell esteja livre de problemas de citação.

Omniforme
fonte
Isso realmente faz o trabalho bem :) Obrigado pela explicação, também sobre a expansão do shell.
bsod99
Além de substituir o ponto (.) Por (_), como posso excluir espaços nos nomes de arquivos.
Discipulus
Este é um script muito bom. As únicas melhorias que posso ver são fazê-lo recursar uma árvore de diretórios e permitir que você substitua $ 1 por $ 2, mas esses são menores. (Ou, como meu professor costumava dizer, "deixado como um exercício para o aluno"!)
lbutlr
4

Use for thisfile in *.*.*(ou seja, faça um loop sobre arquivos com dois pontos ou mais em seu nome). Lembre-se de citar suas variáveis ​​e use --para marcar o final das opções como emmv -- "$thisfile" "$newname.$extension"

Com zsh.

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'
Stéphane Chazelas
fonte
Posso ter implementado sua sugestão incorretamente, mas isso resulta em mv - . . * _ . *
bsod99
3

Que tal agora:

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

AVISO LEGAL: tente primeiro em um diretório fictício, eu não testei!

Joseph R.
fonte
Conciso, conciso, quase somente para gravação! Yay perl! risada
omniforme
Somente gravação? Aposto que é a primeira vez que Perl é chamado assim! ( risada novamente )
Joseph R.
Tomou uma chance de melhorar a legibilidade lá. Espero que este se sente menos "write-only" :)
Joseph R.
1
Sim, isso ajuda muito. É uma pena o único personagem razoável a ser usado como espaço reservado /.
Onipresente
2

Parece que algumas boas respostas já estão disponíveis, mas aqui está outra usando tre sed:

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done
JC Yamokoski
fonte
Como seddecide a qual .*aplicar o maximo máximo? Eu me sentiria muito melhor se o segundo .*fosse [^_]*.
omniforme
Eu também acredito que isso se aplica ao caso de 'no extension' e um nome de arquivo com \tcaracteres.
Onipresente
Definitivamente, essa foi apenas uma solução rápida. Obrigado pela sua contribuição ... Corrigi o regex para acomodar sua primeira recomendação. Vou ver se consigo ajustá-lo para explicar as outras circunstâncias que você mencionou.
JC Yamokoski
echo -n "$file"podia funcionar. :-) Ah, e "está em torno da $( ... )expressão (aka "$( ... )") faria isso.
Onipresente
1

Esta versão permite que você selecione explicitamente o número de pontos que deseja manter, começando pelo lado direito.

Além disso, ele substituirá e / ou excluirá outros caracteres, além de pontos, e o caractere de substituição será -substituído por um sublinhado, mas isso pode ser facilmente alterado.

#!/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
Ernest A
fonte