web-dev-qa-db-fra.com

Comment renommer les noms de fichiers pour éviter les conflits sous Windows ou Mac?

Comment puis-je renommer par lots les noms de fichiers afin qu'ils n'incluent pas de caractères en conflit avec d'autres systèmes de fichiers, comme par exemple,

Screenshot 2015-09-07-25:10:10

Notez que les deux points sont le problème dans ce nom de fichier. Celles-ci ne seront pas digérées par Windows ou Mac.

Ces fichiers pourraient être renommés en

Screenshot 2015-09-07-25--10--10

Je dois déplacer une grande quantité de fichiers d'Ubuntu vers un autre système d'exploitation. Je les ai copiés sur un lecteur NTFS à l'aide de Rsync, mais certains fichiers ont été perdus. Je les ai également copiés sur un lecteur ext4.

La liste suivante contient les caractères réservés:

< (less than)
> (greater than)
: (colon)
" (double quote)
/ (forward slash)
\ (backslash)
| (vertical bar or pipe)
? (question mark)
* (asterisk)

Un autre problème est que Windows n’est pas sensible à la casse en ce qui concerne les noms de fichiers (et la plupart des systèmes OS X également).

5
don.joey

Vous pourriez faire quelque chose comme:

rename 's/[<>:"\\|?*]/_/g' /path/to/file

Ceci remplacera tous ces caractères par un _. Notez qu'il n'est pas nécessaire de remplacer /, car il s'agit d'un caractère non valide pour les noms de fichiers dans les deux systèmes de fichiers, mais il est utilisé comme séparateur de chemin Unix. Étendre à un répertoire et tout son contenu avec:

find /path/to/directory -depth -exec rename 's/[<>:"\\|?*]/_/g' {} +

Notez que / (qui marque la fin du motif) et \ sont échappés. Pour conserver l'unicité, vous pouvez lui ajouter un préfixe aléatoire:

$ rename -n 's/[<>:"\/\\|?*]/_/g && s/^/int(Rand(10000))/e' a\\b
a\b renamed as 8714a_b

Une solution plus complète devrait au moins:

  1. Convertir tous les caractères dans le même cas
  2. Utilisez un système de comptage sensé

Cela signifie que foo.mp3 ne devrait pas devenir foo.mp3.1, mais foo.1.mp3, car Windows dépend davantage des extensions.

Dans cet esprit, j'ai écrit le script suivant. J'ai essayé d'être non destructif, en utilisant un chemin de préfixe dans lequel je peux copier les fichiers renommés, au lieu de modifier l'original.

#! /bin/bash

windows_chars='<>:"\|?*'
prefix="windows/"

# Find number of files/directories which has this name as a prefix
find_num_files ()
(
    if [[ -e $prefix$1$2 ]]
    then
        shopt -s nullglob
        files=( "$prefix$1-"*"$2" )
        echo ${#files[@]}
    fi
)

# From http://www.Shell-fu.org/lister.php?id=542
# Joins strings with a separator. Separator not present for
# Edge case of single string.
str_join ()
(
    IFS=${1:?"Missing separator"}
    shift
    printf "%s" "$*"
)

for i
do
    # convert to lower case, then replace special chars with _
    new_name=$(tr "$windows_chars" _ <<<"${i,,}")

    # if a directory, make it, instead of copying contents
    if [[ -d $i ]]
    then
        mkdir -p "$prefix$new_name"
        echo mkdir -p "$prefix$new_name"
    else
        # get filename without extension
        name_wo_ext=${new_name%.*}
        # get extension
        # The trick is to make sure that, for:
        # "a.b.c", name_wo_ext is "a.b" and ext is ".c"
        # "abc", name_wo_ext is "abc" and ext is empty
        # Then, we can join the strings without worrying about the
        # . before an extension
        ext=${new_name#$name_wo_ext}
        count=$(find_num_files "$name_wo_ext" "$ext")
        name_wo_ext=$(str_join - "$name_wo_ext" $count)
        cp "$i" "$prefix$name_wo_ext$ext"
        echo cp "$i" "$prefix$name_wo_ext$ext"
    fi
done

En action:

$ tree a:b
a:b
├── b:c
│   ├── a:d
│   ├── A:D
│   ├── a:d.b
│   └── a:D.b
├── B:c
└── B"c
    └── a<d.b

3 directories, 5 files
$ find a:b -exec ./rename-windows.sh {} +
mkdir -p windows/a_b
mkdir -p windows/a_b/b_c
mkdir -p windows/a_b/b_c
cp a:b/B"c/a<d.b windows/a_b/b_c/a_d.b
mkdir -p windows/a_b/b_c
cp a:b/b:c/a:D.b windows/a_b/b_c/a_d-0.b
cp a:b/b:c/A:D windows/a_b/b_c/a_d
cp a:b/b:c/a:d windows/a_b/b_c/a_d-1
cp a:b/b:c/a:d.b windows/a_b/b_c/a_d-1.b
$ tree windows/
windows/
└── a_b
    └── b_c
        ├── a_d
        ├── a_d-0.b
        ├── a_d-1
        ├── a_d-1.b
        └── a_d.b

2 directories, 5 files

Le script est disponible dans mon dépôt Github .

6
muru

Remplacer de manière récursive une liste de chaînes ou de caractères dans les noms de fichiers par d'autres chaînes ou de caractères

Le script ci-dessous peut être utilisé pour remplacer une liste de chaînes ou de caractères, éventuellement dans le nom d'un fichier, par un remplacement arbitraire par chaîne . Comme le script ne renomme que le fichier lui-même (pas le chemin), il n’ya aucun risque de jouer avec les répertoires.

Le remplacement est défini dans la liste: chars (voir plus bas). Il est possible de donner à chaque chaîne son propre remplacement afin de pouvoir inverser le changement de nom si vous le souhaitez. (en supposant que le remplacement est une chaîne unique). Si vous souhaitez remplacer toutes les chaînes problématiques par un trait de soulignement, définissez simplement la liste comme suit:

chars = [
    ("<", "_"),
    (">", "_"),
    (":", "_"),
    ('"', "_"),
    ("/", "_"),
    ("\\", "_"),
    ("|", "_"),
    ("?", "_"),
    ("*", "_"),
    ]

Dupes

Pour éviter les doublons, le script crée d'abord le "nouveau" nom. Il vérifie ensuite si un fichier portant le même nom existe déjà dans le même répertoire. Si tel est le cas, il crée un nouveau nom, précédé de dupe_1ou dupe_2, jusqu'à trouver un nouveau nom "disponible" pour le fichier:

enter image description here

devient:

enter image description here

Le scénario

#!/usr/bin/env python3
import os
import shutil
import sys

directory = sys.argv[1]

# --- set replacement below in the format ("<string>", "<replacement>") as below
chars = [
    ("<", "_"),
    (">", "_"),
    (":", "_"),
    ('"', "_"),
    ("/", "_"),
    ("\\", "_"),
    ("|", "_"),
    ("?", "_"),
    ("*", "_"),
    ]
# ---

for root, dirs, files in os.walk(directory):
    for file in files:
        newfile = file
        for c in chars:
            newfile = newfile.replace(c[0], c[1])
        if newfile != file:
            tempname = newfile; n = 0
            while os.path.exists(root+"/"+newfile):
                n = n+1; newfile = "dupe_"+str(n)+"_"+tempname
            shutil.move(root+"/"+file, root+"/"+newfile)

Comment utiliser

  1. Copiez le script dans un fichier vide, enregistrez-le sous le nom rename_chars.py.
  2. Modifiez si vous voulez la liste de remplacement. En l’occurrence, le script remplace toutes les occurrences de caractères problématiques par un trait de soulignement, mais vous avez le choix.
  3. Testez-le sur un répertoire avec la commande:

    python3 /path/to/rename_chars.py <directory_to_rename>
    

Remarque

Notez que dans la ligne:

("\\", "_bsl_"),

en python, une barre oblique inversée doit être évitée par une autre barre oblique inversée.

2
Jacob Vlijm