Reemplace los puntos con guiones bajos en los nombres de archivo, dejando la extensión intacta

8

Tengo un script bash que estoy tratando de reemplazar puntos en los nombres de archivo y reemplazarlos con guiones bajos, dejando la extensión intacta (estoy en Centos 6 por cierto). Como puede ver en el resultado a continuación, el script funciona cuando hay un punto para reemplazar, pero en los casos en que el único punto es la extensión, el script aún intenta cambiar el nombre del archivo, en lugar de ignorarlo. ¿Alguien puede señalar cómo debería manejar esto mejor? Gracias por cualquier ayuda.

Mi script (defectuoso):

#!/bin/bash

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

Entrada de muestra:

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

Salida:

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
fuente
1
¿Qué pasa con los casos difíciles como los tar.gzarchivos? Querrías que lo resuelvan file.tar.gz, no file_tar.gz.
IQAndreas

Respuestas:

10

Creo que este programa hará lo que quieras. Lo he probado y funciona en varios casos interesantes (como ninguna extensión):

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

El problema principal que tenía era que la ##expansión no estaba haciendo lo que quería. Siempre he considerado que la expansión de parámetros de shell en bash es algo así como un arte negro. Las explicaciones en el manual no son completamente claras y carecen de ejemplos de apoyo de cómo se supone que funciona la expansión. También son bastante crípticos.

Personalmente, habría escrito un pequeño guión en el sedque manipulaba el nombre de la manera que quería, o hubiera escrito un pequeño guión perlque simplemente hiciera todo. Una de las otras personas que respondieron tomó ese enfoque.

Otra cosa que me gustaría señalar es mi uso de las citas. Cada vez que hago algo con scripts de shell, les recuerdo a las personas que tengan mucho cuidado con sus citas. Una gran fuente de problemas en los scripts de shell es el intérprete de shell que no debe hacerlo. Y las reglas de las citas están lejos de ser obvias. Creo que este script de shell no tiene problemas de citas.

De todo género
fuente
Esto realmente hace el trabajo bien :) Gracias por la explicación también sobre la expansión de shell.
bsod99
Además de reemplazar el punto (.) Con (_), ¿cómo puedo eliminar espacios en los nombres de archivo?
discipulus
Este es un guión muy bonito. Las únicas mejoras que puedo ver es hacer que vuelva a aparecer un árbol de directorios y permitirle reemplazar $ 1 por $ 2, pero son menores. (O, como solía decir mi maestro, "¡se fue como un ejercicio para el alumno"!)
lbutlr
4

Uso for thisfile in *.*.*(es decir, recorrer archivos con dos puntos o más en su nombre). Recuerde citar sus variables y usar --para marcar el final de las opciones como enmv -- "$thisfile" "$newname.$extension"

Con zsh.

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'
Stéphane Chazelas
fuente
Es posible que haya implementado su sugerencia incorrectamente, pero esto da como resultado mv - . . * _ . *
bsod99
3

Qué tal esto:

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

DESCARGO DE RESPONSABILIDAD: pruébelo primero en un directorio ficticio, ¡no lo he probado!

Joseph R.
fuente
Conciso, concisa, casi de solo escritura. Yay perl! risita
Omnifarious
¿Escribir solamente? ¡Apuesto a que es la primera vez que Perl se llama así! ( Ríe de nuevo )
Joseph R.
Intentó mejorar la legibilidad allí. Espero que esto se sienta menos "solo de escritura" :)
Joseph R.
1
Sí, eso ayuda mucho. Es una pena el único carácter razonable para usar como marcador de posición /.
Omnifarioso
2

Parece que algunas buenas respuestas ya están disponibles, pero aquí hay otra que usa try sed:

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done
JC Yamokoski
fuente
¿Cómo seddecide a qué .*aplicar munch máximo? Me siento mucho mejor acerca de si el segundo .*eran [^_]*.
Omnifarioso
También creo que esto se debe al caso de 'sin extensión' y a un nombre de archivo con \tcaracteres.
Omnifarioso
Esto definitivamente fue solo una solución rápida. Gracias por su aporte ... Arregle la expresión regular para acomodar su primera recomendación. Veré si puedo modificarlo para tener en cuenta las otras circunstancias que mencionaste.
JC Yamokoski
echo -n "$file"trabajaría. :-) Ah, y "alrededor de la $( ... )expresión (aka "$( ... )") lo haría.
Omnifarioso
1

Esta versión le permite seleccionar explícitamente el número de puntos que desea mantener, comenzando desde el lado derecho.

También reemplazará y / o eliminará otros caracteres además de los puntos, y el carácter de reemplazo es en -lugar de un guión bajo, pero esto se puede cambiar fácilmente.

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