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).
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:
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 .
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 = [
("<", "_"),
(">", "_"),
(":", "_"),
('"', "_"),
("/", "_"),
("\\", "_"),
("|", "_"),
("?", "_"),
("*", "_"),
]
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_1
ou dupe_2
, jusqu'à trouver un nouveau nom "disponible" pour le fichier:
devient:
#!/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)
rename_chars.py
.Testez-le sur un répertoire avec la commande:
python3 /path/to/rename_chars.py <directory_to_rename>
Notez que dans la ligne:
("\\", "_bsl_"),
en python, une barre oblique inversée doit être évitée par une autre barre oblique inversée.