Como renomear nomes de arquivos para evitar conflitos no Windows ou Mac?

4

Como posso renomear em lote nomes de arquivos para que eles não incluam caracteres que entrem em conflito com outros sistemas de arquivos, como, por exemplo,

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

Note que os dois pontos são o problema neste nome de arquivo. Estes não serão digeridos pelo Windows ou Mac.

Esses arquivos podem ser renomeados para

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

Eu tenho que mover uma grande quantidade de arquivos do Ubuntu para outro sistema operacional. Eu os copiei para uma unidade NTFS usando o Rsync, mas perdi alguns arquivos. Eu também os copiei para uma unidade ext4.

A lista a seguir é dos caracteres reservados:

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

Outra questão é que o Windows não faz distinção entre maiúsculas e minúsculas quando se trata de nomes de arquivos (e da maioria dos sistemas OS X também).

    
por don.joey 07.09.2015 / 15:40

2 respostas

5

Você poderia fazer algo como:

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

Isso substituirá todos esses caracteres por _ . Note que você não precisa substituir / , já que é um caractere inválido para nomes de arquivos em ambos os sistemas de arquivos, mas é usado como o separador de caminho Unix. Estenda para um diretório e todo o seu conteúdo com:

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

Observe que / (que marca o final do padrão) e \ estão com escape. Para manter a exclusividade, você pode adicionar um prefixo aleatório a ele:

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

Uma solução mais completa deve, pelo menos:

  1. Converta todos os caracteres para o mesmo caso
  2. Use um sistema de contagem sã

Isso significa que foo.mp3 não deve se tornar foo.mp3.1 , mas foo.1.mp3 , pois o Windows depende mais de extensões.

Com isso em mente, escrevi o seguinte script. Eu tentei ser não-destrutivo, usando um caminho de prefixo no qual eu posso copiar os arquivos renomeados, em vez de modificar o 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 ]]
    then
        shopt -s nullglob
        files=( "$prefix-"*"" )
        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

Em ação:

$ 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

O script está disponível em meu repositório do Github .

    
por muru 07.09.2015 / 15:48
2

Substitui recursivamente uma lista de strings ou caracteres em nomes de arquivos por outras strings ou caracteres

O script abaixo pode ser usado para substituir uma lista de strings ou caracteres, possivelmente ocorrendo no nome de um arquivo, por uma substituição arbitrária por string . Como o script apenas renomeia o arquivo em si (não o caminho), não há risco de mexer nos diretórios.

A substituição é definida na lista: chars (veja mais abaixo). É possível dar a cada string seu próprio substituto, para poder reverter a renomeação se você quiser fazer isso. (assumindo que a substituição é uma string única). Caso você queira substituir todas as strings problemáticas por um sublinhado, simplesmente defina a lista como:

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

Dupes

Para evitar nomes duplicados, o script primeiro cria o nome "novo". Em seguida, ele verifica se um arquivo com nome semelhante já existe no mesmo diretório. Nesse caso, ele cria um novo nome, precedido por dupe_1 ou dupe_2 , até encontrar um novo nome "disponível" para o arquivo:

torna-se:

O script

#!/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)

Como usar

  1. Copie o script em um arquivo vazio, salve-o como rename_chars.py .
  2. Edite se você quiser a lista de substituição. Como está, o scrip0t substitui todas as ocorrências de caracteres problemáticos por um sublinhado, mas a escolha é sua.
  3. Teste - execute-o em um diretório pelo comando:

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

Nota

Observe que na linha:

("\", "_bsl_"),

em python, uma barra invertida precisa ser escapada por outra barra invertida.

    
por Jacob Vlijm 07.09.2015 / 21:20