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.