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

¿Alguien puede recomendar una solución segura para reemplazar recursivamente los espacios con caracteres de subrayado en los nombres de archivos y directorios a partir de un directorio raíz dado? 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 

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 “Revision 1.5 1998/12/18 16:16:31 rmb1” de /usr/bin/rename (un script Perl):

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

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 parte “$ {f // / _}” utiliza el mecanismo de expansión de parámetros de bash para reemplazar un patrón dentro de un parámetro con cadena suministrada. La syntax 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 .

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

Al principio no lo conseguí, porque no pensé en directorios.

puedes usar detox por Doug Harple

 detox -r  

Una solución de búsqueda / cambio de nombre . rename es parte de util-linux.

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

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

bash 4.0

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

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 

Aquí hay una solución find -exec (bastante detallada) que escribe avisos de “archivo ya existente” para 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 _ 

Este hace un poco más. Lo uso para cambiar el nombre de mis descargas descargadas (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,$_); } } 

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 

Aquí hay una solución de script bash de tamaño razonable

 #!/bin/bash ( IFS=$'\n' for y in $(ls $1) do mv $1/`echo $y | sed 's/ /\\ /g'` $1/`echo "$y" | sed 's/ /_/g'` done ) 

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 

Esto solo busca 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);

Solo hago 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 

Para archivos en carpeta nombrados / 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 

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 encontrar GNU (gfind) como buscar y luego ejecute la línea de @Michel Krelin :,

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