Cambiar el nombre de archivos por lotes con Bash

101

¿Cómo puede Bash cambiar el nombre de una serie de paquetes para eliminar sus números de versión? He estado jugando con ambos expry %%, en vano.

Ejemplos:

Xft2-2.1.13.pkg se convierte en Xft2.pkg

jasper-1.900.1.pkg se convierte en jasper.pkg

xorg-libXrandr-1.2.3.pkg se convierte en xorg-libXrandr.pkg

Jeremy L
fuente
1
Tengo la intención de usar esto con regularidad, como un script de uso general de una sola escritura. Cualquier sistema que utilice tendrá bash, así que no tengo miedo de los bashismos que son bastante útiles.
Jeremy L
De manera más general, consulte también stackoverflow.com/questions/28725333/…
tripleee

Respuestas:

190

Podrías usar la función de expansión de parámetros de bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Se necesitan comillas para los nombres de archivo con espacios.

richq
fuente
1
También me gusta, pero usa características específicas de bash ... Normalmente no las uso a favor de sh "estándar".
Diego Sevilla
2
Para cosas interactivas desechables de una línea, siempre uso esto en lugar de sed-fu. Si fuera un guión, sí, evita los bashismos.
richq
Un problema que encontré con el código: ¿Qué sucede si los archivos ya fueron renombrados y los ejecuto nuevamente? Recibo advertencias por intentar moverme a un subdirectorio de sí mismo.
Jeremy L
1
Para evitar errores de repetición, puede usar el mismo patrón en la parte " .pkg", es decir, "para i en * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; hecho ". Pero los errores son lo suficientemente inocuos (moviéndose al mismo archivo).
richq
3
No es una solución de bash, pero si tiene Perl instalado, proporciona el útil comando de cambio de nombre para el cambio de nombre por lotes, simplemente hágalorename "s/-[0-9.]*//" *.pkg
Rufus
33

Si todos los archivos están en el mismo directorio, la secuencia

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

hará su trabajo. El comando sed creará una secuencia de comandos mv , que luego puede canalizar al shell. Es mejor ejecutar primero la canalización sin el final| sh para verificar que el comando hace lo que desea.

Para recurrir a través de varios directorios, use algo como

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Tenga en cuenta que en sed, la secuencia de agrupación de expresiones regulares tiene corchetes precedidos por una barra invertida, \(y \), en lugar de corchetes simples (y ).

Diomidis Spinellis
fuente
Perdone mi ingenuidad, pero ¿escapar de los paréntesis con barras invertidas no los haría ser tratados como caracteres literales?
devios1
2
En sed, la secuencia de agrupación de expresiones regulares es (, en lugar de un solo corchete.
Diomidis Spinellis
no funcionó para mí, estaría encantado de obtener su ayuda ... El sufijo del archivo FROM se adjunta al archivo TO: $ find. -tipo d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2efectedkeyboard \ 3" / p' | sh >> >>>>> SALIDA >>>> mv: renombrar ./base/src/main/java/com/anysoftkeyboard/base/dictionaries a ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : No existe tal archivo o directorio
Vitali Pom
Parece que le falta la barra invertida entre corchetes.
Diomidis Spinellis
1
No lo use lsen scripts. La primera variedad se puede lograr con printf '%s\n' * | sed ...y con algo de creatividad, probablemente también pueda deshacerse de ella sed. Bash's printfofrece un '%q'código de formato que podría ser útil si tiene nombres de archivo que contienen metacaracteres de shell literal.
tripleee
10

Haré algo como esto:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

se supone que todos sus archivos están en el directorio actual. Si no es así, intente usar buscar como lo aconsejó Javier.

EDITAR : Además, esta versión no usa ninguna función específica de bash, como otras anteriores, lo que lo lleva a una mayor portabilidad.

Diego Sevilla
fuente
Todos están en el mismo directorio. ¿Qué opinas de la respuesta de rq?
Jeremy L
Me gusta no usar funciones específicas de bash, sino más sh estándar.
Diego Sevilla
1
Supuse que esto era para un cambio de nombre rápido, no para escribir la aplicación empresarial portátil multiplataforma de próxima generación ;-)
richq
1
Jaja, bastante justo ... Salido de un hombre con mentalidad de portabilidad como yo :)
Diego Sevilla
Éste no tiene en cuenta el tercer ejemplo: xorg-libXrandr (guión utilizado en el nombre).
Jeremy L
5

Aquí hay un POSIX casi equivalente a la respuesta actualmente aceptada . Esto intercambia la ${variable/substring/replacement}expansión de parámetros solo Bash por una que está disponible en cualquier shell compatible con Bourne.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

La expansión del parámetro ${variable%pattern}produce el valor de variablecon cualquier sufijo que coincida patterneliminado. (También hay ${variable#pattern}que eliminar un prefijo).

Mantuve el subpatrón -[0-9.]*de la respuesta aceptada aunque quizás sea engañoso. No es una expresión regular, sino un patrón global; por lo que no significa "un guión seguido de cero o más números o puntos". En cambio, significa "un guión, seguido de un número o un punto, seguido de cualquier cosa". El "cualquier cosa" será la coincidencia más corta posible, no la más larga. (Bash ofrece ##y %%para recortar el prefijo o sufijo más largo posible, en lugar del más corto).

triples
fuente
5

Podemos asumir que sedestá disponible en cualquier * nix, pero no podemos estar seguros de que sea compatible sed -ncon la generación de comandos mv. ( NOTA: solo GNU sedhace esto).

Aun así, bash incorporado y sed, podemos rápidamente crear una función de shell para hacer esto.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Uso

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Crear carpetas de destino:

Dado mvque no crea carpetas de destino automáticamente, no podemos usar nuestra versión inicial de sedrename.

Es un cambio bastante pequeño, por lo que sería bueno incluir esa función:

Necesitaremos una función de utilidad abspath(o ruta absoluta) ya que bash no tiene esta compilación.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Una vez que lo tengamos, podemos generar la (s) carpeta (s) de destino para un patrón sed / renombrar que incluye una nueva estructura de carpetas.

Esto asegurará que sepamos los nombres de nuestras carpetas de destino. Cuando cambiemos el nombre, tendremos que usarlo en el nombre del archivo de destino.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Aquí está el script completo con reconocimiento de carpetas ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Por supuesto, también funciona cuando no tenemos carpetas de destino específicas.

Si quisiéramos poner todas las canciones en una carpeta, ./Beethoven/podemos hacer esto:

Uso

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Ronda de bonificación ...

Usando este script para mover archivos de carpetas a una sola carpeta:

Suponiendo que quisiéramos recopilar todos los archivos coincidentes y colocarlos en la carpeta actual, podemos hacerlo:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Nota sobre los patrones de expresiones regulares sed

En este script se aplican reglas de patrones sed regulares, estos patrones no son PCRE (Expresiones regulares compatibles con Perl). Podría tener la sintaxis de expresión regular extendida sed, usando sed -ro sed -E dependiendo de su plataforma.

Consulte la compatibilidad con POSIX man re_formatpara obtener una descripción completa de los patrones de expresiones regulares básicas y extendidas de sed.

ocodo
fuente
4

Encuentro que el cambio de nombre es una herramienta mucho más sencilla de usar para este tipo de cosas. Lo encontré en Homebrew para OSX

Por tu ejemplo yo haría:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

La 's' significa sustituto. El formulario es s / searchPattern / replacement / files_to_apply. Necesita usar expresiones regulares para esto, lo que requiere un poco de estudio, pero vale la pena el esfuerzo.

Simon Katan
fuente
Estoy de acuerdo. Para algo ocasional, usar fory sedetc. está bien, pero es mucho más simple y rápido de usar renamesi se encuentra haciendo esto con frecuencia.
argentum2f
¿Es que estás escapando de los períodos?
Big Money
El problema es que esto no es portátil; no solo no todas las plataformas tienen un renamecomando; Además, algunos tendrán una diferente rename de comandos con diferentes características, la sintaxis y / u opciones. Esto se parece al Perl renameque es bastante popular; pero la respuesta realmente debería aclarar esto.
tripleee
3

mejor uso sedpara esto, algo como:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

calcular la expresión regular se deja como ejercicio (al igual que tratar con nombres de archivo que incluyen espacios)

Javier
fuente
Gracias: No se utilizan espacios en los nombres.
Jeremy L
1

Esto parece funcionar asumiendo que

  • todo termina con $ pkg
  • su número de versión siempre comienza con un "-"

quita el .pkg, luego quita - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Steve B.
fuente
Hay algunos paquetes que tienen un guión en su nombre (vea el tercer ejemplo). ¿Esto impacta su código?
Jeremy L
1
Puede ejecutar múltiples expresiones sed usando -e. Ej sed -e 's/\.pkg//g' -e 's/-.*//g'.
martes
No analice la lssalida y cite sus variables. Consulte también shellcheck.net para diagnosticar problemas comunes y antipatrones en scripts de shell.
tripleee
1

Tenía varios *.txtarchivos para cambiar el nombre como .sqlen la misma carpeta. a continuación funcionó para mí:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Kumar Mashalkar
fuente
2
Puede mejorar su respuesta resumiéndola al caso de uso real del OP.
Dakab
Esto tiene varios problemas y un aparente error de sintaxis. Consulte shellcheck.net para empezar.
tripleee
0

Gracias por estas respuestas. También tuve algún tipo de problema. Mover archivos .nzb.queued a archivos .nzb. Tenía espacios y otros cruft en los nombres de archivo y esto resolvió mi problema:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Se basa en la respuesta de Diomidis Spinellis.

La expresión regular crea un grupo para todo el nombre de archivo y un grupo para la parte antes de .nzb.queued y luego crea un comando de movimiento de shell. Con las cuerdas citadas. Esto también evita la creación de un bucle en el script de shell porque esto ya lo hace sed.

Jerry Jacobs
fuente
Cabe señalar que esto solo es cierto para GNU sed. En BSD, sed no interpretará la -nopción de la misma manera.
ocodo