Cómo reemplazar espacios en nombres de archivo usando un script bash


264

¿Alguien puede recomendar una solución segura para reemplazar recursivamente espacios con guiones bajos en los nombres de archivos y directorios a partir de un directorio raíz determinado? Por ejemplo:

$ tree
.
|-- a dir
|   `-- file with spaces.txt
`-- b dir
    |-- another file with spaces.txt
    `-- yet another file with spaces.pdf

se convierte en:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf

9
¿Qué desea que suceda si hay un archivo llamado foo bary otro archivo llamado foo_baren el mismo directorio?
— Mark Byers

Buena pregunta. No me gustaría sobrescribir los archivos existentes o perder ningún dato. Debería dejarlo sin cambios ... lo ideal es imprimir una advertencia, pero eso probablemente sea pedir demasiado.
— armandino

Respuestas:


312

Use rename(aka prename), que es un script de Perl que ya puede estar en su sistema. Hazlo en dos pasos:

find -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find -name "* *" -type f | rename 's/ /_/g'

Basado en la respuesta de Jürgen y capaz de manejar múltiples capas de archivos y directorios en un solo enlace usando la versión "Revisión 1.5 1998/12/18 16:16:31 rmb1" de /usr/bin/rename(un script de Perl):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;

55
No es necesario realizar dos pasos: use la búsqueda de profundidad primero: encuentre dir -depth
— Jürgen Hötzel el

3
Oh, acabo de leer la página de renamemanual (no conocía la herramienta) y creo que puedes optimizar tu código cambiando s/ /_/ga y/ /_/;-)
— Michael Krelin - hacker

1
Por supuesto, no obtendrás un aumento de rendimiento. Se trata más de usar la herramienta adecuada. Y toda esta pregunta es sobre micro-optimización más o menos. ¿No es divertido, después de todo? ;-)
— Michael Krelin - hacker

15
Si está ejecutando esto en OS X, deberá brew install rename
— hacerlo

77
Esto no funciona en Centos 7, ya que el comando de cambio de nombre es completamente diferente (es un binario, no un script perl), y no acepta datos de stdin.
— CpnCrunch

357

Yo suelo:

for f in *\ *; do mv "$f" "${f// /_}"; done

Aunque no es recursivo, es bastante rápido y simple. Estoy seguro de que alguien aquí podría actualizarlo para que sea recursivo.

La ${f// /_}parte utiliza el mecanismo de expansión de parámetros de bash para reemplazar un patrón dentro de un parámetro con la cadena suministrada. La sintaxis relevante es ${parameter/pattern/string}. Ver: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html o http://wiki.bash-hackers.org/syntax/pe .


9
Simple y funciona en mac. (Mac no tiene rename, y es demasiado difícil instalar esto con brew ..)
— JonnieJS

66
respuesta asombrosa Utilicé for d in *\ *; do mv "$d" "${d// /}"; doneno bajo puntaje.
— Yoon Lee

1
A diferencia de la respuesta 'find -name', ¡esta funcionó en mi OS X! ¡Gracias Señor!
— Julio Faerman

1
Como referencia, esto puede volverse recursivo fácilmente en bash para usar shopt -s globstary for f in **/*\ *; do .... La globstaropción es interna para bash, mientras que el renamecomando es una herramienta común de Linux y no forma parte de bash.
— ghoti

2
${f// /_}es una expansión variable de Bash para buscar y reemplazar . - El fes la variable del forbucle para cada archivo que contiene un espacio. - El primero //significa "reemplazar todo" (no se detenga en la primera aparición). - Entonces el `/ _` significa" reemplazar espacio con guión bajo "
— Ari

101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

Al principio no pude hacerlo bien, porque no pensé en los directorios.


1
Funciona de maravilla. Gracias michael
— armandino

Dennis, buena captura, fácil de arreglar poniéndola IFS=''delante read. Además, por lo que puedo decir con otros comentarios, sortse puede abandonar el paso a favor de la -depthopción find.
— Michael Krelin - hacker

1
No funciona si un nombre de archivo contiene una \ (barra diagonal inversa). Se puede arreglar agregando una -ropción para leer.
— jfg956

8
Esta debe ser la 50ª vez que visito esta página para copiar y usar su solución. Gracias mucho . Prefiero su respuesta, ya que estoy en una Mac y no tengo el renamecomando sugerido por Dennis.
— Alex Constantin

@AlexConstantin, ¿no macportstienes el rename? Nunca me he molestado en averiguarlo porque no creo que la tarea justifique la utilidad. Y si no lo tiene macports, debería considerar instalarlos;)
— Michael Krelin - hacker


12

Una solución para buscar / renombrar . renombrar es parte de util-linux.

Primero debe descender la profundidad, porque un nombre de archivo de espacios en blanco puede ser parte de un directorio de espacios en blanco:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"

No obtengo ningún cambio cuando ejecuto el tuyo.
— Pausado hasta nuevo aviso.

Compruebe la configuración de util-linux: $ rename --version rename (util-linux-ng 2.17.2)
— Jürgen Hötzel el

Grepping / usr / bin / rename (un script de Perl) revela "Revisión 1.5 1998/12/18 16:16:31 rmb1"
— hasta nuevo aviso.

Hmm ... ¿dónde está tu binario util-linux? Esta ruta de archivo debe ser propiedad de util-linux. ¿No nos usas un sistema GNU-Linux?
— Jürgen Hötzel

1
que solo cambia un espacio en mi carrera, así que "ve a decir fuego en la montaña" se convierte en "go_tell fuego en la montaña".
— brokkr

6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done

Parece que esto hará un mv para sí mismo si un archivo o nombre de directorio no tiene espacio (mv: no se puede mover a' to a subdirectory of itself, a / a ')
— armandino

no importa simplemente elimine el mensaje de error redirigiendo a /dev/null.
— ghostdog74

ghostdog, generar mvcincuenta y cinco mil veces solo para cambiar el nombre de cuatro archivos puede ser un poco pesado, incluso si no inunda al usuario con mensajes.
— Michael Krelin - pirata informático

krelin, incluso find pasará por esos 55000 archivos que mencionaste para encontrar aquellos con espacios y luego cambiará el nombre. En el fondo, todavía está pasando por todo. Si lo desea, una comprobación inicial de espacios antes del cambio de nombre lo hará.
— ghostdog74

Estaba hablando de generar mv, no pasar. ¿No lo haría for file in *' '*o algo así haría un mejor trabajo?
— Michael Krelin - hacker

6

puedes usar esto:

    find . -name '* *' | while read fname 

do
        new_fname=`echo $fname | tr " " "_"`

        if [ -e $new_fname ]
        then
                echo "File $new_fname already exists. Not replacing $fname"
        else
                echo "Creating new file $new_fname to replace $fname"
                mv "$fname" $new_fname
        fi
done

3

Versión recursiva de las respuestas de Naidim.

find . -name "* *" | awk '{ print length, $0 }' | sort -nr -s | cut -d" " -f2- | while read f; do base=$(basename "$f"); newbase="${base// /_}"; mv "$(dirname "$f")/$(basename "$f")" "$(dirname "$f")/$newbase"; done

2

Aquí hay una solución find -exec (bastante detallada) que escribe advertencias "el archivo ya existe" en stderr:

function trspace() {
   declare dir name bname dname newname replace_char
   [ $# -lt 1 -o $# -gt 2 ] && { echo "usage: trspace dir char"; return 1; }
   dir="${1}"
   replace_char="${2:-_}"
   find "${dir}" -xdev -depth -name $'*[ \t\r\n\v\f]*' -exec bash -c '
      for ((i=1; i<=$#; i++)); do
         name="${@:i:1}"
         dname="${name%/*}"
         bname="${name##*/}"
         newname="${dname}/${bname//[[:space:]]/${0}}"
         if [[ -e "${newname}" ]]; then
            echo "Warning: file already exists: ${newname}" 1>&2
         else
            mv "${name}" "${newname}"
         fi
      done
  ' "${replace_char}" '{}' +
}

trspace rootdir _

2

Este hace un poco más. Lo uso para cambiar el nombre de mis torrents descargados (sin caracteres especiales (no ASCII), espacios, puntos múltiples, etc.).

#!/usr/bin/perl

&rena(`find . -type d`);
&rena(`find . -type f`);

sub rena
{
    ($elems)=@_;
    @t=split /\n/,$elems;

    for $e (@t)
    {
    $_=$e;
    # remove ./ of find
    s/^\.\///;
    # non ascii transliterate
    tr [\200-\377][_];
    tr [\000-\40][_];
    # special characters we do not want in paths
    s/[ \-\,\;\?\+\'\"\!\[\]\(\)\@\#]/_/g;
    # multiple dots except for extension
    while (/\..*\./)
    {
        s/\./_/;
    }
    # only one _ consecutive
    s/_+/_/g;
    next if ($_ eq $e ) or ("./$_" eq $e);
    print "$e -> $_\n";
    rename ($e,$_);
    }
}

1

Encontré alrededor de este script, puede ser interesante :)

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS

Falla en archivos con nuevas líneas en su nombre.
— ghoti


1

Para aquellos que luchan con esto usando macOS, primero instale todas las herramientas:

 brew install tree findutils rename

Luego, cuando sea necesario cambiar el nombre, cree un alias para GNU find (gfind) como find. Luego ejecute el código de @Michel Krelin:

alias find=gfind 
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done   

0

Esto solo encuentra archivos dentro del directorio actual y los renombra . Tengo este alias.

find ./ -name "* *" -type f -d 1 | perl -ple '$file = $_; $file =~ s/\s+/_/g; rename($_, $file);


0

Acabo de hacer uno para mi propio propósito. Puede usarlo como referencia.

#!/bin/bash
cd /vzwhome/c0cheh1/dev_source/UB_14_8
for file in *
do
    echo $file
    cd "/vzwhome/c0cheh1/dev_source/UB_14_8/$file/Configuration/$file"
    echo "==> `pwd`"
    for subfile in *\ *; do [ -d "$subfile" ] && ( mv "$subfile" "$(echo $subfile | sed -e 's/ /_/g')" ); done
    ls
    cd /vzwhome/c0cheh1/dev_source/UB_14_8
done

0

Para archivos en la carpeta llamada / archivos

for i in `IFS="";find /files -name *\ *`
do
   echo $i
done > /tmp/list


while read line
do
   mv "$line" `echo $line | sed 's/ /_/g'`
done < /tmp/list

rm /tmp/list

0

Mi solución al problema es un script bash:

#!/bin/bash
directory=$1
cd "$directory"
while [ "$(find ./ -regex '.* .*' | wc -l)" -gt 0 ];
do filename="$(find ./ -regex '.* .*' | head -n 1)"
mv "$filename" "$(echo "$filename" | sed 's|'" "'|_|g')"
done

simplemente coloque el nombre del directorio, en el que desea aplicar el script, como argumento después de ejecutar el script.


0

En macOS

Al igual que la respuesta elegida.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.