Comment remplacer des espaces dans les noms de fichiers à l'aide d'un script bash

264

Quelqu'un peut-il recommander une solution sûre pour remplacer récursivement les espaces par des traits de soulignement dans les noms de fichiers et de répertoires à partir d'un répertoire racine donné? Par exemple:

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

devient:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf
armandino
la source
9
Que voulez-vous qu'il se passe s'il y a un fichier appelé foo baret un autre fichier appelé foo_bardans le même répertoire?
Mark Byers
Bonne question. Je ne voudrais pas écraser les fichiers existants ou perdre des données. Il devrait rester inchangé. Idéalement, imprimer un avertissement, mais c'est probablement trop demander.
armandino

Réponses:

312

Utilisez rename(aka prename) qui est un script Perl qui peut déjà être sur votre système. Faites-le en deux étapes:

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

Basé sur la réponse de Jürgen et capable de gérer plusieurs couches de fichiers et de répertoires en une seule liaison en utilisant la version "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' "{}" \;
En pause jusqu'à nouvel ordre.
la source
5
Pas besoin de deux étapes: Utilisez la recherche en profondeur d'abord: trouvez dir -depth
Jürgen Hötzel
3
Oh, je viens de lire la renamepage de manuel (je ne connaissais pas l'outil) et je pense que vous pouvez optimiser votre code en changeant s/ /_/gen y/ /_/;-)
Michael Krelin - hacker
1
Bien sûr, vous n'obtiendrez pas une amélioration des performances. Il s'agit davantage d'utiliser le bon outil. Et toute cette question concerne plus ou moins la micro-optimisation. N'est-ce pas amusant, après tout? ;-)
Michael Krelin - hacker
15
Si vous utilisez ceci sur OS X, vous devrezbrew install rename
loeschg
7
Cela ne fonctionne pas sur Centos 7, car la commande renommer est complètement différente (c'est un binaire, pas un script perl), et elle n'accepte pas les données de stdin.
CpnCrunch
357

J'utilise:

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

Bien que ce ne soit pas récursif, c'est assez rapide et simple. Je suis sûr que quelqu'un ici pourrait le mettre à jour pour être récursif.

La ${f// /_}partie utilise le mécanisme d'expansion des paramètres de bash pour remplacer un modèle dans un paramètre avec la chaîne fournie. La syntaxe appropriée est ${parameter/pattern/string}. Voir: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html ou http://wiki.bash-hackers.org/syntax/pe .

Naidim
la source
9
Simple et fonctionne sous mac. (mac n'a pas rename, et c'est trop difficile à installer avec brew ..)
JonnieJS
6
réponse géniale. j'ai utilisé for d in *\ *; do mv "$d" "${d// /}"; donenon sous score.
Yoon Lee
1
Contrairement à la réponse «find -name», celle-ci a fonctionné sur mon OS X! Merci Monsieur!
Julio Faerman du
1
Pour référence, cela peut facilement devenir récursif en bash pour utiliser shopt -s globstaret for f in **/*\ *; do .... L' globstaroption est interne à bash, tandis que la renamecommande est un outil Linux commun et ne fait pas partie de bash.
ghoti
2
${f// /_}est une extension de variable Bash pour la recherche et le remplacement . - La fest la variable de la forboucle pour chaque fichier qui contient un espace. - Le premier //signifie "remplacer tout" (ne vous arrêtez pas à la première occurrence). - Ensuite, le `/ _` signifie" remplacer l'espace par un trait de soulignement "
Ari
101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

n'a pas réussi à le faire correctement au début, parce que je ne pensais pas aux répertoires.

Michael Krelin - pirate
la source
1
Fonctionne comme un charme. Merci Michael.
armandino
Dennis, bonne prise, facilement réparable en mettant IFS=''devant read. En outre, pour ce que je peux dire par d'autres commentaires, l' sortétape peut être supprimée en faveur de l' -depthoption find.
Michael Krelin - pirate informatique
1
Ne fonctionne pas si un nom de fichier contient une \ (barre oblique inverse). Peut être corrigé en ajoutant une -roption de lecture.
jfg956
8
Ce doit être la 50e fois que je visite cette page pour copier et utiliser votre solution. Merci beaucoup . Je préfère votre réponse, car je suis sur un Mac et je n'ai pas la renamecommande suggérée par Dennis.
Alex Constantin
@AlexConstantin, vous n'avez pas macportsle rename? Je n'ai jamais pris la peine de le découvrir car je ne pense pas que la tâche justifie l'utilité. Et si vous n'en avez pas macports, vous devriez envisager de les installer;)
Michael Krelin - hacker
30

vous pouvez utiliser detoxpar Doug Harple

detox -r <folder>
user78274
la source
12

Une solution trouver / renommer . renommer fait partie d'util-linux.

Vous devez d'abord descendre la profondeur, car un nom de fichier d'espaces blancs peut faire partie d'un répertoire d'espaces blancs:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"
Jürgen Hötzel
la source
Je ne reçois aucun changement du tout quand je dirige le vôtre.
pause jusqu'à nouvel ordre.
Vérifiez la configuration util-linux: $ rename --version rename (util-linux-ng 2.17.2)
Jürgen Hötzel
Grepping / usr / bin / rename (un script Perl) révèle "Revision 1.5 1998/12/18 16:16:31 rmb1"
Pause jusqu'à nouvel ordre.
Hmm ... où est passé votre binaire util-linux? Ce chemin de fichier doit appartenir à util-linux. Vous ne nous utilisez pas de système GNU-Linux?
Jürgen Hötzel
1
qui ne change qu'un espace dans ma course, donc "allez dire feu sur la montagne" devient "go_tell feu sur la montagne".
brokkr
6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done
ghostdog74
la source
On dirait que cela fera un mv à lui-même si un nom de fichier ou de répertoire n'a pas d'espace dedans (mv: ne peut pas déplacer a' to a subdirectory of itself, un / a ')
armandino
n'a pas d'importance. il suffit de supprimer le message d'erreur en redirigeant vers /dev/null.
ghostdog74
ghostdog, engendrer mvcinquante cinq mille fois seulement pour renommer quatre fichiers peut être un peu surchargé même si vous n'inondez pas l'utilisateur de messages.
Michael Krelin - hacker
krelin, même find passera par les 55000 fichiers que vous avez mentionnés pour trouver ceux avec des espaces, puis faire le renommage. À l'arrière, son passe toujours par tout. Si vous le souhaitez, une vérification initiale des espaces avant de renommer le fera.
ghostdog74
Je parlais de frai mv, pas de passage. Est for file in *' '*-ce que certains d'entre eux ne feraient pas un meilleur travail?
Michael Krelin - hacker
6

vous pouvez utiliser ceci:

    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
Itamar
la source
3

Version récursive des réponses 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
Junyeop Lee
la source
2

Voici une solution (assez détaillée) de find -exec qui écrit des avertissements "le fichier existe déjà" dans 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 _
yabt
la source
2

Celui-ci en fait un peu plus. Je l'utilise pour renommer mes torrents téléchargés (pas de caractères spéciaux (non ASCII), d'espaces, de points multiples, 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,$_);
    }
}
degi
la source
1

J'ai trouvé autour de ce script, ça peut être intéressant :)

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS
Juan Sebastian Totero
la source
Échoue sur les fichiers avec des retours à la ligne dans leur nom.
ghoti
1

Voici une solution de script bash de taille raisonnable

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

Pour ceux qui ont du mal à utiliser macOS, installez d'abord tous les outils:

 brew install tree findutils rename

Ensuite, lorsque vous devez renommer, créez un alias pour GNU find (gfind) en tant que find. Exécutez ensuite le code de @Michel Krelin:

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

Cela ne trouve que les fichiers dans le répertoire courant et les renomme . J'ai cette alias.

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

user1060059
la source
0

Je fais juste un pour mon propre but. Vous pouvez l'utiliser comme référence.

#!/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
Hongtao
la source
0

Pour les fichiers du dossier nommé / files

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
Marcos Jean Sampaio
la source
0

Ma solution au problème est 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

il suffit de mettre le nom du répertoire, sur lequel vous souhaitez appliquer le script, comme argument après avoir exécuté le script.

Ágoston Volcz
la source
0

Sous macOS

Tout comme la réponse choisie.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
CK
la source